@saolabs/client 1.0.0 → 1.0.2

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 (378) 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 +17 -15
  9. package/dist/src/core/app/Application.d.ts.map +1 -1
  10. package/dist/src/core/app/Application.js +41 -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 +74 -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 +125 -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 +13 -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/hellpers/utils.d.ts +2 -2
  178. package/dist/src/core/hellpers/utils.js +4 -4
  179. package/dist/src/core/helpers/ApiClient.d.ts +58 -0
  180. package/dist/src/core/helpers/ApiClient.d.ts.map +1 -0
  181. package/dist/src/core/helpers/ApiClient.js +131 -0
  182. package/dist/src/core/helpers/ApiClient.js.map +1 -0
  183. package/dist/src/core/helpers/app.d.ts +3 -0
  184. package/dist/src/core/helpers/app.d.ts.map +1 -0
  185. package/dist/src/core/helpers/app.js +63 -0
  186. package/dist/src/core/helpers/app.js.map +1 -0
  187. package/dist/src/core/helpers/transition.d.ts +34 -0
  188. package/dist/src/core/helpers/transition.d.ts.map +1 -0
  189. package/dist/src/core/helpers/transition.js +118 -0
  190. package/dist/src/core/helpers/transition.js.map +1 -0
  191. package/dist/src/core/helpers/utils.d.ts +26 -0
  192. package/dist/src/core/helpers/utils.d.ts.map +1 -0
  193. package/dist/src/core/helpers/utils.js +96 -0
  194. package/dist/src/core/helpers/utils.js.map +1 -0
  195. package/dist/src/core/helpers/view.d.ts +39 -0
  196. package/dist/src/core/helpers/view.d.ts.map +1 -0
  197. package/dist/src/core/helpers/view.js +286 -0
  198. package/dist/src/core/helpers/view.js.map +1 -0
  199. package/dist/src/core/routers/Router.d.ts +108 -7
  200. package/dist/src/core/routers/Router.d.ts.map +1 -1
  201. package/dist/src/core/routers/Router.js +444 -68
  202. package/dist/src/core/routers/Router.js.map +1 -1
  203. package/dist/src/core/routers/index.d.ts +2 -2
  204. package/dist/src/core/routers/index.d.ts.map +1 -1
  205. package/dist/src/core/routers/index.js +1 -1
  206. package/dist/src/core/routers/index.js.map +1 -1
  207. package/dist/src/core/services/AssetManager.d.ts +112 -0
  208. package/dist/src/core/services/AssetManager.d.ts.map +1 -0
  209. package/dist/src/core/services/AssetManager.js +361 -0
  210. package/dist/src/core/services/AssetManager.js.map +1 -0
  211. package/dist/src/core/services/BlockManager.d.ts +73 -2
  212. package/dist/src/core/services/BlockManager.d.ts.map +1 -1
  213. package/dist/src/core/services/BlockManager.js +270 -48
  214. package/dist/src/core/services/BlockManager.js.map +1 -1
  215. package/dist/src/core/services/DomService.js +3 -3
  216. package/dist/src/core/services/DomService.js.map +1 -1
  217. package/dist/src/core/services/HeadService.d.ts +24 -0
  218. package/dist/src/core/services/HeadService.d.ts.map +1 -0
  219. package/dist/src/core/services/HeadService.js +120 -0
  220. package/dist/src/core/services/HeadService.js.map +1 -0
  221. package/dist/src/core/services/HelperService.d.ts +14 -3
  222. package/dist/src/core/services/HelperService.d.ts.map +1 -1
  223. package/dist/src/core/services/HelperService.js +32 -2
  224. package/dist/src/core/services/HelperService.js.map +1 -1
  225. package/dist/src/core/services/HttpService.d.ts +8 -0
  226. package/dist/src/core/services/HttpService.d.ts.map +1 -1
  227. package/dist/src/core/services/HttpService.js +121 -47
  228. package/dist/src/core/services/HttpService.js.map +1 -1
  229. package/dist/src/core/services/MarkerRegistry.d.ts +51 -8
  230. package/dist/src/core/services/MarkerRegistry.d.ts.map +1 -1
  231. package/dist/src/core/services/MarkerRegistry.js +135 -14
  232. package/dist/src/core/services/MarkerRegistry.js.map +1 -1
  233. package/dist/src/core/services/PageCache.d.ts +101 -0
  234. package/dist/src/core/services/PageCache.d.ts.map +1 -0
  235. package/dist/src/core/services/PageCache.js +184 -0
  236. package/dist/src/core/services/PageCache.js.map +1 -0
  237. package/dist/src/core/services/SectionManager.d.ts +100 -0
  238. package/dist/src/core/services/SectionManager.d.ts.map +1 -0
  239. package/dist/src/core/services/SectionManager.js +408 -0
  240. package/dist/src/core/services/SectionManager.js.map +1 -0
  241. package/dist/src/core/services/StorageService.js +1 -1
  242. package/dist/src/core/services/TranslationService.d.ts +21 -0
  243. package/dist/src/core/services/TranslationService.d.ts.map +1 -0
  244. package/dist/src/core/services/TranslationService.js +95 -0
  245. package/dist/src/core/services/TranslationService.js.map +1 -0
  246. package/dist/src/core/services/index.d.ts +11 -8
  247. package/dist/src/core/services/index.d.ts.map +1 -1
  248. package/dist/src/core/services/index.js +11 -8
  249. package/dist/src/core/services/index.js.map +1 -1
  250. package/dist/src/core/types/utils.d.ts +8 -8
  251. package/dist/src/core/types/utils.d.ts.map +1 -1
  252. package/dist/src/core/view/LoopContext.d.ts +21 -1
  253. package/dist/src/core/view/LoopContext.d.ts.map +1 -1
  254. package/dist/src/core/view/LoopContext.js +36 -0
  255. package/dist/src/core/view/LoopContext.js.map +1 -1
  256. package/dist/src/core/view/ResourceScope.d.ts +38 -0
  257. package/dist/src/core/view/ResourceScope.d.ts.map +1 -0
  258. package/dist/src/core/view/ResourceScope.js +167 -0
  259. package/dist/src/core/view/ResourceScope.js.map +1 -0
  260. package/dist/src/core/view/Section.d.ts +17 -32
  261. package/dist/src/core/view/Section.d.ts.map +1 -1
  262. package/dist/src/core/view/Section.js +14 -55
  263. package/dist/src/core/view/Section.js.map +1 -1
  264. package/dist/src/core/view/View.d.ts +43 -7
  265. package/dist/src/core/view/View.d.ts.map +1 -1
  266. package/dist/src/core/view/View.js +22 -4
  267. package/dist/src/core/view/View.js.map +1 -1
  268. package/dist/src/core/view/ViewController.d.ts +313 -40
  269. package/dist/src/core/view/ViewController.d.ts.map +1 -1
  270. package/dist/src/core/view/ViewController.js +1002 -147
  271. package/dist/src/core/view/ViewController.js.map +1 -1
  272. package/dist/src/core/view/ViewManager.d.ts +318 -27
  273. package/dist/src/core/view/ViewManager.d.ts.map +1 -1
  274. package/dist/src/core/view/ViewManager.js +1199 -258
  275. package/dist/src/core/view/ViewManager.js.map +1 -1
  276. package/dist/src/core/view/ViewState.d.ts +185 -6
  277. package/dist/src/core/view/ViewState.d.ts.map +1 -1
  278. package/dist/src/core/view/ViewState.js +851 -123
  279. package/dist/src/core/view/ViewState.js.map +1 -1
  280. package/dist/src/core/view/index.d.ts +5 -5
  281. package/dist/src/core/view/index.d.ts.map +1 -1
  282. package/dist/src/core/view/index.js +5 -5
  283. package/dist/src/core/view/index.js.map +1 -1
  284. package/dist/src/testing/index.d.ts +69 -0
  285. package/dist/src/testing/index.d.ts.map +1 -0
  286. package/dist/src/testing/index.js +140 -0
  287. package/dist/src/testing/index.js.map +1 -0
  288. package/index.ts +29 -5
  289. package/package.json +30 -21
  290. package/plugins.ts +2 -2
  291. package/src/core/app/Application.ts +58 -31
  292. package/src/core/bootstrap/app.ts +80 -6
  293. package/src/core/bootstrap/bundle.ts +101 -0
  294. package/src/core/bootstrap/providers/ApiServiceProvider.ts +4 -4
  295. package/src/core/bootstrap/providers/CoreServiceProvider.ts +14 -8
  296. package/src/core/bootstrap/providers/HelperServiceProvider.ts +8 -3
  297. package/src/core/bootstrap/providers/RouteServiceProvider.ts +15 -4
  298. package/src/core/bootstrap/providers/ServiceProvider.ts +8 -8
  299. package/src/core/bootstrap/providers/ViewServiceProvider.ts +3 -3
  300. package/src/core/bootstrap/providers/default-providers.ts +51 -16
  301. package/src/core/bootstrap/providers/index.ts +9 -9
  302. package/src/core/bootstrap/providers/provider-order.test.ts +16 -27
  303. package/src/core/bootstrap/providers/provider-order.ts +1 -1
  304. package/src/core/bootstrap/ssr.ts +136 -0
  305. package/src/core/cache/ViewCache.ts +1 -1
  306. package/src/core/cache/index.ts +1 -1
  307. package/src/core/contracts/ApplicationInterface.ts +13 -11
  308. package/src/core/contracts/BlockInterface.ts +18 -18
  309. package/src/core/contracts/BundleInterface.ts +40 -0
  310. package/src/core/contracts/ComponentInterface.ts +7 -6
  311. package/src/core/contracts/ElementInterface.ts +95 -27
  312. package/src/core/contracts/HeadServiceInterface.ts +41 -0
  313. package/src/core/contracts/HelperInterface.ts +9 -1
  314. package/src/core/contracts/HttpServiceInterface.ts +3 -0
  315. package/src/core/contracts/MarkerInterface.ts +22 -49
  316. package/src/core/contracts/ReactiveInterface.ts +9 -9
  317. package/src/core/contracts/RouterInterface.ts +5 -2
  318. package/src/core/contracts/SectionInterface.ts +49 -0
  319. package/src/core/contracts/ServiceProviderInterface.ts +3 -3
  320. package/src/core/contracts/ViewControllerInterface.ts +124 -16
  321. package/src/core/contracts/ViewInterface.ts +34 -5
  322. package/src/core/contracts/ViewManagerInterface.ts +40 -5
  323. package/src/core/contracts/ViewStateInterface.ts +39 -2
  324. package/src/core/contracts/common.ts +3 -3
  325. package/src/core/contracts/utils.ts +19 -19
  326. package/src/core/contracts/views.ts +2 -22
  327. package/src/core/devtools/hook.ts +142 -0
  328. package/src/core/devtools/index.ts +38 -0
  329. package/src/core/devtools/inspector.ts +270 -0
  330. package/src/core/elements/Block.ts +43 -21
  331. package/src/core/elements/BlockOutlet.ts +61 -21
  332. package/src/core/elements/Component.ts +352 -18
  333. package/src/core/elements/ElementManager.ts +9 -9
  334. package/src/core/elements/ForeachSlotCache.ts +170 -0
  335. package/src/core/elements/Fragment.ts +50 -32
  336. package/src/core/elements/Html.ts +696 -117
  337. package/src/core/elements/Output.ts +147 -30
  338. package/src/core/elements/Reactive.ts +329 -52
  339. package/src/core/elements/TextElement.ts +25 -16
  340. package/src/core/elements/Wrapper.ts +84 -18
  341. package/src/core/elements/Yield.ts +35 -24
  342. package/src/core/elements/helpers.ts +12 -12
  343. package/src/core/elements/index.ts +25 -24
  344. package/src/core/elements/reconcileChildren.ts +70 -0
  345. package/src/core/{hellpers → helpers}/ApiClient.ts +8 -1
  346. package/src/core/{hellpers → helpers}/app.ts +13 -2
  347. package/src/core/helpers/transition.ts +127 -0
  348. package/src/core/{hellpers → helpers}/utils.ts +4 -4
  349. package/src/core/helpers/view.ts +283 -0
  350. package/src/core/routers/Router.ts +458 -64
  351. package/src/core/routers/index.ts +2 -2
  352. package/src/core/services/AssetManager.ts +387 -0
  353. package/src/core/services/BlockManager.ts +272 -50
  354. package/src/core/services/DomService.ts +3 -3
  355. package/src/core/services/HeadService.ts +124 -0
  356. package/src/core/services/HelperService.ts +34 -4
  357. package/src/core/services/HttpService.ts +116 -55
  358. package/src/core/services/MarkerRegistry.ts +142 -16
  359. package/src/core/services/PageCache.ts +239 -0
  360. package/src/core/services/SectionManager.ts +387 -0
  361. package/src/core/services/StorageService.ts +1 -1
  362. package/src/core/services/TranslationService.ts +91 -0
  363. package/src/core/services/index.ts +11 -8
  364. package/src/core/types/utils.ts +8 -8
  365. package/src/core/view/LoopContext.ts +38 -1
  366. package/src/core/view/ResourceScope.ts +174 -0
  367. package/src/core/view/Section.ts +21 -68
  368. package/src/core/view/View.ts +57 -8
  369. package/src/core/view/ViewController.ts +1070 -186
  370. package/src/core/view/ViewManager.ts +1346 -272
  371. package/src/core/view/ViewState.ts +834 -116
  372. package/src/core/view/index.ts +5 -5
  373. package/src/testing/index.ts +184 -0
  374. package/dist/src/core/services/MarkerService.d.ts +0 -299
  375. package/dist/src/core/services/MarkerService.d.ts.map +0 -1
  376. package/dist/src/core/services/MarkerService.js +0 -723
  377. package/dist/src/core/services/MarkerService.js.map +0 -1
  378. package/src/core/services/MarkerService.ts +0 -923
@@ -1,12 +1,12 @@
1
- import type { BlockInterface, BlockRenderFactory } from "../contracts/BlockInterface";
2
- import { InitMode } from "../contracts/common";
3
- import type { FragmentInterface, HtmlInterface } from "../contracts/ElementInterface";
4
- import type { ReactiveInterface } from "../contracts/ReactiveInterface";
5
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
6
- import { generateUUID } from "../hellpers/utils";
7
- import markerRegistry from "../services/MarkerRegistry";
8
- import type { OneObjectType } from "../types/utils";
9
- import { Fragment } from "./Fragment";
1
+ import type { BlockInterface, BlockRenderFactory } from "../contracts/BlockInterface.js";
2
+ import { InitMode, InitModes } from "../contracts/common.js";
3
+ import type { FragmentInterface, HtmlInterface } from "../contracts/ElementInterface.js";
4
+ import type { ReactiveInterface } from "../contracts/ReactiveInterface.js";
5
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
6
+ import { generateUUID } from "../helpers/utils.js";
7
+ import markerRegistry from "../services/MarkerRegistry.js";
8
+ import type { SaoObjectType } from "../types/utils.js";
9
+ import { Fragment } from "./Fragment.js";
10
10
  /**
11
11
  * Block — a named mounting slot used in layout views.
12
12
  *
@@ -26,23 +26,24 @@ import { Fragment } from "./Fragment";
26
26
  * 4. Browser back → BlockManager restores cached content without re-render
27
27
  */
28
28
  export class Block implements BlockInterface {
29
- oneType: OneObjectType = 'Block';
29
+ saoType: SaoObjectType = 'Block';
30
30
  id: string;
31
31
  name: string;
32
32
  ctx: ViewControllerInterface;
33
33
  viewId: string | null = null;
34
+ /** Key trả về bởi markerRegistry.register — destroy() dùng để gỡ lại */
35
+ private markerKey: string | null = null;
34
36
  fragment: FragmentInterface | null = null;
35
37
  contentRenderFactory: BlockRenderFactory | null = null;
36
38
  openTag: Comment;
37
39
  closeTag: Comment;
38
-
39
40
  domChildren: Node[] = [];
40
41
  initMode?: InitMode | undefined;
41
42
  parentElement: HtmlInterface | null = null;
42
43
 
43
44
  public readonly isOneBlock = true;
44
- public readonly isOneElement = false;
45
-
45
+ public readonly isSaoElement = false;
46
+
46
47
  constructor({
47
48
  ctx,
48
49
  name,
@@ -58,15 +59,27 @@ export class Block implements BlockInterface {
58
59
  id?: string | null;
59
60
  initMode?: InitMode;
60
61
  }) {
61
- this.id = `${this.viewId}-${id ?? generateUUID(10)}`; // Unique ID for debugging
62
+ this.id = id ?? generateUUID(10); // Unique ID for debugging
62
63
  this.ctx = ctx;
63
64
  this.name = name;
64
65
  this.viewId = viewId ?? ctx.viewId; // Associate block with current viewId
65
66
  this.initMode = initMode;
66
67
  this.contentRenderFactory = contentRenderFactory || ((parentElement: HtmlInterface) => []);
67
- markerRegistry.register('block', this.id, { name, viewId }); // Register block in marker registry
68
- this.openTag = markerRegistry.createMarkerStart('block', this.id);
69
- this.closeTag = markerRegistry.createMarkerEnd('block', this.id);
68
+ // Hydrate: claim cặp marker server qua index O(1) của MarkerRegistry.
69
+ // (Bản cũ dùng chung một TreeWalker không reset nên chỉ block ĐẦU TIÊN
70
+ // claim được, các block sau lặng lẽ tạo marker mới.)
71
+ const claimed = (this.initMode === InitModes.HYDRATE)
72
+ ? markerRegistry.claim('block', this.id)
73
+ : null;
74
+
75
+ if (claimed) {
76
+ this.openTag = claimed.open;
77
+ this.closeTag = claimed.close;
78
+ } else {
79
+ this.openTag = markerRegistry.createMarkerStart('block', this.id);
80
+ this.closeTag = markerRegistry.createMarkerEnd('block', this.id);
81
+ this.markerKey = markerRegistry.register('block', this.id, { name, viewId }); // Register block in marker registry
82
+ }
70
83
 
71
84
  }
72
85
 
@@ -90,7 +103,7 @@ export class Block implements BlockInterface {
90
103
  }
91
104
 
92
105
  mount(mountCtx: ViewControllerInterface, parentElement: HtmlInterface): any {
93
- if(this.fragment) {
106
+ if (this.fragment) {
94
107
  this.fragment.parent = parentElement;
95
108
  this.fragment.render();
96
109
  }
@@ -99,14 +112,23 @@ export class Block implements BlockInterface {
99
112
  // Unmount logic (e.g. hide or remove DOM nodes, stop reactions)
100
113
  }
101
114
  destroy(): void {
102
- // Cleanup logic (e.g. remove DOM nodes, clear caches)
115
+ // MarkerRegistry là singleton toàn cục — không gỡ thì record sống qua navigate.
116
+ if (this.markerKey) {
117
+ markerRegistry.remove(this.markerKey);
118
+ this.markerKey = null;
119
+ }
120
+ // openTag/closeTag nay NẰM TRONG DOM (BlockManager.mountBlockIntoOutlet chèn
121
+ // chúng quanh content). Bỏ lại comment mồ côi thì lần hydrate sau
122
+ // markerRegistry.claim('block', id) có thể bắt trúng cặp cũ và claim nhầm vùng.
123
+ this.openTag.remove();
124
+ this.closeTag.remove();
103
125
  }
104
126
  update(): void {
105
127
  // Update logic (e.g. re-render content on state change)
106
128
  }
107
129
 
108
- /** Set the block's parent element (used for mounting) */
109
- setParentElement(parentElement: HtmlInterface | null): void {
130
+ /** Set the block's parent element (used for mounting) */
131
+ setParentElement(parentElement: HtmlInterface | null): void {
110
132
  this.parentElement = parentElement;
111
133
  }
112
134
 
@@ -1,13 +1,13 @@
1
- import type { BlockOutletInterface } from "../contracts/BlockInterface";
2
- import { InitMode } from "../contracts/common";
3
- import type { HtmlInterface } from "../contracts/ElementInterface";
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 type { BlockOutletInterface } from "../contracts/BlockInterface.js";
2
+ import { InitMode, InitModes } from "../contracts/common.js";
3
+ import type { HtmlInterface } from "../contracts/ElementInterface.js";
4
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
5
+ import { generateUUID } from "../helpers/utils.js";
6
+ import markerRegistry from "../services/MarkerRegistry.js";
7
+ import type { SaoObjectType } from "../types/utils.js";
8
8
 
9
9
  export class BlockOutlet implements BlockOutletInterface {
10
- oneType: OneObjectType = 'BlockOutlet';
10
+ saoType: SaoObjectType = 'BlockOutlet';
11
11
  id: string;
12
12
  name: string;
13
13
  openTag: Comment;
@@ -15,38 +15,78 @@ export class BlockOutlet implements BlockOutletInterface {
15
15
  parent: HtmlInterface | null = null;
16
16
  parentElement: HtmlInterface | null = null;
17
17
  ctx: ViewControllerInterface;
18
- initMode: InitMode = 'create';
19
- constructor({ ctx, parentElement = null, name, id = null, initMode = 'create' }: { ctx: ViewControllerInterface, parentElement?: HtmlInterface | null, name: string, id?: string | null, initMode?: InitMode }) {
20
- this.id = `${ctx.viewId}-${id ?? generateUUID(10)}`; // Unique ID for debugging
18
+ initMode: InitMode = InitModes.CREATE;
19
+ constructor({ ctx, parentElement = null, name, id = null, initMode = InitModes.CREATE }: { ctx: ViewControllerInterface, parentElement?: HtmlInterface | null, name: string, id?: string | null, initMode?: InitMode }) {
20
+ this.id = id ?? generateUUID(10); // Unique ID for debugging
21
21
  this.ctx = ctx;
22
22
  this.name = name;
23
23
  this.parent = parentElement;
24
24
  this.parentElement = parentElement;
25
25
  this.initMode = initMode;
26
26
 
27
- markerRegistry.register('blockoutlet', this.id, { name, viewId: ctx.viewId }); // Register this outlet in the MarkerRegistry
27
+ if (this.initMode === InitModes.HYDRATE) {
28
+ // Claim cặp marker server <!--s:bo:{id}-s--> ... <!--s:bo:{id}-e-->
29
+ const claimed = this.claimSSRMarkers();
30
+ if (claimed) {
31
+ this.openTag = claimed.open;
32
+ this.closeTag = claimed.close;
33
+ } else {
34
+ this.openTag = markerRegistry.createMarkerStart('blockoutlet', this.id);
35
+ this.closeTag = markerRegistry.createMarkerEnd('blockoutlet', this.id);
36
+ this.markerKey = markerRegistry.register('blockoutlet', this.id, { name, viewId: ctx.viewId });
37
+ }
38
+ }
39
+ else {
40
+ this.openTag = markerRegistry.createMarkerStart('blockoutlet', this.id);
41
+ this.closeTag = markerRegistry.createMarkerEnd('blockoutlet', this.id);
42
+ this.markerKey = markerRegistry.register('blockoutlet', this.id, { name, viewId: ctx.viewId }); // Register this outlet in the MarkerRegistry
43
+ }
28
44
 
29
- this.openTag = markerRegistry.createMarkerStart('blockoutlet', this.id);
30
- this.closeTag = markerRegistry.createMarkerEnd('blockoutlet', this.id);
45
+ }
46
+
47
+ /**
48
+ * Tìm cặp marker outlet từ server-rendered HTML (format chuẩn §5.1):
49
+ * open: s:bo:{id}-s close: s:bo:{id}-e
50
+ * Tra index của MarkerRegistry, chặn trong parentElement nếu có.
51
+ */
52
+ private claimSSRMarkers(): { open: Comment; close: Comment } | null {
53
+ return markerRegistry.claim('blockoutlet', this.id, this.parentElement?.element ?? null);
31
54
  }
32
55
 
33
56
  hydrate(): void {
34
57
  // Hydration logic if needed (e.g. reattach event listeners)
35
58
  }
36
59
 
60
+ /** Registry guard */
61
+ public __destroyed__: boolean = false;
62
+ /** Key trả về bởi markerRegistry.register — destroy() dùng để gỡ lại */
63
+ private markerKey: string | null = null;
64
+
65
+ /** Render — idempotent: markers đã trong DOM thì giữ nguyên (same-layout reuse) */
37
66
  render(): void {
38
- if (!this.parentElement || !this.parentElement.element) return;
67
+ if (this.__destroyed__) return;
68
+ if (this.openTag.parentNode) return; // đã đặt — không đặt lại
39
69
 
70
+ if (!this.parentElement || !this.parentElement.element) return;
40
71
  const parentEl = this.parentElement.element;
41
-
42
- // Place markers
43
72
  parentEl.appendChild(this.openTag);
44
- // Register this outlet in the MarkerRegistry for block mounting
45
73
  parentEl.appendChild(this.closeTag);
46
-
47
74
  }
48
75
 
49
76
  destroy(): void {
77
+ this.__destroyed__ = true;
78
+ this.ctx.releaseElement?.(this);
79
+ if (this.markerKey) {
80
+ markerRegistry.remove(this.markerKey);
81
+ this.markerKey = null;
82
+ }
83
+ // Clear nội dung giữa markers (block content nếu còn)
84
+ let current: Node | null = this.openTag.nextSibling;
85
+ while (current && current !== this.closeTag) {
86
+ const next: Node | null = current.nextSibling;
87
+ (current as ChildNode).remove();
88
+ current = next;
89
+ }
50
90
  // Remove markers from DOM
51
91
  this.openTag.remove();
52
92
  this.closeTag.remove();
@@ -65,11 +105,11 @@ export class BlockOutlet implements BlockOutletInterface {
65
105
  this.parentElement = parentElement;
66
106
  }
67
107
 
68
- get isOneElement(): boolean {
108
+ get isSaoElement(): boolean {
69
109
  return true;
70
110
  }
71
111
 
72
- set isOneElement(value: boolean) {
112
+ set isSaoElement(value: boolean) {
73
113
  // No-op setter to satisfy OneElement interface
74
114
  }
75
115
 
@@ -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
+ }