@saolabs/client 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (383) hide show
  1. package/README.md +52 -195
  2. package/dist/index.d.ts +20 -5
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +14 -3
  5. package/dist/index.js.map +1 -1
  6. package/dist/plugins.d.ts +2 -2
  7. package/dist/plugins.js +2 -2
  8. package/dist/src/core/app/Application.d.ts +25 -15
  9. package/dist/src/core/app/Application.d.ts.map +1 -1
  10. package/dist/src/core/app/Application.js +55 -18
  11. package/dist/src/core/app/Application.js.map +1 -1
  12. package/dist/src/core/bootstrap/app.d.ts +1 -1
  13. package/dist/src/core/bootstrap/app.d.ts.map +1 -1
  14. package/dist/src/core/bootstrap/app.js +77 -4
  15. package/dist/src/core/bootstrap/app.js.map +1 -1
  16. package/dist/src/core/bootstrap/bundle.d.ts +41 -0
  17. package/dist/src/core/bootstrap/bundle.d.ts.map +1 -0
  18. package/dist/src/core/bootstrap/bundle.js +98 -0
  19. package/dist/src/core/bootstrap/bundle.js.map +1 -0
  20. package/dist/src/core/bootstrap/providers/ApiServiceProvider.d.ts +1 -1
  21. package/dist/src/core/bootstrap/providers/ApiServiceProvider.d.ts.map +1 -1
  22. package/dist/src/core/bootstrap/providers/ApiServiceProvider.js +3 -3
  23. package/dist/src/core/bootstrap/providers/ApiServiceProvider.js.map +1 -1
  24. package/dist/src/core/bootstrap/providers/CoreServiceProvider.d.ts +1 -1
  25. package/dist/src/core/bootstrap/providers/CoreServiceProvider.d.ts.map +1 -1
  26. package/dist/src/core/bootstrap/providers/CoreServiceProvider.js +11 -8
  27. package/dist/src/core/bootstrap/providers/CoreServiceProvider.js.map +1 -1
  28. package/dist/src/core/bootstrap/providers/HelperServiceProvider.d.ts +3 -1
  29. package/dist/src/core/bootstrap/providers/HelperServiceProvider.d.ts.map +1 -1
  30. package/dist/src/core/bootstrap/providers/HelperServiceProvider.js +8 -3
  31. package/dist/src/core/bootstrap/providers/HelperServiceProvider.js.map +1 -1
  32. package/dist/src/core/bootstrap/providers/RouteServiceProvider.d.ts +2 -1
  33. package/dist/src/core/bootstrap/providers/RouteServiceProvider.d.ts.map +1 -1
  34. package/dist/src/core/bootstrap/providers/RouteServiceProvider.js +14 -4
  35. package/dist/src/core/bootstrap/providers/RouteServiceProvider.js.map +1 -1
  36. package/dist/src/core/bootstrap/providers/ServiceProvider.d.ts +4 -4
  37. package/dist/src/core/bootstrap/providers/ServiceProvider.d.ts.map +1 -1
  38. package/dist/src/core/bootstrap/providers/ServiceProvider.js +5 -5
  39. package/dist/src/core/bootstrap/providers/ServiceProvider.js.map +1 -1
  40. package/dist/src/core/bootstrap/providers/ViewServiceProvider.d.ts +1 -1
  41. package/dist/src/core/bootstrap/providers/ViewServiceProvider.d.ts.map +1 -1
  42. package/dist/src/core/bootstrap/providers/ViewServiceProvider.js +3 -3
  43. package/dist/src/core/bootstrap/providers/ViewServiceProvider.js.map +1 -1
  44. package/dist/src/core/bootstrap/providers/default-providers.d.ts +1 -1
  45. package/dist/src/core/bootstrap/providers/default-providers.d.ts.map +1 -1
  46. package/dist/src/core/bootstrap/providers/default-providers.js +44 -11
  47. package/dist/src/core/bootstrap/providers/default-providers.js.map +1 -1
  48. package/dist/src/core/bootstrap/providers/index.d.ts +9 -9
  49. package/dist/src/core/bootstrap/providers/index.d.ts.map +1 -1
  50. package/dist/src/core/bootstrap/providers/index.js +8 -8
  51. package/dist/src/core/bootstrap/providers/index.js.map +1 -1
  52. package/dist/src/core/bootstrap/providers/provider-order.d.ts +1 -1
  53. package/dist/src/core/bootstrap/providers/provider-order.d.ts.map +1 -1
  54. package/dist/src/core/bootstrap/ssr.d.ts +47 -0
  55. package/dist/src/core/bootstrap/ssr.d.ts.map +1 -0
  56. package/dist/src/core/bootstrap/ssr.js +129 -0
  57. package/dist/src/core/bootstrap/ssr.js.map +1 -0
  58. package/dist/src/core/cache/ViewCache.d.ts +1 -1
  59. package/dist/src/core/cache/ViewCache.d.ts.map +1 -1
  60. package/dist/src/core/cache/index.d.ts +1 -1
  61. package/dist/src/core/cache/index.d.ts.map +1 -1
  62. package/dist/src/core/cache/index.js +1 -1
  63. package/dist/src/core/cache/index.js.map +1 -1
  64. package/dist/src/core/contracts/ApplicationInterface.d.ts +15 -11
  65. package/dist/src/core/contracts/ApplicationInterface.d.ts.map +1 -1
  66. package/dist/src/core/contracts/BlockInterface.d.ts +10 -10
  67. package/dist/src/core/contracts/BlockInterface.d.ts.map +1 -1
  68. package/dist/src/core/contracts/BundleInterface.d.ts +38 -0
  69. package/dist/src/core/contracts/BundleInterface.d.ts.map +1 -0
  70. package/dist/src/core/contracts/BundleInterface.js +2 -0
  71. package/dist/src/core/contracts/BundleInterface.js.map +1 -0
  72. package/dist/src/core/contracts/ComponentInterface.d.ts +7 -6
  73. package/dist/src/core/contracts/ComponentInterface.d.ts.map +1 -1
  74. package/dist/src/core/contracts/ElementInterface.d.ts +91 -27
  75. package/dist/src/core/contracts/ElementInterface.d.ts.map +1 -1
  76. package/dist/src/core/contracts/HeadServiceInterface.d.ts +37 -0
  77. package/dist/src/core/contracts/HeadServiceInterface.d.ts.map +1 -0
  78. package/dist/src/core/contracts/HeadServiceInterface.js +3 -0
  79. package/dist/src/core/contracts/HeadServiceInterface.js.map +1 -0
  80. package/dist/src/core/contracts/HelperInterface.d.ts +9 -1
  81. package/dist/src/core/contracts/HelperInterface.d.ts.map +1 -1
  82. package/dist/src/core/contracts/HttpServiceInterface.d.ts +3 -0
  83. package/dist/src/core/contracts/HttpServiceInterface.d.ts.map +1 -1
  84. package/dist/src/core/contracts/MarkerInterface.d.ts +29 -44
  85. package/dist/src/core/contracts/MarkerInterface.d.ts.map +1 -1
  86. package/dist/src/core/contracts/ReactiveInterface.d.ts +9 -9
  87. package/dist/src/core/contracts/ReactiveInterface.d.ts.map +1 -1
  88. package/dist/src/core/contracts/RouterInterface.d.ts +5 -2
  89. package/dist/src/core/contracts/RouterInterface.d.ts.map +1 -1
  90. package/dist/src/core/contracts/SectionInterface.d.ts +48 -0
  91. package/dist/src/core/contracts/SectionInterface.d.ts.map +1 -0
  92. package/dist/src/core/contracts/SectionInterface.js +2 -0
  93. package/dist/src/core/contracts/SectionInterface.js.map +1 -0
  94. package/dist/src/core/contracts/ServiceProviderInterface.d.ts +3 -3
  95. package/dist/src/core/contracts/ServiceProviderInterface.d.ts.map +1 -1
  96. package/dist/src/core/contracts/ViewControllerInterface.d.ts +116 -15
  97. package/dist/src/core/contracts/ViewControllerInterface.d.ts.map +1 -1
  98. package/dist/src/core/contracts/ViewInterface.d.ts +31 -5
  99. package/dist/src/core/contracts/ViewInterface.d.ts.map +1 -1
  100. package/dist/src/core/contracts/ViewManagerInterface.d.ts +47 -5
  101. package/dist/src/core/contracts/ViewManagerInterface.d.ts.map +1 -1
  102. package/dist/src/core/contracts/ViewStateInterface.d.ts +39 -2
  103. package/dist/src/core/contracts/ViewStateInterface.d.ts.map +1 -1
  104. package/dist/src/core/contracts/common.d.ts +3 -3
  105. package/dist/src/core/contracts/common.js +3 -3
  106. package/dist/src/core/contracts/utils.d.ts +19 -19
  107. package/dist/src/core/contracts/utils.d.ts.map +1 -1
  108. package/dist/src/core/contracts/views.d.ts +1 -22
  109. package/dist/src/core/contracts/views.d.ts.map +1 -1
  110. package/dist/src/core/devtools/hook.d.ts +66 -0
  111. package/dist/src/core/devtools/hook.d.ts.map +1 -0
  112. package/dist/src/core/devtools/hook.js +114 -0
  113. package/dist/src/core/devtools/hook.js.map +1 -0
  114. package/dist/src/core/devtools/index.d.ts +34 -0
  115. package/dist/src/core/devtools/index.d.ts.map +1 -0
  116. package/dist/src/core/devtools/index.js +33 -0
  117. package/dist/src/core/devtools/index.js.map +1 -0
  118. package/dist/src/core/devtools/inspector.d.ts +31 -0
  119. package/dist/src/core/devtools/inspector.d.ts.map +1 -0
  120. package/dist/src/core/devtools/inspector.js +249 -0
  121. package/dist/src/core/devtools/inspector.js.map +1 -0
  122. package/dist/src/core/elements/Block.d.ts +9 -7
  123. package/dist/src/core/elements/Block.d.ts.map +1 -1
  124. package/dist/src/core/elements/Block.js +33 -9
  125. package/dist/src/core/elements/Block.js.map +1 -1
  126. package/dist/src/core/elements/BlockOutlet.d.ts +19 -8
  127. package/dist/src/core/elements/BlockOutlet.d.ts.map +1 -1
  128. package/dist/src/core/elements/BlockOutlet.js +57 -13
  129. package/dist/src/core/elements/BlockOutlet.js.map +1 -1
  130. package/dist/src/core/elements/Component.d.ts +83 -11
  131. package/dist/src/core/elements/Component.d.ts.map +1 -1
  132. package/dist/src/core/elements/Component.js +334 -13
  133. package/dist/src/core/elements/Component.js.map +1 -1
  134. package/dist/src/core/elements/ForeachSlotCache.d.ts +95 -0
  135. package/dist/src/core/elements/ForeachSlotCache.d.ts.map +1 -0
  136. package/dist/src/core/elements/ForeachSlotCache.js +149 -0
  137. package/dist/src/core/elements/ForeachSlotCache.js.map +1 -0
  138. package/dist/src/core/elements/Fragment.d.ts +19 -13
  139. package/dist/src/core/elements/Fragment.d.ts.map +1 -1
  140. package/dist/src/core/elements/Fragment.js +43 -20
  141. package/dist/src/core/elements/Fragment.js.map +1 -1
  142. package/dist/src/core/elements/Html.d.ts +89 -12
  143. package/dist/src/core/elements/Html.d.ts.map +1 -1
  144. package/dist/src/core/elements/Html.js +707 -106
  145. package/dist/src/core/elements/Html.js.map +1 -1
  146. package/dist/src/core/elements/Output.d.ts +34 -10
  147. package/dist/src/core/elements/Output.d.ts.map +1 -1
  148. package/dist/src/core/elements/Output.js +141 -26
  149. package/dist/src/core/elements/Output.js.map +1 -1
  150. package/dist/src/core/elements/Reactive.d.ts +85 -14
  151. package/dist/src/core/elements/Reactive.d.ts.map +1 -1
  152. package/dist/src/core/elements/Reactive.js +315 -41
  153. package/dist/src/core/elements/Reactive.js.map +1 -1
  154. package/dist/src/core/elements/TextElement.d.ts +11 -7
  155. package/dist/src/core/elements/TextElement.d.ts.map +1 -1
  156. package/dist/src/core/elements/TextElement.js +18 -11
  157. package/dist/src/core/elements/TextElement.js.map +1 -1
  158. package/dist/src/core/elements/Wrapper.d.ts +24 -13
  159. package/dist/src/core/elements/Wrapper.d.ts.map +1 -1
  160. package/dist/src/core/elements/Wrapper.js +65 -8
  161. package/dist/src/core/elements/Wrapper.js.map +1 -1
  162. package/dist/src/core/elements/Yield.d.ts +10 -9
  163. package/dist/src/core/elements/Yield.d.ts.map +1 -1
  164. package/dist/src/core/elements/Yield.js +31 -19
  165. package/dist/src/core/elements/Yield.js.map +1 -1
  166. package/dist/src/core/elements/helpers.d.ts +7 -7
  167. package/dist/src/core/elements/helpers.d.ts.map +1 -1
  168. package/dist/src/core/elements/helpers.js +4 -4
  169. package/dist/src/core/elements/index.d.ts +12 -11
  170. package/dist/src/core/elements/index.d.ts.map +1 -1
  171. package/dist/src/core/elements/index.js +12 -11
  172. package/dist/src/core/elements/index.js.map +1 -1
  173. package/dist/src/core/elements/reconcileChildren.d.ts +7 -0
  174. package/dist/src/core/elements/reconcileChildren.d.ts.map +1 -0
  175. package/dist/src/core/elements/reconcileChildren.js +74 -0
  176. package/dist/src/core/elements/reconcileChildren.js.map +1 -0
  177. package/dist/src/core/helpers/ApiClient.d.ts.map +1 -0
  178. package/dist/src/core/{hellpers → helpers}/ApiClient.js +10 -1
  179. package/dist/src/core/helpers/ApiClient.js.map +1 -0
  180. package/dist/src/core/helpers/app.d.ts +3 -0
  181. package/dist/src/core/helpers/app.d.ts.map +1 -0
  182. package/dist/src/core/{hellpers → helpers}/app.js +11 -1
  183. package/dist/src/core/helpers/app.js.map +1 -0
  184. package/dist/src/core/helpers/transition.d.ts +34 -0
  185. package/dist/src/core/helpers/transition.d.ts.map +1 -0
  186. package/dist/src/core/helpers/transition.js +118 -0
  187. package/dist/src/core/helpers/transition.js.map +1 -0
  188. package/dist/src/core/{hellpers → helpers}/utils.d.ts +2 -2
  189. package/dist/src/core/helpers/utils.d.ts.map +1 -0
  190. package/dist/src/core/{hellpers → helpers}/utils.js +4 -4
  191. package/dist/src/core/helpers/utils.js.map +1 -0
  192. package/dist/src/core/helpers/view.d.ts +39 -0
  193. package/dist/src/core/helpers/view.d.ts.map +1 -0
  194. package/dist/src/core/helpers/view.js +286 -0
  195. package/dist/src/core/helpers/view.js.map +1 -0
  196. package/dist/src/core/routers/Router.d.ts +108 -7
  197. package/dist/src/core/routers/Router.d.ts.map +1 -1
  198. package/dist/src/core/routers/Router.js +444 -68
  199. package/dist/src/core/routers/Router.js.map +1 -1
  200. package/dist/src/core/routers/index.d.ts +2 -2
  201. package/dist/src/core/routers/index.d.ts.map +1 -1
  202. package/dist/src/core/routers/index.js +1 -1
  203. package/dist/src/core/routers/index.js.map +1 -1
  204. package/dist/src/core/services/AssetManager.d.ts +112 -0
  205. package/dist/src/core/services/AssetManager.d.ts.map +1 -0
  206. package/dist/src/core/services/AssetManager.js +361 -0
  207. package/dist/src/core/services/AssetManager.js.map +1 -0
  208. package/dist/src/core/services/BlockManager.d.ts +73 -2
  209. package/dist/src/core/services/BlockManager.d.ts.map +1 -1
  210. package/dist/src/core/services/BlockManager.js +270 -48
  211. package/dist/src/core/services/BlockManager.js.map +1 -1
  212. package/dist/src/core/services/DomService.js +3 -3
  213. package/dist/src/core/services/DomService.js.map +1 -1
  214. package/dist/src/core/services/HeadService.d.ts +24 -0
  215. package/dist/src/core/services/HeadService.d.ts.map +1 -0
  216. package/dist/src/core/services/HeadService.js +120 -0
  217. package/dist/src/core/services/HeadService.js.map +1 -0
  218. package/dist/src/core/services/HelperService.d.ts +14 -3
  219. package/dist/src/core/services/HelperService.d.ts.map +1 -1
  220. package/dist/src/core/services/HelperService.js +32 -2
  221. package/dist/src/core/services/HelperService.js.map +1 -1
  222. package/dist/src/core/services/HttpService.d.ts +8 -0
  223. package/dist/src/core/services/HttpService.d.ts.map +1 -1
  224. package/dist/src/core/services/HttpService.js +121 -47
  225. package/dist/src/core/services/HttpService.js.map +1 -1
  226. package/dist/src/core/services/MarkerRegistry.d.ts +51 -8
  227. package/dist/src/core/services/MarkerRegistry.d.ts.map +1 -1
  228. package/dist/src/core/services/MarkerRegistry.js +135 -14
  229. package/dist/src/core/services/MarkerRegistry.js.map +1 -1
  230. package/dist/src/core/services/PageCache.d.ts +101 -0
  231. package/dist/src/core/services/PageCache.d.ts.map +1 -0
  232. package/dist/src/core/services/PageCache.js +184 -0
  233. package/dist/src/core/services/PageCache.js.map +1 -0
  234. package/dist/src/core/services/SectionManager.d.ts +100 -0
  235. package/dist/src/core/services/SectionManager.d.ts.map +1 -0
  236. package/dist/src/core/services/SectionManager.js +408 -0
  237. package/dist/src/core/services/SectionManager.js.map +1 -0
  238. package/dist/src/core/services/StorageService.js +1 -1
  239. package/dist/src/core/services/TranslationService.d.ts +21 -0
  240. package/dist/src/core/services/TranslationService.d.ts.map +1 -0
  241. package/dist/src/core/services/TranslationService.js +95 -0
  242. package/dist/src/core/services/TranslationService.js.map +1 -0
  243. package/dist/src/core/services/index.d.ts +11 -8
  244. package/dist/src/core/services/index.d.ts.map +1 -1
  245. package/dist/src/core/services/index.js +11 -8
  246. package/dist/src/core/services/index.js.map +1 -1
  247. package/dist/src/core/types/utils.d.ts +8 -8
  248. package/dist/src/core/types/utils.d.ts.map +1 -1
  249. package/dist/src/core/view/LoopContext.d.ts +21 -1
  250. package/dist/src/core/view/LoopContext.d.ts.map +1 -1
  251. package/dist/src/core/view/LoopContext.js +36 -0
  252. package/dist/src/core/view/LoopContext.js.map +1 -1
  253. package/dist/src/core/view/ResourceScope.d.ts +38 -0
  254. package/dist/src/core/view/ResourceScope.d.ts.map +1 -0
  255. package/dist/src/core/view/ResourceScope.js +167 -0
  256. package/dist/src/core/view/ResourceScope.js.map +1 -0
  257. package/dist/src/core/view/Section.d.ts +17 -32
  258. package/dist/src/core/view/Section.d.ts.map +1 -1
  259. package/dist/src/core/view/Section.js +14 -55
  260. package/dist/src/core/view/Section.js.map +1 -1
  261. package/dist/src/core/view/View.d.ts +43 -7
  262. package/dist/src/core/view/View.d.ts.map +1 -1
  263. package/dist/src/core/view/View.js +22 -4
  264. package/dist/src/core/view/View.js.map +1 -1
  265. package/dist/src/core/view/ViewController.d.ts +313 -40
  266. package/dist/src/core/view/ViewController.d.ts.map +1 -1
  267. package/dist/src/core/view/ViewController.js +1002 -147
  268. package/dist/src/core/view/ViewController.js.map +1 -1
  269. package/dist/src/core/view/ViewManager.d.ts +318 -27
  270. package/dist/src/core/view/ViewManager.d.ts.map +1 -1
  271. package/dist/src/core/view/ViewManager.js +1199 -258
  272. package/dist/src/core/view/ViewManager.js.map +1 -1
  273. package/dist/src/core/view/ViewState.d.ts +185 -6
  274. package/dist/src/core/view/ViewState.d.ts.map +1 -1
  275. package/dist/src/core/view/ViewState.js +851 -123
  276. package/dist/src/core/view/ViewState.js.map +1 -1
  277. package/dist/src/core/view/index.d.ts +5 -5
  278. package/dist/src/core/view/index.d.ts.map +1 -1
  279. package/dist/src/core/view/index.js +5 -5
  280. package/dist/src/core/view/index.js.map +1 -1
  281. package/dist/src/testing/index.d.ts +69 -0
  282. package/dist/src/testing/index.d.ts.map +1 -0
  283. package/dist/src/testing/index.js +140 -0
  284. package/dist/src/testing/index.js.map +1 -0
  285. package/index.ts +29 -5
  286. package/package.json +30 -21
  287. package/plugins.ts +2 -2
  288. package/src/core/app/Application.ts +72 -31
  289. package/src/core/bootstrap/app.ts +84 -6
  290. package/src/core/bootstrap/bundle.ts +101 -0
  291. package/src/core/bootstrap/providers/ApiServiceProvider.ts +4 -4
  292. package/src/core/bootstrap/providers/CoreServiceProvider.ts +14 -8
  293. package/src/core/bootstrap/providers/HelperServiceProvider.ts +8 -3
  294. package/src/core/bootstrap/providers/RouteServiceProvider.ts +15 -4
  295. package/src/core/bootstrap/providers/ServiceProvider.ts +8 -8
  296. package/src/core/bootstrap/providers/ViewServiceProvider.ts +3 -3
  297. package/src/core/bootstrap/providers/default-providers.ts +51 -16
  298. package/src/core/bootstrap/providers/index.ts +9 -9
  299. package/src/core/bootstrap/providers/provider-order.test.ts +16 -27
  300. package/src/core/bootstrap/providers/provider-order.ts +1 -1
  301. package/src/core/bootstrap/ssr.ts +141 -0
  302. package/src/core/cache/ViewCache.ts +1 -1
  303. package/src/core/cache/index.ts +1 -1
  304. package/src/core/contracts/ApplicationInterface.ts +15 -11
  305. package/src/core/contracts/BlockInterface.ts +18 -18
  306. package/src/core/contracts/BundleInterface.ts +40 -0
  307. package/src/core/contracts/ComponentInterface.ts +7 -6
  308. package/src/core/contracts/ElementInterface.ts +95 -27
  309. package/src/core/contracts/HeadServiceInterface.ts +41 -0
  310. package/src/core/contracts/HelperInterface.ts +9 -1
  311. package/src/core/contracts/HttpServiceInterface.ts +3 -0
  312. package/src/core/contracts/MarkerInterface.ts +22 -49
  313. package/src/core/contracts/ReactiveInterface.ts +9 -9
  314. package/src/core/contracts/RouterInterface.ts +5 -2
  315. package/src/core/contracts/SectionInterface.ts +49 -0
  316. package/src/core/contracts/ServiceProviderInterface.ts +3 -3
  317. package/src/core/contracts/ViewControllerInterface.ts +124 -16
  318. package/src/core/contracts/ViewInterface.ts +34 -5
  319. package/src/core/contracts/ViewManagerInterface.ts +40 -5
  320. package/src/core/contracts/ViewStateInterface.ts +39 -2
  321. package/src/core/contracts/common.ts +3 -3
  322. package/src/core/contracts/utils.ts +19 -19
  323. package/src/core/contracts/views.ts +2 -22
  324. package/src/core/devtools/hook.ts +142 -0
  325. package/src/core/devtools/index.ts +38 -0
  326. package/src/core/devtools/inspector.ts +270 -0
  327. package/src/core/elements/Block.ts +43 -21
  328. package/src/core/elements/BlockOutlet.ts +61 -21
  329. package/src/core/elements/Component.ts +352 -18
  330. package/src/core/elements/ElementManager.ts +9 -9
  331. package/src/core/elements/ForeachSlotCache.ts +170 -0
  332. package/src/core/elements/Fragment.ts +50 -32
  333. package/src/core/elements/Html.ts +696 -117
  334. package/src/core/elements/Output.ts +147 -30
  335. package/src/core/elements/Reactive.ts +329 -52
  336. package/src/core/elements/TextElement.ts +25 -16
  337. package/src/core/elements/Wrapper.ts +84 -18
  338. package/src/core/elements/Yield.ts +35 -24
  339. package/src/core/elements/helpers.ts +12 -12
  340. package/src/core/elements/index.ts +25 -24
  341. package/src/core/elements/reconcileChildren.ts +70 -0
  342. package/src/core/{hellpers → helpers}/ApiClient.ts +8 -1
  343. package/src/core/{hellpers → helpers}/app.ts +13 -2
  344. package/src/core/helpers/transition.ts +127 -0
  345. package/src/core/{hellpers → helpers}/utils.ts +4 -4
  346. package/src/core/helpers/view.ts +283 -0
  347. package/src/core/routers/Router.ts +458 -64
  348. package/src/core/routers/index.ts +2 -2
  349. package/src/core/services/AssetManager.ts +387 -0
  350. package/src/core/services/BlockManager.ts +272 -50
  351. package/src/core/services/DomService.ts +3 -3
  352. package/src/core/services/HeadService.ts +124 -0
  353. package/src/core/services/HelperService.ts +34 -4
  354. package/src/core/services/HttpService.ts +116 -55
  355. package/src/core/services/MarkerRegistry.ts +142 -16
  356. package/src/core/services/PageCache.ts +239 -0
  357. package/src/core/services/SectionManager.ts +387 -0
  358. package/src/core/services/StorageService.ts +1 -1
  359. package/src/core/services/TranslationService.ts +91 -0
  360. package/src/core/services/index.ts +11 -8
  361. package/src/core/types/utils.ts +8 -8
  362. package/src/core/view/LoopContext.ts +38 -1
  363. package/src/core/view/ResourceScope.ts +174 -0
  364. package/src/core/view/Section.ts +21 -68
  365. package/src/core/view/View.ts +57 -8
  366. package/src/core/view/ViewController.ts +1070 -186
  367. package/src/core/view/ViewManager.ts +1346 -272
  368. package/src/core/view/ViewState.ts +834 -116
  369. package/src/core/view/index.ts +5 -5
  370. package/src/testing/index.ts +184 -0
  371. package/dist/src/core/hellpers/ApiClient.d.ts.map +0 -1
  372. package/dist/src/core/hellpers/ApiClient.js.map +0 -1
  373. package/dist/src/core/hellpers/app.d.ts +0 -3
  374. package/dist/src/core/hellpers/app.d.ts.map +0 -1
  375. package/dist/src/core/hellpers/app.js.map +0 -1
  376. package/dist/src/core/hellpers/utils.d.ts.map +0 -1
  377. package/dist/src/core/hellpers/utils.js.map +0 -1
  378. package/dist/src/core/services/MarkerService.d.ts +0 -299
  379. package/dist/src/core/services/MarkerService.d.ts.map +0 -1
  380. package/dist/src/core/services/MarkerService.js +0 -723
  381. package/dist/src/core/services/MarkerService.js.map +0 -1
  382. package/src/core/services/MarkerService.ts +0 -923
  383. /package/dist/src/core/{hellpers → helpers}/ApiClient.d.ts +0 -0
@@ -1,7 +1,7 @@
1
- import type { BlockInterface, BlockManagerInterface, BlockOutletInterface, BlockRenderFactory } from "../contracts/BlockInterface";
2
- import type { HtmlInterface } from "../contracts/ElementInterface";
3
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
4
- import { Block } from "../elements/Block";
1
+ import type { BlockInterface, BlockManagerInterface, BlockOutletInterface, BlockRenderFactory } from "../contracts/BlockInterface.js";
2
+ import type { HtmlInterface } from "../contracts/ElementInterface.js";
3
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
4
+ import { Block } from "../elements/Block.js";
5
5
 
6
6
  /**
7
7
  * BlockManager — manages all block slots in a layout view.
@@ -96,91 +96,313 @@ export class BlockManagerService implements BlockManagerInterface {
96
96
  * viewId:blockName), and inserts block content between outlet markers.
97
97
  */
98
98
  mountAll(): void {
99
- for (const [name, block] of this.activeBlocks) {
100
- // Try to find an outlet that matches this block name
101
- for (const [outletKey, outlet] of this.blockOutlets) {
102
- if (outletKey.endsWith(`:${name}`) || outlet.name === name) {
103
- if (block.contentRenderFactory) {
104
- this.mountBlockIntoOutlet(block, outlet);
105
- }
106
- break;
107
- }
99
+ // Mount block content vào outlet cùng tên; outlet không có block active → clear về rỗng
100
+ for (const [, outlet] of this.blockOutlets) {
101
+ const block = this.activeBlocks.get(outlet.name);
102
+ if (block && block.contentRenderFactory) {
103
+ this.mountBlockIntoOutlet(block, outlet);
104
+ } else {
105
+ this.clearOutletInstance(outlet);
108
106
  }
109
107
  }
110
108
  }
111
109
 
110
+ /**
111
+ * Mount only blocks owned by one Page/Layout controller. Nested layout
112
+ * chains call this from outer to inner, so an inner outlet exists before
113
+ * the next owner is mounted and retained layouts are not rebuilt.
114
+ */
115
+ mountViewBlocks(viewId: string): void {
116
+ for (const [, block] of this.blocks) {
117
+ if (block.viewId !== viewId || !block.contentRenderFactory) continue;
118
+ const outlet = this.findOutletByName(block.name);
119
+ if (!outlet) continue;
120
+ this.activeBlocks.set(block.name, block);
121
+ this.mountBlockIntoOutlet(block, outlet);
122
+ }
123
+ }
124
+
125
+ /** Hydration counterpart of mountViewBlocks(). */
126
+ hydrateViewBlocks(viewId: string): void {
127
+ for (const [, block] of this.blocks) {
128
+ if (block.viewId !== viewId || !block.contentRenderFactory) continue;
129
+ const outlet = this.findOutletByName(block.name);
130
+ if (!outlet) continue;
131
+ this.activeBlocks.set(block.name, block);
132
+ this.hydrateBlockIntoOutlet(block, outlet);
133
+ }
134
+ }
135
+
112
136
  /**
113
137
  * Mount a single block's content into an outlet.
114
- * Content is rendered and inserted between the outlet's open/close markers.
138
+ * Clear nội dung cũ trước, render content mới GIỮA outlet markers
139
+ * (cùng insertion model với Reactive — RUNTIME_CONTRACT.md §2).
115
140
  */
116
141
  private mountBlockIntoOutlet(block: BlockInterface, outlet: BlockOutletInterface): void {
117
- if (!outlet.parentElement?.element) return;
142
+ if (!outlet.openTag.parentNode) return; // outlet chưa nằm trong DOM
143
+
144
+ // Clear nội dung cũ (page trước) trước khi mount page mới
145
+ this.clearOutletInstance(outlet);
118
146
 
119
- const parentEl = outlet.parentElement.element;
120
147
  const children: any[] = [];
148
+ const insertBeforeClose = (node: Node) => {
149
+ outlet.closeTag.parentNode?.insertBefore(node, outlet.closeTag);
150
+ };
121
151
 
122
- // Render block content using the factory
123
- const content = block.contentRenderFactory!(block.ctx as any);
152
+ // Render block content using the factory — elements thuộc về PAGE ctrl
153
+ const content = block.contentRenderFactory!(outlet.parentElement as any);
124
154
 
125
- if (!Array.isArray(content)) return;
155
+ // Marker `s:b:` của chính block phải bao quanh content — Blade emit cặp này
156
+ // trong @block (ViewStorageManager::getMarkerOpenTag), nên thiếu nó ở đây là
157
+ // DOM sau SPA nav khác DOM từ server: hydrate lần sau không tìm ra block để
158
+ // claim, và cổng tests/e2e/ssr-csr-parity thấy marker lệch một phía.
159
+ // Block dựng sẵn openTag/closeTag trong constructor; chỗ chèn là ở đây, cùng
160
+ // insertion model với content (RUNTIME_CONTRACT.md §2).
161
+ insertBeforeClose(block.openTag);
126
162
 
127
- // Insert each child between outlet markers
128
- for (const child of content) {
163
+ // Content không phải mảng vẫn phải đóng marker — block rỗng ở server là cặp
164
+ // marker liền nhau, không phải không có marker.
165
+ for (const child of Array.isArray(content) ? content : []) {
166
+ if (child === null || child === undefined) continue;
129
167
  if (typeof child === 'string' || typeof child === 'number') {
130
- const textNode = document.createTextNode(String(child));
131
- parentEl.insertBefore(textNode, outlet.closeTag);
132
- } else if (child && typeof child === 'object') {
133
- if ('element' in child) {
134
- // HtmlInterface, TextInterface
135
- parentEl.insertBefore(child.element, outlet.closeTag);
168
+ insertBeforeClose(document.createTextNode(String(child)));
169
+ } else if (child instanceof Node) {
170
+ insertBeforeClose(child);
171
+ } else if (typeof child === 'object') {
172
+ if ('element' in child && (child as any).element) {
173
+ insertBeforeClose((child as any).element);
136
174
  children.push(child);
137
175
  child.render();
138
176
  } else if ('openTag' in child) {
139
- // Reactive, Fragment, Output — set parent, render
177
+ // Marker-based: đặt markers đúng vị trí trước, render sau (idempotent)
140
178
  if ('parent' in child) {
141
179
  (child as any).parent = outlet.parentElement;
142
180
  }
143
181
  if ('parentElement' in child) {
144
182
  (child as any).parentElement = outlet.parentElement;
145
183
  }
184
+ insertBeforeClose((child as any).openTag);
185
+ insertBeforeClose((child as any).closeTag);
146
186
  children.push(child);
147
187
  child.render();
148
188
  }
149
189
  }
150
190
  }
151
191
 
152
- // Track mounted children for cleanup
153
- this.mountedChildren.set(outlet.name, children);
192
+ insertBeforeClose(block.closeTag);
193
+
194
+ // Track mounted children for lifecycle (start/stop/destroy)
195
+ this.mountedChildren.set(this.outletKey(outlet), children);
154
196
  }
155
197
 
156
198
  /**
157
- * Clear content from a specific outlet (for page swap).
158
- * Removes all DOM nodes between a named outlet's markers.
199
+ * Hydrate version của mountAll — dùng khi SSR.
200
+ * KHÁC mountAll: KHÔNG clearOutlet (giữ DOM server), KHÔNG insertBefore.
201
+ * Chạy block factory ở HYDRATE mode → Html/Output/Reactive con CLAIM
202
+ * DOM server đã render sẵn giữa cặp marker của outlet.
203
+ *
204
+ * Tiền đề: page ctrl.initMode === HYDRATE khi gọi (để this.html() trong
205
+ * factory tạo element claim DOM thay vì tạo mới).
159
206
  */
160
- clearOutlet(name: string): void {
161
- // Find outlet by name
162
- for (const [key, outlet] of this.blockOutlets) {
163
- if (outlet.name === name) {
164
- // Destroy tracked children first
165
- const children = this.mountedChildren.get(name) || [];
166
- if (children) {
167
- for (const child of children) {
168
- if ('destroy' in child && typeof child.destroy === 'function') {
169
- child.destroy();
170
- }
171
- }
172
- this.mountedChildren.delete(name);
207
+ mountAllHydrate(): void {
208
+ for (const [, outlet] of this.blockOutlets) {
209
+ const block = this.activeBlocks.get(outlet.name);
210
+ if (block && block.contentRenderFactory) {
211
+ this.hydrateBlockIntoOutlet(block, outlet);
212
+ }
213
+ // outlet không có block active → để nguyên DOM server (thường rỗng)
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Claim block content vào outlet (hydrate). Chạy factory, gọi render() đệ quy
219
+ * trên children để claim DOM, KHÔNG chèn node mới. Track children cho lifecycle.
220
+ */
221
+ private hydrateBlockIntoOutlet(block: BlockInterface, outlet: BlockOutletInterface): void {
222
+ const children: any[] = [];
223
+ const content = block.contentRenderFactory!(outlet.parentElement as any);
224
+ if (!Array.isArray(content)) return;
225
+
226
+ for (const child of content) {
227
+ if (child === null || child === undefined) continue;
228
+ if (typeof child === 'string' || typeof child === 'number') continue; // text: giữ server
229
+ if (child instanceof Node) continue;
230
+ if (typeof child === 'object') {
231
+ if ('element' in child && (child as any).element) {
232
+ children.push(child);
233
+ (child as any).render(); // HYDRATE: Html claim DOM, không chèn
234
+ } else if ('openTag' in child) {
235
+ if ('parent' in child) (child as any).parent = outlet.parentElement;
236
+ if ('parentElement' in child) (child as any).parentElement = outlet.parentElement;
237
+ children.push(child);
238
+ (child as any).render(); // HYDRATE: Output/Reactive claim markers
173
239
  }
240
+ }
241
+ }
242
+
243
+ this.mountedChildren.set(this.outletKey(outlet), children);
244
+ }
174
245
 
175
- // Remove any remaining DOM nodes between markers
246
+ /** Tìm outlet theo tên (outlet key = `${layoutViewId}-ob-${name}`) */
247
+ private findOutletByName(name: string): BlockOutletInterface | null {
248
+ let found: BlockOutletInterface | null = null;
249
+ for (const [, outlet] of this.blockOutlets) {
250
+ if (outlet.name === name) found = outlet;
251
+ }
252
+ // Nearest/deepest outlet is registered last while mounting a nested chain.
253
+ return found;
254
+ }
255
+
256
+ /**
257
+ * Detach block content của một page (rời trang, vào PageCache):
258
+ * gỡ DOM giữa markers của từng outlet vào DocumentFragment, lấy children
259
+ * đang track ra khỏi manager (KHÔNG destroy — instance sống trong cache).
260
+ *
261
+ * Trả Map<outletName, {fragment, children}> để restore sau này.
262
+ */
263
+ detachPageContent(viewId: string): Map<string, { fragment: DocumentFragment; children: any[] }> {
264
+ const result = new Map<string, { fragment: DocumentFragment; children: any[] }>();
265
+ for (const [name, block] of Array.from(this.activeBlocks)) {
266
+ if (block.viewId !== viewId) continue;
267
+
268
+ const fragment = document.createDocumentFragment();
269
+ const outlet = this.findOutletByName(name);
270
+ if (outlet && outlet.openTag.parentNode) {
176
271
  let current = outlet.openTag.nextSibling;
177
272
  while (current && current !== outlet.closeTag) {
178
273
  const next = current.nextSibling;
179
- current.remove();
274
+ fragment.appendChild(current); // appendChild tự remove khỏi DOM
180
275
  current = next;
181
276
  }
182
- break;
183
277
  }
278
+
279
+ const outletKey = outlet ? this.outletKey(outlet) : name;
280
+ const children = this.mountedChildren.get(outletKey) ?? [];
281
+ this.mountedChildren.delete(outletKey);
282
+ this.activeBlocks.delete(name); // page rời đi — không còn active ở outlet này
283
+ result.set(name, { fragment, children });
284
+ }
285
+ return result;
286
+ }
287
+
288
+ /**
289
+ * Restore block content của một page từ PageCache vào outlets hiện tại.
290
+ * Tiền đề: layout đang mount trùng với layout lúc detach (ViewManager guard).
291
+ */
292
+ restorePageContent(viewId: string, contents: Map<string, { fragment: DocumentFragment; children: any[] }>): void {
293
+ for (const [name, { fragment, children }] of contents) {
294
+ const outlet = this.findOutletByName(name);
295
+ if (outlet && outlet.closeTag.parentNode) {
296
+ outlet.closeTag.parentNode.insertBefore(fragment, outlet.closeTag);
297
+ }
298
+ if (outlet) this.mountedChildren.set(this.outletKey(outlet), children);
299
+ // Re-activate block của page này (blocks map còn giữ — pause không xoá)
300
+ const block = this.blocks.get(name + viewId);
301
+ if (block) {
302
+ this.activeBlocks.set(name, block);
303
+ }
304
+ }
305
+ }
306
+
307
+ /** Start toàn bộ block content đang mounted (gọi sau mountAll) */
308
+ startAll(): void {
309
+ for (const [, children] of this.mountedChildren) {
310
+ for (const child of children) {
311
+ if (child && typeof child.start === 'function') child.start();
312
+ }
313
+ }
314
+ }
315
+
316
+ /** Stop toàn bộ block content (trước khi swap page) */
317
+ stopAll(): void {
318
+ for (const [, children] of this.mountedChildren) {
319
+ for (const child of children) {
320
+ if (child && typeof child.stop === 'function') child.stop();
321
+ }
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Gỡ mọi dấu vết của một view (page bị destroy):
327
+ * clear outlet đang chứa content của nó + xoá block đăng ký.
328
+ */
329
+ unmountView(viewId: string): void {
330
+ for (const [name, block] of this.activeBlocks) {
331
+ if (block.viewId === viewId) {
332
+ this.clearOutlet(name);
333
+ this.activeBlocks.delete(name);
334
+ }
335
+ }
336
+ for (const [key, block] of this.blocks) {
337
+ if (block.viewId === viewId) {
338
+ // destroy TRƯỚC khi rời Map. Block KHÔNG nằm trong ctrl.elements
339
+ // nên teardown cây element không bao giờ chạm tới nó — đây là chỗ
340
+ // DUY NHẤT gỡ được record marker toàn cục của nó. Thiếu dòng này
341
+ // thì MarkerRegistry (singleton, sống qua navigate) tăng đúng 1
342
+ // record mỗi lần điều hướng, vĩnh viễn.
343
+ block.destroy?.();
344
+ this.blocks.delete(key);
345
+ }
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Gỡ outlets của một layout khỏi registry mà KHÔNG destroy — layout pause
351
+ * vào PageCache. Nếu để lại, mountAll/findOutletByName có thể đụng outlet
352
+ * (trùng tên) của layout đang detached. Re-register khi resume qua
353
+ * addOutlet (ViewManager.reregisterLayoutOutlets).
354
+ */
355
+ detachOutletsOfView(viewId: string): void {
356
+ for (const [key, outlet] of this.blockOutlets) {
357
+ if ((outlet as any).ctx?.viewId === viewId) {
358
+ this.blockOutlets.delete(key);
359
+ }
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Gỡ outlets của một layout bị destroy — XOÁ THẬT, khác hẳn
365
+ * `detachOutletsOfView` ở ngay trên (layout chỉ pause vào PageCache và sẽ
366
+ * được re-register, nên KHÔNG được destroy ở đó).
367
+ * `destroy()` idempotent nên gọi ở đây an toàn kể cả khi teardown cây
368
+ * element đã destroy outlet trước đó.
369
+ */
370
+ removeOutletsOfView(viewId: string): void {
371
+ for (const [key, outlet] of this.blockOutlets) {
372
+ if ((outlet as any).ctx?.viewId === viewId) {
373
+ this.mountedChildren.delete(this.outletKey(outlet));
374
+ (outlet as any).destroy?.();
375
+ this.blockOutlets.delete(key);
376
+ }
377
+ }
378
+ }
379
+
380
+ /**
381
+ * Clear content from a specific outlet (for page swap).
382
+ * Removes all DOM nodes between a named outlet's markers.
383
+ */
384
+ clearOutlet(name: string): void {
385
+ const outlet = this.findOutletByName(name);
386
+ if (outlet) this.clearOutletInstance(outlet);
387
+ }
388
+
389
+ private outletKey(outlet: BlockOutletInterface): string {
390
+ return String((outlet as any).id ?? `${(outlet as any).ctx?.viewId ?? ''}:${outlet.name}`);
391
+ }
392
+
393
+ private clearOutletInstance(outlet: BlockOutletInterface): void {
394
+ const key = this.outletKey(outlet);
395
+ const children = this.mountedChildren.get(key) ?? [];
396
+ for (const child of children) {
397
+ if ('destroy' in child && typeof child.destroy === 'function') child.destroy();
398
+ }
399
+ this.mountedChildren.delete(key);
400
+
401
+ let current = outlet.openTag.nextSibling;
402
+ while (current && current !== outlet.closeTag) {
403
+ const next = current.nextSibling;
404
+ current.remove();
405
+ current = next;
184
406
  }
185
407
  }
186
408
 
@@ -188,8 +410,8 @@ export class BlockManagerService implements BlockManagerInterface {
188
410
  * Clear all outlets (for full layout teardown).
189
411
  */
190
412
  clearAllOutlets(): void {
191
- for (const [key, outlet] of this.blockOutlets) {
192
- this.clearOutlet(outlet.name);
413
+ for (const [, outlet] of this.blockOutlets) {
414
+ this.clearOutletInstance(outlet);
193
415
  }
194
416
  }
195
417
 
@@ -1,4 +1,4 @@
1
- import { app } from "../hellpers/app";
1
+ import { app } from "../helpers/app.js";
2
2
 
3
3
 
4
4
  export class DomService {
@@ -9,9 +9,9 @@ export class DomService {
9
9
 
10
10
  parse(html: string): Node[]{
11
11
  this.container.innerHTML = html;
12
- const content = this.container.content;
12
+ const nodes = Array.from(this.container.content.childNodes);
13
13
  this.container.innerHTML = ''; // Clear template content to free memory
14
- return Array.from(content.childNodes);
14
+ return nodes;
15
15
  }
16
16
  create(tagName: string, options?: ElementCreationOptions): HTMLElement {
17
17
  return document.createElement(tagName, options);
@@ -0,0 +1,124 @@
1
+ import type { HeadLinkOptions, HeadServiceInterface, HeadTagOptions, HeadTagScope } from "../contracts/HeadServiceInterface.js";
2
+
3
+ interface ManagedHeadEntry {
4
+ scope: HeadTagScope;
5
+ /** Undo exactly this entry's DOM change — restore the pre-managed value, or remove the element this service created. */
6
+ revert: () => void;
7
+ }
8
+
9
+ /**
10
+ * HeadService — the single place that writes to `<head>` (title, meta, link,
11
+ * JSON-LD). Independent of any View: callable from anywhere via `app('Head')`
12
+ * / `App.Head`. Reuses whatever the server already rendered there (SSR's
13
+ * `<title>`, `<meta name="description">`, ...) instead of creating
14
+ * duplicates, and snapshots the pre-managed value so `resetPage()` can put it
15
+ * back — the fix for tags leaking from one SPA-navigated page into the next.
16
+ */
17
+ export class HeadServiceImpl implements HeadServiceInterface {
18
+ private managed: Map<string, ManagedHeadEntry> = new Map();
19
+
20
+ setTitle(title: string, options: HeadTagOptions = {}): void {
21
+ if (typeof document === 'undefined') return;
22
+ const key = '__title__';
23
+ if (!this.managed.has(key)) {
24
+ const original = document.title;
25
+ this.managed.set(key, {
26
+ scope: options.scope ?? 'page',
27
+ revert: () => { document.title = original; },
28
+ });
29
+ }
30
+ document.title = title;
31
+ }
32
+
33
+ setMeta(name: string, content: string, options: HeadTagOptions = {}): void {
34
+ this.setAttrTag(`meta:name:${name}`, 'meta', { name }, 'content', content, options.scope ?? 'page');
35
+ }
36
+
37
+ setMetaProperty(property: string, content: string, options: HeadTagOptions = {}): void {
38
+ this.setAttrTag(`meta:property:${property}`, 'meta', { property }, 'content', content, options.scope ?? 'page');
39
+ }
40
+
41
+ setLink(rel: string, href: string, options: HeadLinkOptions = {}): void {
42
+ const extra = options.attrs ?? {};
43
+ const key = `link:${rel}` + Object.entries(extra).map(([k, v]) => `:${k}=${v}`).join('');
44
+ this.setAttrTag(key, 'link', { rel, ...extra }, 'href', href, options.scope ?? 'page');
45
+ }
46
+
47
+ setJsonLd(id: string, data: Record<string, any>, options: HeadTagOptions = {}): void {
48
+ if (typeof document === 'undefined') return;
49
+ const key = `jsonld:${id}`;
50
+ const selector = `script[data-head-id="${id}"]`;
51
+ if (!this.managed.has(key)) {
52
+ let el = document.head.querySelector<HTMLScriptElement>(selector);
53
+ const created = !el;
54
+ if (!el) {
55
+ el = document.createElement('script');
56
+ el.type = 'application/ld+json';
57
+ el.setAttribute('data-head-id', id);
58
+ document.head.appendChild(el);
59
+ }
60
+ // Page-owned SSR schema belongs to the initial route; remove it on navigation.
61
+ const originalText = created || el.getAttribute('data-head-scope') === 'page' ? null : el.textContent;
62
+ this.managed.set(key, {
63
+ scope: options.scope ?? 'page',
64
+ revert: () => {
65
+ const target = document.head.querySelector<HTMLScriptElement>(selector);
66
+ if (!target) return;
67
+ if (originalText === null) target.remove();
68
+ else target.textContent = originalText;
69
+ },
70
+ });
71
+ }
72
+ const el = document.head.querySelector<HTMLScriptElement>(selector);
73
+ if (el) el.textContent = JSON.stringify(data);
74
+ }
75
+
76
+ unset(key: string): void {
77
+ const entry = this.managed.get(key);
78
+ if (!entry) return;
79
+ entry.revert();
80
+ this.managed.delete(key);
81
+ }
82
+
83
+ resetPage(): void {
84
+ for (const [key, entry] of Array.from(this.managed)) {
85
+ if (entry.scope !== 'page') continue;
86
+ entry.revert();
87
+ this.managed.delete(key);
88
+ }
89
+ }
90
+
91
+ /** Find-or-create an element by `identAttrs`, snapshot `valueAttr` once, then apply the new value. */
92
+ private setAttrTag(
93
+ key: string, tagName: string, identAttrs: Record<string, string>,
94
+ valueAttr: string, value: string, scope: HeadTagScope,
95
+ ): void {
96
+ if (typeof document === 'undefined') return;
97
+ const selector = tagName + Object.entries(identAttrs).map(([k, v]) => `[${k}="${v}"]`).join('');
98
+
99
+ if (!this.managed.has(key)) {
100
+ let el = document.head.querySelector<HTMLElement>(selector);
101
+ const created = !el;
102
+ if (!el) {
103
+ el = document.createElement(tagName);
104
+ for (const [k, v] of Object.entries(identAttrs)) el.setAttribute(k, v);
105
+ document.head.appendChild(el);
106
+ }
107
+ const originalValue = created ? null : el.getAttribute(valueAttr);
108
+ this.managed.set(key, {
109
+ scope,
110
+ revert: () => {
111
+ const target = document.head.querySelector<HTMLElement>(selector);
112
+ if (!target) return;
113
+ if (created) target.remove();
114
+ else if (originalValue === null) target.removeAttribute(valueAttr);
115
+ else target.setAttribute(valueAttr, originalValue);
116
+ },
117
+ });
118
+ }
119
+ document.head.querySelector<HTMLElement>(selector)?.setAttribute(valueAttr, value);
120
+ }
121
+ }
122
+
123
+ export const HeadService = new HeadServiceImpl();
124
+ export default HeadService;
@@ -1,9 +1,10 @@
1
- import { CollectionProxyInterface, HelperInterface } from "../contracts/HelperInterface";
2
- import { ApplicationInterface } from "../contracts/ApplicationInterface";
3
- import { app } from "../hellpers/app";
1
+ import { TranslationService, type TranslationConfig } from './TranslationService.js';
2
+ import { CollectionProxyInterface, HelperInterface } from "../contracts/HelperInterface.js";
3
+ import { ApplicationInterface } from "../contracts/ApplicationInterface.js";
4
+ import { app } from "../helpers/app.js";
4
5
 
5
6
  /**
6
- * HelperService — PHP-compatible helper functions for OneView runtime.
7
+ * HelperService — PHP-compatible helper functions for SaoView runtime.
7
8
  *
8
9
  * The compiler converts PHP function calls → `App.Helper.xxx()`.
9
10
  * This class provides JavaScript implementations of those PHP functions
@@ -37,6 +38,7 @@ import { app } from "../hellpers/app";
37
38
  */
38
39
  export class HelperService implements HelperInterface {
39
40
  public App: any = null;
41
+ private translations = new TranslationService();
40
42
  private config: Record<string, any> = {};
41
43
 
42
44
  constructor(App?: any) {
@@ -60,6 +62,34 @@ export class HelperService implements HelperInterface {
60
62
  this.config = { ...this.config, ...config };
61
63
  }
62
64
 
65
+ initTranslations(config: TranslationConfig): void {
66
+ this.translations.init(config);
67
+ this.syncLocale();
68
+ }
69
+
70
+ getLocale(): string { return this.translations.locale; }
71
+ __(key: string, replace: Record<string, any> = {}, locale?: string): string { return this.translations.trans(key, replace, locale); }
72
+ trans(key: string, replace: Record<string, any> = {}, locale?: string): string { return this.__(key, replace, locale); }
73
+ lang(key: string, replace: Record<string, any> = {}, locale?: string): string { return this.__(key, replace, locale); }
74
+ choice(key: string, count: number, replace: Record<string, any> = {}, locale?: string): string { return this.translations.choice(key, count, replace, locale); }
75
+ trans_choice(key: string, count: number, replace: Record<string, any> = {}, locale?: string): string { return this.choice(key, count, replace, locale); }
76
+
77
+ setLocale(locale: string): void {
78
+ if (!this.translations.setLocale(locale)) return;
79
+ this.syncLocale();
80
+ this.App?.View?.invalidateLocale?.();
81
+ const route = this.App?.Router?.getCurrentRoute?.();
82
+ if (route) this.App.Router.replace(route.$uri + (route.$fragment ? '#' + route.$fragment : ''));
83
+ if (typeof document !== 'undefined') document.dispatchEvent(new CustomEvent('saola:locale-change', { detail: { locale } }));
84
+ }
85
+
86
+ private syncLocale(): void {
87
+ const locale = this.getLocale();
88
+ if (typeof document !== 'undefined') document.documentElement.lang = locale;
89
+ this.App?.Http?.setHeader?.('Accept-Language', locale);
90
+ this.App?.API?.getHttpService?.()?.setHeader?.('Accept-Language', locale);
91
+ }
92
+
63
93
  // ─── Execution ──────────────────────────────────────────────
64
94
 
65
95
  /** Execute a function safely, return result or empty string on error */