@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,8 +1,13 @@
1
- import { InitMode, InitModes } from "../contracts/common";
2
- import type { FragmentInterface, HtmlInterface, OneChildrenFactory, OneChildrenFactoryOutput, OneElementChildren, WrapperInterface } from "../contracts/ElementInterface";
3
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
4
- import { generateUUID } from "../hellpers/utils";
5
- import type { OneObjectType } from "../types/utils";
1
+ import { InitMode, InitModes } from "../contracts/common.js";
2
+ import type { DOMElement, FragmentInterface, HtmlInterface, SaoChildrenFactory, SaoChildrenFactoryOutput, SaoElement, SaoElementChildren, WrapperInterface } from "../contracts/ElementInterface.js";
3
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
4
+ import { app } from "../helpers/app.js";
5
+ import { generateUUID } from "../helpers/utils.js";
6
+ import { mountElementList } from "../helpers/view.js";
7
+ import { MarkerRegistryService } from "../services/index.js";
8
+ import type { SaoObjectType } from "../types/utils.js";
9
+ import { Html } from "./Html.js";
10
+ import { TextElement } from "./TextElement.js";
6
11
 
7
12
  /**
8
13
  * Wrapper — renders multiple root nodes into a parent without a wrapping tag.
@@ -14,13 +19,13 @@ import type { OneObjectType } from "../types/utils";
14
19
  * similar to Reactive but without the reactivity overhead.
15
20
  */
16
21
  export class Wrapper implements WrapperInterface {
17
- oneType: OneObjectType = 'Wrapper';
22
+ saoType: SaoObjectType = 'Wrapper';
18
23
  public parent: HtmlInterface | null;
19
24
  public nodes: Node[] = [];
20
25
  /** Tracked child element wrappers (Html, Output, Reactive, TextElement, etc.) */
21
- public children: OneElementChildren = [];
26
+ public children: SaoElementChildren = [];
22
27
  private ctx: ViewControllerInterface;
23
- private childrenFactory: OneChildrenFactory;
28
+ private childrenFactory: SaoChildrenFactory;
24
29
  public openTag: Comment;
25
30
  public closeTag: Comment;
26
31
  public id: string; // Unique ID for debugging and marker registry
@@ -37,7 +42,7 @@ export class Wrapper implements WrapperInterface {
37
42
  ctx: ViewControllerInterface,
38
43
  initMode?: InitMode,
39
44
  parentElement?: HtmlInterface | null,
40
- childrenFactory: OneChildrenFactory
45
+ childrenFactory: SaoChildrenFactory
41
46
  }) {
42
47
  this.ctx = ctx;
43
48
  this.parent = parentElement;
@@ -45,18 +50,75 @@ export class Wrapper implements WrapperInterface {
45
50
  this.id = ctx.viewId;
46
51
  this.initMode = initMode;
47
52
 
48
- this.openTag = document.createComment('wrapper-start');
49
- this.closeTag = document.createComment('wrapper-end');
53
+ this.init();
54
+ const registry = app<MarkerRegistryService>("Registry");
55
+ if (this.initMode === InitModes.HYDRATE) {
56
+ // ── Hydrate: tìm view markers SSR trong DOM ─────────────────
57
+ // Format MarkerRegistry: open = `v:id`, close = `/v:id`
58
+ // (v = shortcut cho 'view'). Nếu không có (vd partial hydration
59
+ // khi root element CHÍNH là boundary của view) → tạo markers mới,
60
+ // không cảnh báo vì đây là trường hợp hợp lệ.
61
+ const claimed = this.claimSSRMarkers(registry);
62
+ if (claimed) {
63
+ this.openTag = claimed.open;
64
+ this.closeTag = claimed.close;
65
+ } else {
66
+ this.openTag = registry.createMarkerStart('view', this.id);
67
+ this.closeTag = registry.createMarkerEnd('view', this.id);
68
+ }
69
+ }
70
+ else {
71
+ this.openTag = registry.createMarkerStart('view', this.id);
72
+ this.closeTag = registry.createMarkerEnd('view', this.id);
73
+ }
74
+
75
+ }
76
+
77
+ /**
78
+ * Tìm cặp view markers từ server-rendered HTML.
79
+ * Format MarkerRegistry: open = `v:id`, close = `/v:id`.
80
+ * Quét comment nodes trong parent element (fallback document.body).
81
+ */
82
+ private claimSSRMarkers(
83
+ registry: MarkerRegistryService
84
+ ): { open: Comment; close: Comment } | null {
85
+ return registry.claim('view', this.id, this.parent?.element ?? null);
86
+ }
87
+
88
+ init() {
50
89
 
51
90
  }
91
+
92
+
52
93
  setParentElement(parent: HtmlInterface | null): void {
53
94
  this.parent = parent;
54
95
  }
55
- render(): void {
56
-
96
+ render(): SaoElementChildren {
97
+ this.children = [];
98
+ const children = this.childrenFactory(this.parent);
99
+ this.children = children
100
+ .filter((child): child is NonNullable<typeof child> => child !== null && child !== undefined)
101
+ .map((child) => {
102
+ if (typeof child === 'string' || typeof child === 'number') {
103
+ return new TextElement({ ctx: this.ctx, parent: this.parent, stateKeys: [], generateText: () => String(child) });
104
+ }
105
+ return child;
106
+ });
107
+ return this.children;
108
+ }
109
+ appendTo(parent: HtmlInterface): void {
110
+ this.parent = parent;
111
+ this.parent.appendElement(this.openTag);
112
+ mountElementList(this.parent, this.render());
113
+ this.parent.appendElement(this.closeTag);
114
+ }
115
+
116
+ mountTo(parent: HtmlInterface): void {
117
+ parent.clearHTML();
118
+ this.appendTo(parent);
57
119
  }
58
120
 
59
- setChildrenFactory(factory: OneChildrenFactory): void {
121
+ setChildrenFactory(factory: SaoChildrenFactory): void {
60
122
  this.childrenFactory = factory;
61
123
  }
62
124
 
@@ -108,24 +170,28 @@ export class Wrapper implements WrapperInterface {
108
170
  this.nodes = [];
109
171
  }
110
172
 
173
+ /** Registry guard — wrapper đã destroy không được reuse (ViewController.wrapper) */
174
+ public __destroyed__: boolean = false;
175
+
111
176
  destroy(): void {
177
+ this.__destroyed__ = true;
112
178
  this.clear();
113
179
  this.openTag.remove();
114
180
  this.closeTag.remove();
115
181
  this.parent = null;
116
182
  }
117
183
 
118
- get isOneElement(): boolean {
184
+ get isSaoElement(): boolean {
119
185
  return true;
120
186
  }
121
- set isOneElement(value: boolean) {
187
+ set isSaoElement(value: boolean) {
122
188
  // No-op setter to satisfy the Interface; this property is always true for Fragment elements
123
189
  }
124
190
 
125
- get isOneFragment(): boolean {
191
+ get isSaoFragment(): boolean {
126
192
  return true;
127
193
  }
128
- set isOneFragment(value: boolean) {
194
+ set isSaoFragment(value: boolean) {
129
195
  // No-op setter to satisfy the Interface; this property is always true for Fragment elements
130
196
  }
131
197
  }
@@ -1,54 +1,63 @@
1
- import { InitMode, InitModes } from "../contracts/common";
2
- import { HtmlInterface, OneChildrenFactoryOutput, YieldInterface } from "../contracts/ElementInterface";
3
- import { ViewControllerInterface } from "../contracts/ViewControllerInterface";
4
- import { generateUUID } from "../hellpers/utils";
5
- import { MarkerModel, OneMarker } from "../services/MarkerService";
6
- import { OneObjectType } from "../types/utils";
1
+ import { InitMode, InitModes } from "../contracts/common.js";
2
+ import { HtmlInterface, YieldInterface } from "../contracts/ElementInterface.js";
3
+ import { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
4
+ import { generateUUID } from "../helpers/utils.js";
5
+ import markerRegistry from "../services/MarkerRegistry.js";
6
+ import { SaoObjectType } from "../types/utils.js";
7
7
 
8
8
  export class YieldElement implements YieldInterface{
9
- oneType: OneObjectType = "Yield";
9
+ saoType: SaoObjectType = "Yield";
10
10
  ctx: ViewControllerInterface;
11
11
  name: string
12
12
  id: string;
13
- contentFactory: () => OneChildrenFactoryOutput = () => [];
14
13
  openTag!: Comment;
15
14
  closeTag!: Comment;
16
15
  initMode: InitMode = InitModes.CREATE
17
16
  domChildren: Node[] = [];
18
17
  parent: HtmlInterface | null = null;
19
18
  defaultValue: string = '';
19
+ /** Registry guard — thiếu field này thì aliveFromRegistry tái dùng Yield đã destroy */
20
+ public __destroyed__: boolean = false;
20
21
  constructor({ctx, name = '', initMode = InitModes.CREATE, id = null, defaultValue = ''} : {ctx: ViewControllerInterface, name: string, initMode?: InitMode, id?: string | null, defaultValue?: string}) {
21
22
  this.ctx = ctx;
22
23
  this.name = name;
23
24
  this.initMode = initMode;
24
- this.id = id && id.length > 0 ? id : generateUUID();
25
+ // Marker id đầy đủ = {viewId}-{hash} — khớp quy ước server (HYDRATION.md §5.1),
26
+ // giống Component.ts. Trước đây thiếu prefix viewId → hydrate không tìm đúng marker.
27
+ const rawId = id && id.length > 0 ? id : generateUUID();
28
+ this.id = `${ctx.viewId}-${rawId}`;
25
29
  this.defaultValue = defaultValue;
26
-
27
- const yeildMarker: MarkerModel | null = (this.initMode === InitModes.HYDRATE) ? OneMarker.first('yield', this.id) : null;
28
- if(yeildMarker){
29
- this.openTag = yeildMarker.openTag;
30
- this.closeTag = yeildMarker.closeTag;
31
- this.domChildren = yeildMarker.nodes.map((el: Node) => el);
30
+
31
+ // Claim marker server qua index O(1) của MarkerRegistry — giống Reactive/
32
+ // Output/Component. Bản cũ duyệt TOÀN BỘ comment của tài liệu cho mỗi @yield.
33
+ const claimed = (this.initMode === InitModes.HYDRATE)
34
+ ? markerRegistry.claim('yield', this.id)
35
+ : null;
36
+
37
+ if (claimed) {
38
+ this.openTag = claimed.open;
39
+ this.closeTag = claimed.close;
40
+ for (let node = claimed.open.nextSibling; node && node !== claimed.close; node = node.nextSibling) {
41
+ this.domChildren.push(node);
42
+ }
32
43
  } else {
33
44
  this.createMarkers();
34
45
  }
35
46
 
36
47
  }
37
48
  private createMarkers(){
38
- const key = OneMarker.addRegistry('yield', this.id, {name: this.name});
39
- this.openTag = OneMarker.createOpenMarker('yield', this.id);
40
- this.closeTag = OneMarker.createCloseMarker('yield', this.id);
49
+ markerRegistry.register('yield', this.id, {name: this.name});
50
+ this.openTag = markerRegistry.createMarkerStart('yield', this.id);
51
+ this.closeTag = markerRegistry.createMarkerEnd('yield', this.id);
41
52
  }
42
53
 
43
54
  setParentElement(parent: HtmlInterface | null): void {
44
55
  this.parent = parent;
45
56
  }
46
57
 
47
- setContentFactory(factory: () => OneChildrenFactoryOutput): void {
48
- this.contentFactory = factory;
49
- }
50
-
58
+ /** Idempotent: markers already in DOM (hydrate claim, or same-layout reuse) → keep as-is. */
51
59
  render(): void {
60
+ if (this.openTag.parentNode) return;
52
61
  if (!this.parent?.element) return;
53
62
 
54
63
  const parentEl = this.parent.element;
@@ -57,14 +66,16 @@ export class YieldElement implements YieldInterface{
57
66
  }
58
67
 
59
68
  destroy(): void {
69
+ this.__destroyed__ = true;
70
+ this.ctx.releaseElement?.(this);
60
71
  this.openTag?.remove();
61
72
  this.closeTag?.remove();
62
73
  this.domChildren = [];
63
74
  this.parent = null;
64
75
  }
65
76
 
66
- get isOneElement(): boolean { return true; }
67
- set isOneElement(_: boolean) {}
77
+ get isSaoElement(): boolean { return true; }
78
+ set isSaoElement(_: boolean) {}
68
79
  get isOneYield(): boolean { return true; }
69
80
  set isOneYield(_: boolean) {}
70
81
  }
@@ -1,12 +1,12 @@
1
- import { BlockInterface, BlockOutletInterface } from "../contracts/BlockInterface";
2
- import { FragmentInterface, HtmlInterface, OutputInterface, OneChildrenFactoryOutput, OneElementChildren } from "../contracts/ElementInterface";
3
- import { ReactiveInterface } from "../contracts/ReactiveInterface";
4
- import { ViewControllerInterface } from "../contracts/ViewControllerInterface";
5
- import { OneObjectType } from "../types/utils";
1
+ import { BlockInterface, BlockOutletInterface } from "../contracts/BlockInterface.js";
2
+ import { FragmentInterface, HtmlInterface, OutputInterface, SaoChildrenFactoryOutput, SaoElementChildren } from "../contracts/ElementInterface.js";
3
+ import { ReactiveInterface } from "../contracts/ReactiveInterface.js";
4
+ import { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
5
+ import { SaoObjectType } from "../types/utils.js";
6
6
 
7
- export function getOneObjectType(instance: any): OneObjectType | null {
8
- if (instance && typeof instance === 'object' && 'oneType' in instance) {
9
- return instance.oneType;
7
+ export function getSaoObjectType(instance: any): SaoObjectType | null {
8
+ if (instance && typeof instance === 'object' && 'saoType' in instance) {
9
+ return instance.saoType;
10
10
  }
11
11
  return null;
12
12
  }
@@ -14,9 +14,9 @@ export function getOneObjectType(instance: any): OneObjectType | null {
14
14
  export function parseElementChildren(
15
15
  oneElement: HtmlInterface | ReactiveInterface | BlockOutletInterface | OutputInterface | FragmentInterface | ViewControllerInterface,
16
16
  parentElement: HTMLElement,
17
- children: OneChildrenFactoryOutput
18
- ): OneElementChildren {
19
- const parsedChildren: OneElementChildren = [];
17
+ children: SaoChildrenFactoryOutput
18
+ ): SaoElementChildren {
19
+ const parsedChildren: SaoElementChildren = [];
20
20
 
21
21
  if(!children || !Array.isArray(children)) {
22
22
  console.warn('Children factory returned invalid output (not an array):', children);
@@ -39,7 +39,7 @@ export function parseElementChildren(
39
39
  console.warn('Skipping invalid child:', child);
40
40
  return;
41
41
  }
42
- let childType: OneObjectType = getOneObjectType(child);
42
+ let childType: SaoObjectType = getSaoObjectType(child);
43
43
  if(!childType) {
44
44
  return;
45
45
  }
@@ -1,26 +1,27 @@
1
- import type { BlockOutletInterface, BlockRenderFactory } from "../contracts/BlockInterface";
2
- import type { HtmlInterface, HtmlElementConfig, OneChildrenFactory } from "../contracts/ElementInterface";
3
- import type { ReactiveChildrenFactory } from "../contracts/ReactiveInterface";
4
- import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
5
- import { ViewController } from "../view";
6
- import { Block } from "./Block";
7
- import BlockManager from "../services/BlockManager";
8
- import { BlockOutlet } from "./BlockOutlet";
9
- import { Fragment } from "./Fragment";
10
- import { Html } from "./Html";
11
- import { Output } from "./Output";
12
- import { Reactive } from "./Reactive";
13
- import { TextElement } from "./TextElement";
1
+ import type { BlockOutletInterface, BlockRenderFactory } from "../contracts/BlockInterface.js";
2
+ import type { HtmlInterface, HtmlElementConfig, SaoChildrenFactory } from "../contracts/ElementInterface.js";
3
+ import type { ReactiveChildrenFactory } from "../contracts/ReactiveInterface.js";
4
+ import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
5
+ import { ViewController } from "../view/index.js";
6
+ import { Block } from "./Block.js";
7
+ import BlockManager from "../services/BlockManager.js";
8
+ import { BlockOutlet } from "./BlockOutlet.js";
9
+ import { Fragment } from "./Fragment.js";
10
+ import { Html } from "./Html.js";
11
+ import { Output } from "./Output.js";
12
+ import { Reactive } from "./Reactive.js";
13
+ import { TextElement } from "./TextElement.js";
14
14
 
15
15
 
16
- export * from "./Block";
17
- export * from "./BlockOutlet";
18
- export * from './Component';
19
- export * from "./ElementManager";
20
- export * from "./Fragment";
21
- export * from "./helpers";
22
- export * from "./Html";
23
- export * from "./Output";
24
- export * from './Reactive';
25
- export * from "./TextElement";
26
- export * from "./Yield";
16
+ export * from "./Block.js";
17
+ export * from "./BlockOutlet.js";
18
+ export * from './Component.js';
19
+ export * from "./ElementManager.js";
20
+ export * from "./Fragment.js";
21
+ export * from "./helpers.js";
22
+ export * from "./Html.js";
23
+ export * from "./Output.js";
24
+ export * from './Reactive.js';
25
+ export * from './ForeachSlotCache.js';
26
+ export * from "./TextElement.js";
27
+ export * from "./Yield.js";
@@ -0,0 +1,70 @@
1
+ import { TextElement } from './TextElement.js';
2
+
3
+ /** Static compiler text has no lifecycle state; retain its existing Text node. */
4
+ export function reuseStaticText(previous: readonly any[], next: any[]): any[] {
5
+ return next.map((child, index) => {
6
+ const old = previous[index];
7
+ if (old instanceof TextElement && child instanceof TextElement && old !== child
8
+ && old.isStatic && child.isStatic && !old.__destroyed__) {
9
+ old.replaceStaticText(child.text);
10
+ child.destroy();
11
+ return old;
12
+ }
13
+ return child;
14
+ });
15
+ }
16
+
17
+ /** Place an element or marker range without touching nodes already in order. */
18
+ export function placeChild(parent: Node, child: any, before: Node | null): Node | null {
19
+ const first: Node | undefined = child instanceof Node ? child : child.element ?? child.openTag;
20
+ const last: Node | undefined = child instanceof Node ? child : child.element ?? child.closeTag;
21
+ if (!first || !last) return before;
22
+ if (first.parentNode === parent && last.parentNode === parent && last.nextSibling === before) return first;
23
+ const nodes: Node[] = [first];
24
+ if (first !== last) {
25
+ let current = first.nextSibling;
26
+ while (current && current !== last) {
27
+ nodes.push(current);
28
+ current = current.nextSibling;
29
+ }
30
+ nodes.push(last);
31
+ }
32
+ for (const node of nodes) {
33
+ // State-preserving native moves where available; detached/new nodes use
34
+ // normal insertion. Older browsers use the focus restoration below.
35
+ const moveBefore = (parent as any).moveBefore;
36
+ if (typeof moveBefore === 'function' && node.isConnected && parent.isConnected) moveBefore.call(parent, node, before);
37
+ else parent.insertBefore(node, before);
38
+ }
39
+ return first;
40
+ }
41
+
42
+ /** Reconcile Saola children by wrapper identity; factories refresh their config. */
43
+ export function reconcileChildren(
44
+ parent: Node, previous: readonly any[], next: readonly any[],
45
+ anchor: Node | null = null, refresh: (child: any) => boolean = () => true,
46
+ ): void {
47
+ const focused = parent.ownerDocument?.activeElement as HTMLElement | null;
48
+ const containsFocus = focused && parent.contains(focused);
49
+ const selection = containsFocus && 'selectionStart' in focused
50
+ ? { start: (focused as HTMLInputElement).selectionStart, end: (focused as HTMLInputElement).selectionEnd,
51
+ direction: (focused as HTMLInputElement).selectionDirection } : null;
52
+ const nextSet = new Set(next);
53
+ const oldSet = new Set(previous);
54
+ for (const child of previous) {
55
+ if (nextSet.has(child)) continue;
56
+ if (typeof child.destroy === 'function') child.destroy();
57
+ else if (child instanceof Node) child.parentNode?.removeChild(child);
58
+ }
59
+ let before = anchor;
60
+ for (let i = next.length - 1; i >= 0; i--) before = placeChild(parent, next[i], before);
61
+ for (const child of next) {
62
+ if (!oldSet.has(child) || refresh(child)) child.render?.();
63
+ }
64
+ if (containsFocus && focused.isConnected && parent.ownerDocument?.activeElement !== focused) {
65
+ focused.focus({ preventScroll: true });
66
+ if (selection?.start != null && selection.end != null) {
67
+ (focused as HTMLInputElement).setSelectionRange(selection.start, selection.end, selection.direction ?? undefined);
68
+ }
69
+ }
70
+ }
@@ -47,7 +47,14 @@ export class ApiClient implements APIClientInterface {
47
47
  }
48
48
 
49
49
  init(config: Record<string, any>): void {
50
- // Placeholder for any future initialization logic
50
+ if (typeof config.baseUrl === 'string') this.http.setBaseUrl(config.baseUrl);
51
+ if (typeof config.timeout === 'number') this.http.setTimeout(config.timeout);
52
+ for (const [name, value] of Object.entries(config.headers || {})) {
53
+ this.http.setHeader(name, String(value));
54
+ }
55
+ if (config.endpoints && typeof config.endpoints === 'object') {
56
+ this.endpoints = { ...this.endpoints, ...config.endpoints };
57
+ }
51
58
  }
52
59
 
53
60
  /**
@@ -1,5 +1,5 @@
1
- import { Application } from "../app/Application";
2
- import { AppFactory, ServiceKey } from "../contracts/ApplicationInterface";
1
+ import { Application } from "../app/Application.js";
2
+ import { AppFactory, ServiceKey } from "../contracts/ApplicationInterface.js";
3
3
 
4
4
 
5
5
  /**
@@ -8,6 +8,12 @@ import { AppFactory, ServiceKey } from "../contracts/ApplicationInterface";
8
8
  */
9
9
  const AppInstance = new Application();
10
10
 
11
+ // Tự đăng ký container dưới key 'App' — compiled view luôn gọi `app("App")`
12
+ // để lấy Application (App.View, App.Helper, App.Router...). Thiếu dòng này thì
13
+ // mọi view compiled ném "Cannot resolve: App". (Unit test cũ dùng `new View()`
14
+ // trực tiếp nên không lộ; chỉ lộ khi chạy view compiled thật trên trình duyệt.)
15
+ AppInstance.instance('App', AppInstance);
16
+
11
17
  /**
12
18
  * Helper function để lấy/đăng ký services
13
19
  * Cách dùng:
@@ -35,6 +41,11 @@ const appFactory = <T>(key?: ServiceKey, value?: any): Application | T | any | v
35
41
  // Không truyền gì → trả về container
36
42
  return AppInstance;
37
43
  }
44
+ // Compiler contract: mọi generated view dùng app("App"). Alias này phải
45
+ // tồn tại cả sau flush()/test teardown, không phụ thuộc singleton map.
46
+ if (key === 'App' && value === undefined) {
47
+ return AppInstance;
48
+ }
38
49
  if (value !== undefined) {
39
50
  // Truyền 2 tham số → đăng ký instance
40
51
  AppInstance.instance(key, value);
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Transition enter/leave cho element — `@transition('fade')`.
3
+ *
4
+ * # Quy ước class (giống Vue, để người dùng không phải học lại)
5
+ * enter: `{n}-enter-from` `{n}-enter-active` → frame sau → `{n}-enter-to`
6
+ * leave: `{n}-leave-from` `{n}-leave-active` → frame sau → `{n}-leave-to`
7
+ * `-active` có mặt suốt quá trình (chỗ đặt `transition:`), `-from`/`-to` là
8
+ * hai đầu giá trị.
9
+ *
10
+ * # Vì sao dùng `getAnimations()` chứ không nghe `transitionend`
11
+ * `transitionend` bắn MỘT lần cho MỖI property, không bắn nếu giá trị không
12
+ * đổi, và không phân biệt transition của element với của con nó — nên cách
13
+ * làm kinh điển phải parse `transition-duration` + hẹn giờ dự phòng.
14
+ * `element.getAnimations()` trả thẳng mọi animation/transition đang chạy trên
15
+ * element kèm promise `.finished`, xử lý đúng cả trường hợp nhiều property và
16
+ * bị huỷ giữa chừng. Không có animation nào → resolve ngay → hành vi y hệt
17
+ * lúc chưa có transition (đây cũng là đường chạy trong jsdom).
18
+ *
19
+ * # Vì sao cần `isLeaving()`
20
+ * Element rời đi phải NẰM LẠI DOM tới khi animation xong, trong khi
21
+ * `Reactive.clearContent()`/`_cleanOrphanNodes()` quét sạch mọi node giữa cặp
22
+ * marker. Hai vòng quét đó phải bỏ qua node đang leave, nếu không node bị giật
23
+ * đi ngay và animation không bao giờ thấy được.
24
+ */
25
+
26
+ /** Node đang chạy leave — vòng quét DOM giữa marker phải BỎ QUA. */
27
+ const leavingNodes = new WeakSet<Node>();
28
+
29
+ /** Sequence hiện tại của mỗi element — chống enter/leave chồng nhau (reorder). */
30
+ const generation = new WeakMap<Element, number>();
31
+
32
+ /** True khi node đang chạy leave và chưa được gỡ. */
33
+ export function isLeaving(node: Node): boolean {
34
+ return leavingNodes.has(node);
35
+ }
36
+
37
+ function bump(el: Element): number {
38
+ const next = (generation.get(el) ?? 0) + 1;
39
+ generation.set(el, next);
40
+ return next;
41
+ }
42
+
43
+ const isCurrent = (el: Element, token: number) => generation.get(el) === token;
44
+
45
+ /** Đợi sang frame kế — để trình duyệt kịp áp `-from` trước khi đổi sang `-to`. */
46
+ function nextFrame(): Promise<void> {
47
+ return new Promise(resolve => {
48
+ if (typeof requestAnimationFrame !== 'function') return resolve();
49
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Đợi mọi animation/transition trên element kết thúc.
55
+ * `allSettled` vì `.finished` REJECT khi animation bị huỷ — với ta thì huỷ
56
+ * cũng là xong, không phải lỗi.
57
+ *
58
+ * ponytail: animation vô hạn (`animation-iteration-count: infinite`) trên
59
+ * element đang leave sẽ giữ node lại mãi. Thêm timeout trần nếu gặp thật.
60
+ */
61
+ async function whenAnimationsDone(el: Element): Promise<void> {
62
+ const getAnimations = (el as any).getAnimations;
63
+ if (typeof getAnimations !== 'function') return; // jsdom / browser cũ
64
+ const running = getAnimations.call(el) as Animation[];
65
+ if (!running || running.length === 0) return;
66
+ await Promise.allSettled(running.map(a => a.finished));
67
+ }
68
+
69
+ const classesOf = (name: string, phase: 'enter' | 'leave') => ({
70
+ from: `${name}-${phase}-from`,
71
+ active: `${name}-${phase}-active`,
72
+ to: `${name}-${phase}-to`,
73
+ });
74
+
75
+ /** Gỡ mọi class transition của cả 2 phase — dùng khi bị huỷ giữa chừng. */
76
+ function clearClasses(el: Element, name: string): void {
77
+ for (const phase of ['enter', 'leave'] as const) {
78
+ const c = classesOf(name, phase);
79
+ el.classList.remove(c.from, c.active, c.to);
80
+ }
81
+ }
82
+
83
+ /** Chạy enter. Bị enter/leave khác chen ngang → thoát, không đụng DOM nữa. */
84
+ export async function runEnter(el: Element, name: string): Promise<void> {
85
+ const token = bump(el);
86
+ const c = classesOf(name, 'enter');
87
+
88
+ clearClasses(el, name);
89
+ el.classList.add(c.from, c.active);
90
+
91
+ await nextFrame();
92
+ if (!isCurrent(el, token)) return;
93
+
94
+ el.classList.remove(c.from);
95
+ el.classList.add(c.to);
96
+
97
+ await whenAnimationsDone(el);
98
+ if (!isCurrent(el, token)) return;
99
+
100
+ el.classList.remove(c.active, c.to);
101
+ }
102
+
103
+ /**
104
+ * Chạy leave rồi GỠ node. Trả promise resolve sau khi node đã rời DOM.
105
+ * Element được đánh dấu leaving ngay lập tức để không ai giật mất giữa chừng.
106
+ */
107
+ export async function runLeave(el: Element, name: string): Promise<void> {
108
+ const token = bump(el);
109
+ const c = classesOf(name, 'leave');
110
+
111
+ leavingNodes.add(el);
112
+ clearClasses(el, name);
113
+ el.classList.add(c.from, c.active);
114
+
115
+ await nextFrame();
116
+ if (!isCurrent(el, token)) { leavingNodes.delete(el); return; }
117
+
118
+ el.classList.remove(c.from);
119
+ el.classList.add(c.to);
120
+
121
+ await whenAnimationsDone(el);
122
+ if (!isCurrent(el, token)) { leavingNodes.delete(el); return; }
123
+
124
+ leavingNodes.delete(el);
125
+ el.classList.remove(c.active, c.to);
126
+ (el as ChildNode).remove();
127
+ }
@@ -80,8 +80,8 @@ export function isTextNode(value: any): boolean {
80
80
  return value instanceof Text;
81
81
  }
82
82
 
83
- export function isOneElement(value: any): boolean {
84
- return value && typeof value === 'object' && 'isOneElement' in value && value.isOneElement === true;
83
+ export function isSaoElement(value: any): boolean {
84
+ return value && typeof value === 'object' && 'isSaoElement' in value && value.isSaoElement === true;
85
85
  }
86
86
 
87
87
  export function isOneHtml(value: any): boolean {
@@ -100,8 +100,8 @@ export function isOneReactive(value: any): boolean {
100
100
  return value && typeof value === 'object' && 'isOneReactive' in value && value.isOneReactive === true;
101
101
  }
102
102
 
103
- export function isOneFragment(value: any): boolean {
104
- return value && typeof value === 'object' && 'isOneFragment' in value && value.isOneFragment === true;
103
+ export function isSaoFragment(value: any): boolean {
104
+ return value && typeof value === 'object' && 'isSaoFragment' in value && value.isSaoFragment === true;
105
105
  }
106
106
 
107
107
  export function isOneBlock(value: any): boolean {