@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
@@ -0,0 +1,239 @@
1
+ /**
2
+ * PageCache — bfcache-style cache cho trang đã ghé (ROUTE_RENDER_FLOW.md §8).
3
+ *
4
+ * Key = `${viewName}::${requestUri}` — requestUri gồm path + query, KHÔNG gồm
5
+ * hash (ViewManager.cacheKey). Value = view chain (instance còn sống, state
6
+ * nguyên vẹn) + DOM đã detach + scroll position.
7
+ *
8
+ * Chính sách:
9
+ * - LRU max `maxEntries` (mặc định 10)
10
+ * - TTL mặc định 10 phút — entry quá hạn bị destroy thật + xoá khỏi cache
11
+ * - Hết hạn kiểm tra 3 tầng: lazy khi take(), sweep() khi navigate,
12
+ * sweep() khi tab visible lại (đăng ký ở bootstrap)
13
+ * - Trang đang active KHÔNG nằm trong cache (take() chuyển ownership ra ngoài)
14
+ * - Page thuộc layout: DOM được detach THEO TỪNG OUTLET (outletContents);
15
+ * chỉ restore được khi layout đang mount trùng `layoutPath`.
16
+ */
17
+ import type { ViewInterface } from "../contracts/ViewInterface.js";
18
+
19
+ /** Nội dung một outlet đã detach (page thuộc layout) */
20
+ export interface OutletContent {
21
+ fragment: DocumentFragment;
22
+ /** Saola elements (block content) — để re-track lifecycle khi restore */
23
+ children: any[];
24
+ }
25
+
26
+ export interface PageCacheEntry {
27
+ urlPath: string;
28
+ /** View chain (page + layouts không reuse), instance sống — state giữ nguyên */
29
+ views: ViewInterface[];
30
+ /** DOM đã detach (toàn bộ vùng wrapper, gồm cả comment markers) — standalone page */
31
+ fragment: DocumentFragment;
32
+ /** Page thuộc layout: path của layout lúc cache — restore đòi hỏi layout trùng */
33
+ layoutPath: string | null;
34
+ /** Page thuộc layout: DOM + children đã detach theo tên outlet */
35
+ outletContents: Map<string, OutletContent> | null;
36
+ scroll: { x: number; y: number };
37
+ savedAt: number;
38
+ ttl: number;
39
+ }
40
+
41
+ export interface PageCacheSetOptions {
42
+ views: ViewInterface[];
43
+ fragment?: DocumentFragment;
44
+ layoutPath?: string | null;
45
+ outletContents?: Map<string, OutletContent> | null;
46
+ scroll?: { x: number; y: number };
47
+ /** Override TTL cho entry này (ms). 0 = không cache. */
48
+ ttl?: number;
49
+ }
50
+
51
+ export class PageCacheService {
52
+ /** Map giữ insertion order → dùng làm LRU */
53
+ private entries = new Map<string, PageCacheEntry>();
54
+
55
+ public maxEntries = 10;
56
+ /** 10 phút — trong khoảng thiết kế 5–15 phút; override per-view qua cache:{ttl} */
57
+ public defaultTTL = 10 * 60 * 1000;
58
+
59
+ /** Hook khi entry bị destroy (evict/expire/invalidate) — ViewManager dọn store tại đây */
60
+ public onEvict: ((entry: PageCacheEntry) => void) | null = null;
61
+
62
+ /** Injectable cho test */
63
+ public now: () => number = () => Date.now();
64
+
65
+ set(urlPath: string, options: PageCacheSetOptions): void {
66
+ const ttl = options.ttl ?? this.defaultTTL;
67
+ if (ttl <= 0) {
68
+ // Không cache — destroy luôn
69
+ this.destroyViews(options.views);
70
+ return;
71
+ }
72
+
73
+ // Đã có entry cùng path → destroy bản cũ trước
74
+ const existing = this.entries.get(urlPath);
75
+ if (existing) {
76
+ this.entries.delete(urlPath);
77
+ this.destroyEntry(existing);
78
+ }
79
+
80
+ this.entries.set(urlPath, {
81
+ urlPath,
82
+ views: options.views,
83
+ fragment: options.fragment ?? document.createDocumentFragment(),
84
+ layoutPath: options.layoutPath ?? null,
85
+ outletContents: options.outletContents ?? null,
86
+ scroll: options.scroll ?? { x: 0, y: 0 },
87
+ savedAt: this.now(),
88
+ ttl,
89
+ });
90
+
91
+ // LRU evict
92
+ while (this.entries.size > this.maxEntries) {
93
+ const oldestKey = this.entries.keys().next().value as string;
94
+ const oldest = this.entries.get(oldestKey)!;
95
+ this.entries.delete(oldestKey);
96
+ this.destroyEntry(oldest);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Trả một entry ĐÃ take() về lại cache — dùng khi restore thất bại nhưng
102
+ * entry còn giá trị (vd layout của page chưa mount lại; lần điều hướng sau
103
+ * có thể restore được). GIỮ savedAt gốc — TTL KHÔNG được reset.
104
+ */
105
+ putBack(entry: PageCacheEntry): void {
106
+ if (this.isExpired(entry)) {
107
+ this.destroyEntry(entry);
108
+ return;
109
+ }
110
+ const existing = this.entries.get(entry.urlPath);
111
+ if (existing && existing !== entry) {
112
+ this.entries.delete(entry.urlPath);
113
+ this.destroyEntry(existing);
114
+ }
115
+ this.entries.set(entry.urlPath, entry);
116
+
117
+ while (this.entries.size > this.maxEntries) {
118
+ const oldestKey = this.entries.keys().next().value as string;
119
+ const oldest = this.entries.get(oldestKey)!;
120
+ this.entries.delete(oldestKey);
121
+ this.destroyEntry(oldest);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Lấy entry RA KHỎI cache (chuyển ownership cho caller để restore).
127
+ * Entry quá hạn → destroy, trả null.
128
+ */
129
+ take(urlPath: string): PageCacheEntry | null {
130
+ const entry = this.entries.get(urlPath);
131
+ if (!entry) return null;
132
+
133
+ this.entries.delete(urlPath);
134
+
135
+ if (this.isExpired(entry)) {
136
+ this.destroyEntry(entry);
137
+ return null;
138
+ }
139
+ return entry;
140
+ }
141
+
142
+ has(urlPath: string): boolean {
143
+ const entry = this.entries.get(urlPath);
144
+ return !!entry && !this.isExpired(entry);
145
+ }
146
+
147
+ /** Xoá + destroy entry (vd: push navigation cần data tươi, hoặc sau mutation) */
148
+ invalidate(urlPath: string): boolean {
149
+ const entry = this.entries.get(urlPath);
150
+ if (!entry) return false;
151
+ this.entries.delete(urlPath);
152
+ this.destroyEntry(entry);
153
+ return true;
154
+ }
155
+
156
+ /** Quét destroy mọi entry quá hạn. Gọi sau mỗi navigation + khi tab visible lại. */
157
+ sweep(): number {
158
+ let removed = 0;
159
+ for (const [key, entry] of this.entries) {
160
+ if (this.isExpired(entry)) {
161
+ this.entries.delete(key);
162
+ this.destroyEntry(entry);
163
+ removed++;
164
+ }
165
+ }
166
+ return removed;
167
+ }
168
+
169
+ clear(): void {
170
+ for (const entry of this.entries.values()) {
171
+ this.destroyEntry(entry);
172
+ }
173
+ this.entries.clear();
174
+ }
175
+
176
+ get size(): number {
177
+ return this.entries.size;
178
+ }
179
+
180
+ // ─── Private ────────────────────────────────────────────────
181
+
182
+ private isExpired(entry: PageCacheEntry): boolean {
183
+ return this.now() - entry.savedAt > entry.ttl;
184
+ }
185
+
186
+ private destroyEntry(entry: PageCacheEntry): void {
187
+ this.destroyTakenEntry(entry);
188
+ try { this.onEvict?.(entry); } catch (e) { console.error('[PageCache] onEvict error:', e); }
189
+ }
190
+
191
+ /**
192
+ * Destroy một entry ĐÃ take() ra khỏi cache nhưng không restore được
193
+ * (vd layout hiện tại không khớp layoutPath). Public cho ViewManager.
194
+ */
195
+ destroyTakenEntry(entry: PageCacheEntry): void {
196
+ // Block content đã detach — ViewController.destroy() không đi qua chúng
197
+ // (không nằm trong _rootTree) nên phải destroy tường minh trước.
198
+ if (entry.outletContents) {
199
+ for (const [, content] of entry.outletContents) {
200
+ for (const child of content.children) {
201
+ try { child?.destroy?.(); }
202
+ catch (e) { console.error('[PageCache] destroy block child error:', e); }
203
+ }
204
+ }
205
+ }
206
+ this.destroyViews(entry.views);
207
+ }
208
+
209
+ private destroyViews(views: ViewInterface[]): void {
210
+ for (const view of views) {
211
+ try { view.__ctrl__?.destroy(); }
212
+ catch (e) { console.error('[PageCache] destroy view error:', e); }
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Detach toàn bộ vùng DOM của một wrapper (từ openTag đến closeTag, inclusive)
219
+ * vào DocumentFragment — node KHÔNG bị destroy, chỉ rời khỏi document.
220
+ */
221
+ export function detachWrapperDOM(wrapper: { openTag: Comment; closeTag: Comment }): DocumentFragment {
222
+ const fragment = document.createDocumentFragment();
223
+ const start = wrapper.openTag;
224
+ const end = wrapper.closeTag;
225
+ if (!start.parentNode) return fragment;
226
+
227
+ let current: Node | null = start;
228
+ while (current) {
229
+ const isEnd = current === end;
230
+ const next: Node | null = current.nextSibling;
231
+ fragment.appendChild(current); // appendChild tự remove khỏi vị trí cũ
232
+ if (isEnd) break;
233
+ current = next;
234
+ }
235
+ return fragment;
236
+ }
237
+
238
+ export const PageCache = new PageCacheService();
239
+ export default PageCache;
@@ -0,0 +1,387 @@
1
+ import type { SectionInterface, SectionManagerInterface } from "../contracts/SectionInterface.js";
2
+ import type { YieldInterface } from "../contracts/ElementInterface.js";
3
+ import HeadService from "./HeadService.js";
4
+
5
+ const HEAD_SECTION_PREFIX = 'meta:';
6
+
7
+ /**
8
+ * SectionManager — connects `@section(name, ...)` declarations to `@yield(name, ...)`
9
+ * markers, cross-controller (a page declares a section, a layout — or nothing —
10
+ * yields it), mirroring how BlockManager connects Block ↔ BlockOutlet.
11
+ *
12
+ * Two content shapes (RUNTIME_CONTRACT):
13
+ * - contentType 'text' (short `@section(name, value)`) — resolves to a plain
14
+ * string. Used as: (a) a single text node between the yield's markers,
15
+ * (b) the `.value` of a `<textarea>` when the yield's immediate DOM parent
16
+ * is a textarea (mutating child text nodes of a live textarea does not
17
+ * reliably update what's displayed once the dirty-value flag is set — every
18
+ * framework sets `.value` directly instead), (c) resolved synchronously via
19
+ * `resolve()` for `this.yieldContent()` used inside an attribute/prop factory.
20
+ * - contentType 'html' (long `@section(name) ... @endsection`) — a children
21
+ * array, mounted between the yield's markers the same way BlockManager
22
+ * mounts block content into an outlet.
23
+ *
24
+ * Head metadata (`meta:title`, `meta:description`, ...): the app shell's
25
+ * `<head>` is plain Laravel Blade (native `@yield`), entirely outside the
26
+ * client's mounted tree — SSR already gets it right for free. SPA navigation
27
+ * bypasses Blade though, so `syncHeadSections()` pushes any `meta:*` section
28
+ * straight to `document.title` / `<meta>` on every mount and on every reactive
29
+ * update, keeping the tab title and metadata "live" across client-side routes.
30
+ */
31
+ export class SectionManagerService implements SectionManagerInterface {
32
+ sections: Map<string, SectionInterface> = new Map(); // key: name + viewId
33
+ activeSections: Map<string, SectionInterface> = new Map(); // key: name
34
+ yields: Map<string, YieldInterface> = new Map(); // key: yield element id
35
+ subscribers: Map<string, ((section: SectionInterface) => void)[]> = new Map();
36
+
37
+ /** name+viewId -> unsubscribe fn (only for reactive sections, active while started) */
38
+ private unsubscribers: Map<string, () => void> = new Map();
39
+ /** yield id -> tracked nodes/elements mounted between its markers (for clear/start/stop) */
40
+ private mounted: Map<string, any[]> = new Map();
41
+ /** Owners in the current route chain; cached pages retain their sections. */
42
+ private headViewIds = new Set<string>();
43
+
44
+ add(section: SectionInterface): void {
45
+ const key = section.name + (section.viewId ?? '');
46
+ if (!this.sections.has(key)) {
47
+ this.sections.set(key, section);
48
+ }
49
+ this.active(section.name, section.viewId ?? '');
50
+ }
51
+
52
+ active(name: string, viewId: string): void {
53
+ const key = name + viewId;
54
+ const section = this.sections.get(key);
55
+ if (!section) return;
56
+ this.activeSections.set(name, section);
57
+ this.notify(section);
58
+ }
59
+
60
+ subscribe(name: string, callback: (section: SectionInterface) => void): () => void {
61
+ if (!this.subscribers.has(name)) this.subscribers.set(name, []);
62
+ this.subscribers.get(name)!.push(callback);
63
+ return () => this.unsubscribe(name, callback);
64
+ }
65
+
66
+ unsubscribe(name: string, callback?: (section: SectionInterface) => void): void {
67
+ if (!this.subscribers.has(name)) return;
68
+ if (!callback) {
69
+ this.subscribers.delete(name);
70
+ return;
71
+ }
72
+ const listeners = this.subscribers.get(name)!.filter(fn => fn !== callback);
73
+ if (listeners.length === 0) this.subscribers.delete(name);
74
+ else this.subscribers.set(name, listeners);
75
+ }
76
+
77
+ addYield(id: string, yieldEl: YieldInterface): void {
78
+ this.yields.set(id, yieldEl);
79
+ }
80
+
81
+ /** XOÁ THẬT yields của một view bị destroy — destroy rồi mới rời Map. */
82
+ removeYieldsOfView(viewId: string): void {
83
+ for (const [id, y] of Array.from(this.yields)) {
84
+ if ((y as any).ctx?.viewId === viewId) {
85
+ this.clearYield(y);
86
+ (y as any).destroy?.(); // idempotent — gỡ khỏi ctrl.elements
87
+ this.yields.delete(id);
88
+ }
89
+ }
90
+ }
91
+
92
+ /** Resolve the current value of a named section — for `this.yieldContent()` in attrs/props. */
93
+ resolve(name: string, defaultValue: any = null): any {
94
+ const section = this.activeSections.get(name);
95
+ if (!section?.renderFactory) return defaultValue;
96
+ const value = section.renderFactory(null);
97
+ return value === undefined || value === null ? defaultValue : value;
98
+ }
99
+
100
+ /**
101
+ * Mount every yield owned by one controller (page or layout — matches the
102
+ * controller Router/ViewManager is currently activating).
103
+ *
104
+ * Render happens for the whole chain before any mounting does (page
105
+ * registers its sections while building its tree, THEN returns
106
+ * `extendView(layout)`, whose own render builds the yields) — so by mount
107
+ * time `activeSections` already holds every section in the chain
108
+ * regardless of call order. Only the yield side needs to be scoped to
109
+ * `viewId`, so each controller's own markers get touched exactly once,
110
+ * mirroring BlockManagerService.mountViewBlocks()'s per-owner pass.
111
+ */
112
+ mountViewSections(viewId: string): void {
113
+ this.activateHeadSections(viewId);
114
+ for (const [, yieldEl] of this.yields) {
115
+ if ((yieldEl as any).ctx?.viewId !== viewId) continue;
116
+ this.mountSectionIntoYield(this.activeSections.get(yieldEl.name) ?? null, yieldEl);
117
+ }
118
+ this.syncHeadSections();
119
+ }
120
+
121
+ /** Hydrate counterpart: trust server-rendered content, only let nested elements self-claim. */
122
+ hydrateViewSections(viewId: string): void {
123
+ this.activateHeadSections(viewId);
124
+ for (const [, yieldEl] of this.yields) {
125
+ if ((yieldEl as any).ctx?.viewId !== viewId) continue;
126
+ const section = this.activeSections.get(yieldEl.name) ?? null;
127
+ if (section) this.hydrateApplyToYield(section, yieldEl);
128
+ }
129
+ this.syncHeadSections();
130
+ }
131
+
132
+ private findYieldsByName(name: string): YieldInterface[] {
133
+ const found: YieldInterface[] = [];
134
+ for (const [, y] of this.yields) if (y.name === name) found.push(y);
135
+ return found;
136
+ }
137
+
138
+ /** Re-apply every yield bound to `name` — used after a reactive section change. */
139
+ private applyByName(name: string): void {
140
+ const section = this.activeSections.get(name) ?? null;
141
+ for (const yieldEl of this.findYieldsByName(name)) {
142
+ this.mountSectionIntoYield(section, yieldEl);
143
+ }
144
+ }
145
+
146
+ private mountSectionIntoYield(section: SectionInterface | null, yieldEl: any): void {
147
+ this.clearYield(yieldEl);
148
+ if (!yieldEl.openTag?.parentNode) return; // outlet not in DOM yet
149
+
150
+ const isTextarea = yieldEl.parent?.element?.tagName === 'TEXTAREA';
151
+
152
+ if (!section) {
153
+ this.applyText(yieldEl, isTextarea, String(yieldEl.defaultValue ?? ''));
154
+ return;
155
+ }
156
+ if (section.contentType === 'html') {
157
+ this.mountHtml(section, yieldEl);
158
+ } else {
159
+ const raw = section.renderFactory ? section.renderFactory(null) : undefined;
160
+ const text = raw === undefined || raw === null ? String(yieldEl.defaultValue ?? '') : String(raw);
161
+ this.applyText(yieldEl, isTextarea, text);
162
+ }
163
+ }
164
+
165
+ private applyText(yieldEl: any, isTextarea: boolean, text: string): void {
166
+ if (isTextarea) {
167
+ // Mutating a live textarea's child text nodes does not reliably repaint
168
+ // its displayed value once the browser's dirty-value flag is set — set
169
+ // `.value` directly instead, the same way every DOM framework does.
170
+ (yieldEl.parent!.element as HTMLTextAreaElement).value = text;
171
+ return;
172
+ }
173
+ if (!text) return;
174
+ const node = document.createTextNode(text);
175
+ yieldEl.closeTag.parentNode?.insertBefore(node, yieldEl.closeTag);
176
+ this.mounted.set(yieldEl.id, [node]);
177
+ }
178
+
179
+ /** Same insertion pattern as BlockManagerService.mountBlockIntoOutlet(). */
180
+ private mountHtml(section: SectionInterface, yieldEl: any): void {
181
+ const insertBeforeClose = (node: Node) => yieldEl.closeTag.parentNode?.insertBefore(node, yieldEl.closeTag);
182
+ const content = section.renderFactory!(yieldEl.parent);
183
+ if (!Array.isArray(content)) return;
184
+
185
+ const children: any[] = [];
186
+ for (const child of content) {
187
+ if (child === null || child === undefined) continue;
188
+ if (typeof child === 'string' || typeof child === 'number') {
189
+ insertBeforeClose(document.createTextNode(String(child)));
190
+ } else if (child instanceof Node) {
191
+ insertBeforeClose(child);
192
+ } else if (typeof child === 'object') {
193
+ if ('element' in child && (child as any).element) {
194
+ insertBeforeClose((child as any).element);
195
+ children.push(child);
196
+ child.render();
197
+ } else if ('openTag' in child) {
198
+ if ('parent' in child) (child as any).parent = yieldEl.parent;
199
+ if ('parentElement' in child) (child as any).parentElement = yieldEl.parent;
200
+ insertBeforeClose((child as any).openTag);
201
+ insertBeforeClose((child as any).closeTag);
202
+ children.push(child);
203
+ child.render();
204
+ }
205
+ }
206
+ }
207
+ this.mounted.set(yieldEl.id, children);
208
+ }
209
+
210
+ /** Hydrate: claim only — DOM already there from SSR, never insert/clear. */
211
+ private hydrateApplyToYield(section: SectionInterface, yieldEl: YieldInterface): void {
212
+ if (section.contentType !== 'html') return; // text: leave server's text node as-is
213
+ const content = section.renderFactory ? section.renderFactory((yieldEl as any).parent) : null;
214
+ if (!Array.isArray(content)) return;
215
+ const children: any[] = [];
216
+ for (const child of content) {
217
+ if (child === null || child === undefined) continue;
218
+ if (typeof child === 'string' || typeof child === 'number') continue; // text: server already has it
219
+ if (child instanceof Node) continue;
220
+ if (typeof child === 'object') {
221
+ if ('element' in child && (child as any).element) {
222
+ children.push(child);
223
+ (child as any).render();
224
+ } else if ('openTag' in child) {
225
+ if ('parent' in child) (child as any).parent = (yieldEl as any).parent;
226
+ if ('parentElement' in child) (child as any).parentElement = (yieldEl as any).parent;
227
+ children.push(child);
228
+ (child as any).render();
229
+ }
230
+ }
231
+ }
232
+ this.mounted.set((yieldEl as any).id, children);
233
+ }
234
+
235
+ private clearYield(yieldEl: any): void {
236
+ const children = this.mounted.get(yieldEl.id) ?? [];
237
+ for (const child of children) {
238
+ if (child && typeof child.destroy === 'function') child.destroy();
239
+ }
240
+ this.mounted.delete(yieldEl.id);
241
+
242
+ if (yieldEl.openTag?.parentNode) {
243
+ let current = yieldEl.openTag.nextSibling;
244
+ while (current && current !== yieldEl.closeTag) {
245
+ const next = current.nextSibling;
246
+ current.remove();
247
+ current = next;
248
+ }
249
+ }
250
+ }
251
+
252
+ /** Activate reactive sections (subscribe) + start any mounted html-content children. Call after mount/hydrate. */
253
+ startAll(): void {
254
+ for (const section of this.activeSections.values()) {
255
+ this.subscribeIfReactive(section);
256
+ }
257
+ for (const children of this.mounted.values()) {
258
+ for (const child of children) {
259
+ if (child && typeof child.start === 'function') child.start();
260
+ }
261
+ }
262
+ }
263
+
264
+ /** Deactivate — unsubscribe + stop mounted children. */
265
+ stopAll(): void {
266
+ for (const unsub of this.unsubscribers.values()) unsub();
267
+ this.unsubscribers.clear();
268
+ for (const children of this.mounted.values()) {
269
+ for (const child of children) {
270
+ if (child && typeof child.stop === 'function') child.stop();
271
+ }
272
+ }
273
+ }
274
+
275
+ private subscribeIfReactive(section: SectionInterface): void {
276
+ const key = section.name + (section.viewId ?? '');
277
+ if (this.unsubscribers.has(key)) return;
278
+ if ((section.type === 'reactive' || section.type === 'dynamic')
279
+ && section.stateKeys?.length && section.ctx) {
280
+ const unsub = section.ctx.states.__.subscribe(section.stateKeys, () => {
281
+ this.applyByName(section.name);
282
+ this.syncHeadSections();
283
+ this.notify(section);
284
+ });
285
+ this.unsubscribers.set(key, unsub);
286
+ }
287
+ }
288
+
289
+ /** Notify `subscribe(name, cb)` listeners — both when a different section becomes active
290
+ * (see `active()`) and when the currently active section's own value changes. Consumers:
291
+ * `Html.ts` attribute/prop bindings built from `@yield(...)` (no stateKeys of their own —
292
+ * the section they depend on is only known at runtime), subscribed via `yieldName`. */
293
+ private notify(section: SectionInterface): void {
294
+ const listeners = this.subscribers.get(section.name);
295
+ if (listeners) listeners.forEach(fn => fn(section));
296
+ }
297
+
298
+ unmountView(viewId: string): void {
299
+ this.headViewIds.delete(viewId);
300
+ for (const [name, section] of Array.from(this.activeSections)) {
301
+ if (section.viewId === viewId) this.activeSections.delete(name);
302
+ }
303
+ for (const [key, section] of Array.from(this.sections)) {
304
+ if (section.viewId !== viewId) continue;
305
+ const unsub = this.unsubscribers.get(key);
306
+ if (unsub) { unsub(); this.unsubscribers.delete(key); }
307
+ this.sections.delete(key);
308
+ }
309
+ }
310
+
311
+ /**
312
+ * Push `meta:*` sections to HeadService — SPA navigation never runs Blade,
313
+ * so nothing else keeps `<title>`/`<meta>` (outside the client's mounted
314
+ * tree entirely) in sync with the active page after the first load. This
315
+ * is the declarative convenience layer over HeadService: a page can
316
+ * either call `@section('meta:title', ...)` in its template, or reach
317
+ * `app('Head')` directly (e.g. from an async data callback) — both end up
318
+ * writing through the same service, so they never fight each other.
319
+ */
320
+ private activateHeadSections(viewId: string): void {
321
+ this.headViewIds.add(viewId);
322
+ for (const section of this.sections.values()) {
323
+ if (section.viewId === viewId && section.name.startsWith(HEAD_SECTION_PREFIX)) {
324
+ this.activeSections.set(section.name, section);
325
+ }
326
+ }
327
+ }
328
+
329
+ private syncHeadSections(): void {
330
+ for (const [name, section] of this.activeSections) {
331
+ if (!name.startsWith(HEAD_SECTION_PREFIX) || !section.renderFactory) continue;
332
+ if (!this.headViewIds.has(section.viewId ?? '')) continue;
333
+ const raw = section.renderFactory(null);
334
+ if (raw === undefined || raw === null) continue;
335
+ const value = String(raw);
336
+ const key = name.slice(HEAD_SECTION_PREFIX.length);
337
+ if (key === 'title') {
338
+ HeadService.setTitle(value);
339
+ HeadService.setMetaProperty('og:title', value);
340
+ HeadService.setMeta('twitter:title', value);
341
+ } else if (key === 'description') {
342
+ HeadService.setMeta('description', value);
343
+ HeadService.setMetaProperty('og:description', value);
344
+ HeadService.setMeta('twitter:description', value);
345
+ } else if (key.startsWith('og:')) {
346
+ HeadService.setMetaProperty(key, value);
347
+ } else if (key.startsWith('twitter:')) {
348
+ HeadService.setMeta(key, value);
349
+ } else if (key === 'canonical') {
350
+ HeadService.setLink('canonical', value);
351
+ } else if (key.startsWith('jsonld:')) {
352
+ if (!value.trim()) continue; // Async page data has not arrived yet.
353
+ try {
354
+ const data = JSON.parse(value);
355
+ if (data && typeof data === 'object' && !Array.isArray(data)) {
356
+ HeadService.setJsonLd(key.slice('jsonld:'.length), data);
357
+ }
358
+ } catch {
359
+ console.warn(`[SectionManager] Invalid JSON-LD in section "${name}".`);
360
+ }
361
+ } else {
362
+ HeadService.setMeta(key, value);
363
+ }
364
+ }
365
+ }
366
+
367
+ /** Revert every page-scoped head tag before mounting a new page's chain. Call once per navigation. */
368
+ resetPageHead(): void {
369
+ HeadService.resetPage();
370
+ this.headViewIds.clear();
371
+ }
372
+
373
+ destroy(): void {
374
+ this.headViewIds.clear();
375
+ for (const unsub of this.unsubscribers.values()) unsub();
376
+ this.unsubscribers.clear();
377
+ for (const [, y] of this.yields) this.clearYield(y);
378
+ this.sections.clear();
379
+ this.activeSections.clear();
380
+ this.yields.clear();
381
+ this.subscribers.clear();
382
+ this.mounted.clear();
383
+ }
384
+ }
385
+
386
+ export const SectionManager = new SectionManagerService();
387
+ export default SectionManager;
@@ -47,7 +47,7 @@ export class StorageService {
47
47
  private listeners = new Map<string, StorageEventCallback[]>();
48
48
  private supported: boolean;
49
49
 
50
- constructor(storageKey: string = 'oneview_storage') {
50
+ constructor(storageKey: string = 'saoview_storage') {
51
51
  this.storageKey = storageKey;
52
52
  this.supported = typeof localStorage !== 'undefined';
53
53
  if (this.supported) this.load();