@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
@@ -0,0 +1,38 @@
1
+ /**
2
+ * DevTools — API công khai, gắn vào DI container dưới tên `Devtools`
3
+ * (dùng `app('Devtools')` hoặc `App.devtools`).
4
+ *
5
+ * Tắt hoàn toàn mặc định: `emit()` thoát ngay ở dòng đầu khi chưa bật, nên
6
+ * production không trả phí gì ngoài vài hàm không bao giờ chạy.
7
+ *
8
+ * @example
9
+ * App.devtools.open(); // bật + mở panel
10
+ * App.devtools.enableShortcut(); // Ctrl+Shift+D bật/tắt panel
11
+ * App.devtools.getViewTree(); // đọc cây view bằng code
12
+ */
13
+ import devtools from './hook.js';
14
+ import inspector, { bindDevtoolsShortcut } from './inspector.js';
15
+
16
+ export const Devtools = {
17
+ /** Bật thu thập sự kiện (chưa mở UI) — dùng khi chỉ cần đọc bằng code. */
18
+ enable: () => devtools.enable(),
19
+ disable: () => { inspector.close(); devtools.disable(); },
20
+ isEnabled: () => devtools.isEnabled(),
21
+
22
+ /** Bật + mở panel in-page. */
23
+ open: () => inspector.open(),
24
+ close: () => inspector.close(),
25
+ toggle: () => inspector.toggle(),
26
+ isOpen: () => inspector.isOpen(),
27
+
28
+ enableShortcut: () => bindDevtoolsShortcut(),
29
+
30
+ getViewTree: () => devtools.getViewTree(),
31
+ getLog: () => devtools.getLog(),
32
+ clearLog: () => devtools.clearLog(),
33
+ subscribe: (fn: Parameters<typeof devtools.subscribe>[0]) => devtools.subscribe(fn),
34
+ };
35
+
36
+ export { devtools, inspector };
37
+ export type { DevtoolsEvent, DevtoolsEventType, DevtoolsViewNode } from './hook.js';
38
+ export default Devtools;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * DevTools inspector — panel in-page, không cần cài extension.
3
+ *
4
+ * Vì sao overlay chứ không phải browser extension: extension cần scaffolding
5
+ * riêng (manifest, content script, bridge, publish store) mà vẫn hiển thị đúng
6
+ * dữ liệu này. Overlay chạy ngay ở mọi môi trường (kể cả máy khác, mobile
7
+ * webview) với chi phí nhỏ hơn nhiều. Hook (`devtools/hook.ts`) đã tách riêng
8
+ * nên extension vẫn dựng được sau này trên CÙNG nguồn dữ liệu.
9
+ *
10
+ * Bật: App.devtools.open() — hoặc Ctrl+Shift+D sau khi App.devtools.enable()
11
+ *
12
+ * Toàn bộ DOM của panel dựng bằng createElement + textContent — panel hiển thị
13
+ * state/đường dẫn/thông báo lỗi do người dùng kiểm soát, nội suy chuỗi vào
14
+ * innerHTML ở đây là đường tiêm HTML (đúng lỗi đã vá ở GAP-07).
15
+ */
16
+ import devtools, { DevtoolsEvent, DevtoolsViewNode } from './hook.js';
17
+
18
+ const PANEL_ID = '__saola_devtools_panel__';
19
+ const HIGHLIGHT_ID = '__saola_devtools_highlight__';
20
+
21
+ class Inspector {
22
+ private panel: HTMLElement | null = null;
23
+ private unsubscribe: (() => void) | null = null;
24
+ private refreshQueued = false;
25
+ private selectedViewId: string | null = null;
26
+
27
+ isOpen(): boolean { return this.panel !== null; }
28
+
29
+ open(): void {
30
+ if (this.panel || typeof document === 'undefined') return;
31
+ devtools.enable();
32
+
33
+ this.panel = this.buildPanel();
34
+ document.body.appendChild(this.panel);
35
+
36
+ // Gom nhiều sự kiện trong cùng frame thành 1 lần vẽ — state đổi có thể
37
+ // bắn hàng loạt, vẽ lại từng cái sẽ tự làm chậm chính app đang debug.
38
+ this.unsubscribe = devtools.subscribe(() => this.queueRefresh());
39
+ this.refresh();
40
+ }
41
+
42
+ close(): void {
43
+ this.unsubscribe?.();
44
+ this.unsubscribe = null;
45
+ this.panel?.remove();
46
+ this.panel = null;
47
+ this.clearHighlight();
48
+ }
49
+
50
+ toggle(): void { this.isOpen() ? this.close() : this.open(); }
51
+
52
+ private queueRefresh(): void {
53
+ if (this.refreshQueued) return;
54
+ this.refreshQueued = true;
55
+ requestAnimationFrame(() => { this.refreshQueued = false; this.refresh(); });
56
+ }
57
+
58
+ // ── Dựng khung ────────────────────────────────────────────────
59
+
60
+ private buildPanel(): HTMLElement {
61
+ const panel = document.createElement('div');
62
+ panel.id = PANEL_ID;
63
+ panel.style.cssText = [
64
+ 'position:fixed', 'right:12px', 'bottom:12px', 'width:380px', 'max-height:70vh',
65
+ 'z-index:2147483647', 'display:flex', 'flex-direction:column',
66
+ 'background:#1e1e2e', 'color:#cdd6f4', 'border:1px solid #45475a', 'border-radius:8px',
67
+ 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace',
68
+ 'box-shadow:0 8px 32px rgba(0,0,0,.4)', 'overflow:hidden',
69
+ ].join(';');
70
+
71
+ const header = document.createElement('div');
72
+ header.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px 10px;background:#181825;border-bottom:1px solid #45475a';
73
+ const title = document.createElement('strong');
74
+ title.textContent = 'Saola DevTools';
75
+ title.style.cssText = 'flex:1;color:#89b4fa';
76
+ const btnClear = this.button('Xoá log', () => { devtools.clearLog(); this.refresh(); });
77
+ const btnClose = this.button('✕', () => this.close());
78
+ header.append(title, btnClear, btnClose);
79
+
80
+ const body = document.createElement('div');
81
+ body.setAttribute('data-role', 'body');
82
+ body.style.cssText = 'overflow:auto;padding:8px 10px';
83
+
84
+ panel.append(header, body);
85
+ return panel;
86
+ }
87
+
88
+ private button(label: string, onClick: () => void): HTMLButtonElement {
89
+ const b = document.createElement('button');
90
+ b.textContent = label;
91
+ b.style.cssText = 'background:#313244;color:#cdd6f4;border:1px solid #45475a;border-radius:4px;padding:2px 8px;cursor:pointer;font:inherit';
92
+ b.addEventListener('click', onClick);
93
+ return b;
94
+ }
95
+
96
+ private section(label: string): HTMLElement {
97
+ const h = document.createElement('div');
98
+ h.textContent = label;
99
+ h.style.cssText = 'margin:10px 0 4px;color:#a6adc8;text-transform:uppercase;font-size:10px;letter-spacing:.08em';
100
+ return h;
101
+ }
102
+
103
+ // ── Vẽ nội dung ───────────────────────────────────────────────
104
+
105
+ private refresh(): void {
106
+ const body = this.panel?.querySelector('[data-role="body"]') as HTMLElement | null;
107
+ if (!body) return;
108
+ body.replaceChildren();
109
+
110
+ const tree = devtools.getViewTree();
111
+ body.append(this.section(`Cây view (${tree.length} gốc)`));
112
+ if (tree.length === 0) {
113
+ body.append(this.muted('Chưa có view nào mount.'));
114
+ } else {
115
+ for (const node of tree) body.append(this.renderNode(node, 0));
116
+ }
117
+
118
+ const log = devtools.getLog().slice(-25).reverse();
119
+ body.append(this.section(`Sự kiện gần đây (${log.length})`));
120
+ if (log.length === 0) body.append(this.muted('Chưa có sự kiện.'));
121
+ else for (const e of log) body.append(this.renderEvent(e));
122
+ }
123
+
124
+ private muted(text: string): HTMLElement {
125
+ const d = document.createElement('div');
126
+ d.textContent = text;
127
+ d.style.cssText = 'color:#6c7086;padding:2px 0';
128
+ return d;
129
+ }
130
+
131
+ private renderNode(node: DevtoolsViewNode, depth: number): HTMLElement {
132
+ const wrap = document.createElement('div');
133
+ wrap.style.cssText = `margin-left:${depth * 12}px`;
134
+
135
+ const row = document.createElement('div');
136
+ row.style.cssText = 'display:flex;gap:6px;align-items:baseline;padding:2px 4px;border-radius:4px;cursor:pointer';
137
+ row.addEventListener('mouseenter', () => this.highlight(node.viewId));
138
+ row.addEventListener('mouseleave', () => this.clearHighlight());
139
+ row.addEventListener('click', () => {
140
+ this.selectedViewId = this.selectedViewId === node.viewId ? null : node.viewId;
141
+ this.refresh();
142
+ });
143
+
144
+ const path = document.createElement('span');
145
+ path.textContent = node.path || '(không tên)';
146
+ path.style.cssText = 'color:#a6e3a1;flex:1';
147
+
148
+ const meta = document.createElement('span');
149
+ meta.textContent = `${node.viewType} · ${node.lifecycleState} · ${node.elementCount}el`;
150
+ meta.style.cssText = `color:${node.lifecycleState === 'active' ? '#6c7086' : '#f9e2af'};font-size:10px`;
151
+
152
+ row.append(path, meta);
153
+ wrap.append(row);
154
+
155
+ // Bấm để mở/đóng state — mặc định đóng cho gọn khi cây lớn
156
+ if (this.selectedViewId === node.viewId) {
157
+ const keys = Object.keys(node.state);
158
+ if (keys.length === 0) {
159
+ wrap.append(this.muted(' (không có state)'));
160
+ } else {
161
+ for (const key of keys) {
162
+ const line = document.createElement('div');
163
+ line.style.cssText = 'display:flex;gap:6px;padding-left:10px';
164
+ const k = document.createElement('span');
165
+ k.textContent = key + ':';
166
+ k.style.cssText = 'color:#f5c2e7';
167
+ const v = document.createElement('span');
168
+ // textContent — state có thể chứa chuỗi do người dùng nhập
169
+ v.textContent = JSON.stringify(node.state[key]) ?? 'undefined';
170
+ v.style.cssText = 'color:#cdd6f4;word-break:break-all';
171
+ line.append(k, v);
172
+ wrap.append(line);
173
+ }
174
+ }
175
+ }
176
+
177
+ for (const child of node.children) wrap.append(this.renderNode(child, depth + 1));
178
+ return wrap;
179
+ }
180
+
181
+ private renderEvent(e: DevtoolsEvent): HTMLElement {
182
+ const colors: Record<string, string> = {
183
+ 'view:mounted': '#a6e3a1', 'view:destroyed': '#f38ba8',
184
+ 'state:changed': '#89b4fa', 'error': '#fab387',
185
+ };
186
+ const row = document.createElement('div');
187
+ row.style.cssText = 'display:flex;gap:6px;padding:1px 0';
188
+
189
+ const t = document.createElement('span');
190
+ t.textContent = String(e.at).padStart(6, ' ');
191
+ t.style.cssText = 'color:#6c7086;flex-shrink:0';
192
+
193
+ const type = document.createElement('span');
194
+ type.textContent = e.type;
195
+ type.style.cssText = `color:${colors[e.type] ?? '#cdd6f4'};flex-shrink:0`;
196
+
197
+ const detail = document.createElement('span');
198
+ detail.textContent = this.describe(e);
199
+ detail.style.cssText = 'color:#a6adc8;word-break:break-all';
200
+
201
+ row.append(t, type, detail);
202
+ return row;
203
+ }
204
+
205
+ private describe(e: DevtoolsEvent): string {
206
+ if (e.type === 'state:changed') return `${e.path ?? ''} [${(e.detail?.keys ?? []).join(', ')}]`;
207
+ if (e.type === 'error') return `${e.detail?.phase ?? ''} ${e.path ?? ''}: ${e.detail?.message ?? ''}`;
208
+ return e.path ?? '';
209
+ }
210
+
211
+ // ── Highlight vùng DOM của view theo marker ───────────────────
212
+
213
+ /**
214
+ * View không có 1 element bọc ngoài (kiến trúc marker, no-VDOM) → lấy
215
+ * bounding box của TẤT CẢ node giữa cặp marker `<!--s:v:{viewId}-s/-e-->`.
216
+ */
217
+ private highlight(viewId: string): void {
218
+ this.clearHighlight();
219
+ const range = this.rangeOfView(viewId);
220
+ if (!range) return;
221
+ const rect = range.getBoundingClientRect();
222
+ if (rect.width === 0 && rect.height === 0) return;
223
+
224
+ const box = document.createElement('div');
225
+ box.id = HIGHLIGHT_ID;
226
+ box.style.cssText = [
227
+ 'position:fixed', `left:${rect.left}px`, `top:${rect.top}px`,
228
+ `width:${rect.width}px`, `height:${rect.height}px`,
229
+ 'background:rgba(137,180,250,.25)', 'border:1px solid #89b4fa',
230
+ 'pointer-events:none', 'z-index:2147483646',
231
+ ].join(';');
232
+ document.body.appendChild(box);
233
+ }
234
+
235
+ private rangeOfView(viewId: string): Range | null {
236
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
237
+ let open: Comment | null = null;
238
+ let node: Comment | null;
239
+ while ((node = walker.nextNode() as Comment | null)) {
240
+ const v = node.nodeValue?.trim() ?? '';
241
+ if (v === `s:v:${viewId}-s`) { open = node; continue; }
242
+ if (open && v === `s:v:${viewId}-e`) {
243
+ const range = document.createRange();
244
+ range.setStartAfter(open);
245
+ range.setEndBefore(node);
246
+ return range;
247
+ }
248
+ }
249
+ return null;
250
+ }
251
+
252
+ private clearHighlight(): void {
253
+ document.getElementById(HIGHLIGHT_ID)?.remove();
254
+ }
255
+ }
256
+
257
+ export const inspector = new Inspector();
258
+
259
+ /** Ctrl+Shift+D bật/tắt panel — chỉ gắn khi devtools đã enable. */
260
+ export function bindDevtoolsShortcut(): void {
261
+ if (typeof window === 'undefined') return;
262
+ window.addEventListener('keydown', (e) => {
263
+ if (e.ctrlKey && e.shiftKey && (e.key === 'D' || e.key === 'd')) {
264
+ e.preventDefault();
265
+ inspector.toggle();
266
+ }
267
+ });
268
+ }
269
+
270
+ export default inspector;
@@ -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