@saolabs/client 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -195
- package/dist/index.d.ts +20 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/plugins.d.ts +2 -2
- package/dist/plugins.js +2 -2
- package/dist/src/core/app/Application.d.ts +17 -15
- package/dist/src/core/app/Application.d.ts.map +1 -1
- package/dist/src/core/app/Application.js +41 -18
- package/dist/src/core/app/Application.js.map +1 -1
- package/dist/src/core/bootstrap/app.d.ts +1 -1
- package/dist/src/core/bootstrap/app.d.ts.map +1 -1
- package/dist/src/core/bootstrap/app.js +74 -4
- package/dist/src/core/bootstrap/app.js.map +1 -1
- package/dist/src/core/bootstrap/bundle.d.ts +41 -0
- package/dist/src/core/bootstrap/bundle.d.ts.map +1 -0
- package/dist/src/core/bootstrap/bundle.js +98 -0
- package/dist/src/core/bootstrap/bundle.js.map +1 -0
- package/dist/src/core/bootstrap/providers/ApiServiceProvider.d.ts +1 -1
- package/dist/src/core/bootstrap/providers/ApiServiceProvider.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/ApiServiceProvider.js +3 -3
- package/dist/src/core/bootstrap/providers/ApiServiceProvider.js.map +1 -1
- package/dist/src/core/bootstrap/providers/CoreServiceProvider.d.ts +1 -1
- package/dist/src/core/bootstrap/providers/CoreServiceProvider.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/CoreServiceProvider.js +11 -8
- package/dist/src/core/bootstrap/providers/CoreServiceProvider.js.map +1 -1
- package/dist/src/core/bootstrap/providers/HelperServiceProvider.d.ts +3 -1
- package/dist/src/core/bootstrap/providers/HelperServiceProvider.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/HelperServiceProvider.js +8 -3
- package/dist/src/core/bootstrap/providers/HelperServiceProvider.js.map +1 -1
- package/dist/src/core/bootstrap/providers/RouteServiceProvider.d.ts +2 -1
- package/dist/src/core/bootstrap/providers/RouteServiceProvider.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/RouteServiceProvider.js +14 -4
- package/dist/src/core/bootstrap/providers/RouteServiceProvider.js.map +1 -1
- package/dist/src/core/bootstrap/providers/ServiceProvider.d.ts +4 -4
- package/dist/src/core/bootstrap/providers/ServiceProvider.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/ServiceProvider.js +5 -5
- package/dist/src/core/bootstrap/providers/ServiceProvider.js.map +1 -1
- package/dist/src/core/bootstrap/providers/ViewServiceProvider.d.ts +1 -1
- package/dist/src/core/bootstrap/providers/ViewServiceProvider.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/ViewServiceProvider.js +3 -3
- package/dist/src/core/bootstrap/providers/ViewServiceProvider.js.map +1 -1
- package/dist/src/core/bootstrap/providers/default-providers.d.ts +1 -1
- package/dist/src/core/bootstrap/providers/default-providers.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/default-providers.js +44 -11
- package/dist/src/core/bootstrap/providers/default-providers.js.map +1 -1
- package/dist/src/core/bootstrap/providers/index.d.ts +9 -9
- package/dist/src/core/bootstrap/providers/index.d.ts.map +1 -1
- package/dist/src/core/bootstrap/providers/index.js +8 -8
- package/dist/src/core/bootstrap/providers/index.js.map +1 -1
- package/dist/src/core/bootstrap/providers/provider-order.d.ts +1 -1
- package/dist/src/core/bootstrap/providers/provider-order.d.ts.map +1 -1
- package/dist/src/core/bootstrap/ssr.d.ts +47 -0
- package/dist/src/core/bootstrap/ssr.d.ts.map +1 -0
- package/dist/src/core/bootstrap/ssr.js +125 -0
- package/dist/src/core/bootstrap/ssr.js.map +1 -0
- package/dist/src/core/cache/ViewCache.d.ts +1 -1
- package/dist/src/core/cache/ViewCache.d.ts.map +1 -1
- package/dist/src/core/cache/index.d.ts +1 -1
- package/dist/src/core/cache/index.d.ts.map +1 -1
- package/dist/src/core/cache/index.js +1 -1
- package/dist/src/core/cache/index.js.map +1 -1
- package/dist/src/core/contracts/ApplicationInterface.d.ts +13 -11
- package/dist/src/core/contracts/ApplicationInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/BlockInterface.d.ts +10 -10
- package/dist/src/core/contracts/BlockInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/BundleInterface.d.ts +38 -0
- package/dist/src/core/contracts/BundleInterface.d.ts.map +1 -0
- package/dist/src/core/contracts/BundleInterface.js +2 -0
- package/dist/src/core/contracts/BundleInterface.js.map +1 -0
- package/dist/src/core/contracts/ComponentInterface.d.ts +7 -6
- package/dist/src/core/contracts/ComponentInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/ElementInterface.d.ts +91 -27
- package/dist/src/core/contracts/ElementInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/HeadServiceInterface.d.ts +37 -0
- package/dist/src/core/contracts/HeadServiceInterface.d.ts.map +1 -0
- package/dist/src/core/contracts/HeadServiceInterface.js +3 -0
- package/dist/src/core/contracts/HeadServiceInterface.js.map +1 -0
- package/dist/src/core/contracts/HelperInterface.d.ts +9 -1
- package/dist/src/core/contracts/HelperInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/HttpServiceInterface.d.ts +3 -0
- package/dist/src/core/contracts/HttpServiceInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/MarkerInterface.d.ts +29 -44
- package/dist/src/core/contracts/MarkerInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/ReactiveInterface.d.ts +9 -9
- package/dist/src/core/contracts/ReactiveInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/RouterInterface.d.ts +5 -2
- package/dist/src/core/contracts/RouterInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/SectionInterface.d.ts +48 -0
- package/dist/src/core/contracts/SectionInterface.d.ts.map +1 -0
- package/dist/src/core/contracts/SectionInterface.js +2 -0
- package/dist/src/core/contracts/SectionInterface.js.map +1 -0
- package/dist/src/core/contracts/ServiceProviderInterface.d.ts +3 -3
- package/dist/src/core/contracts/ServiceProviderInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/ViewControllerInterface.d.ts +116 -15
- package/dist/src/core/contracts/ViewControllerInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/ViewInterface.d.ts +31 -5
- package/dist/src/core/contracts/ViewInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/ViewManagerInterface.d.ts +47 -5
- package/dist/src/core/contracts/ViewManagerInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/ViewStateInterface.d.ts +39 -2
- package/dist/src/core/contracts/ViewStateInterface.d.ts.map +1 -1
- package/dist/src/core/contracts/common.d.ts +3 -3
- package/dist/src/core/contracts/common.js +3 -3
- package/dist/src/core/contracts/utils.d.ts +19 -19
- package/dist/src/core/contracts/utils.d.ts.map +1 -1
- package/dist/src/core/contracts/views.d.ts +1 -22
- package/dist/src/core/contracts/views.d.ts.map +1 -1
- package/dist/src/core/devtools/hook.d.ts +66 -0
- package/dist/src/core/devtools/hook.d.ts.map +1 -0
- package/dist/src/core/devtools/hook.js +114 -0
- package/dist/src/core/devtools/hook.js.map +1 -0
- package/dist/src/core/devtools/index.d.ts +34 -0
- package/dist/src/core/devtools/index.d.ts.map +1 -0
- package/dist/src/core/devtools/index.js +33 -0
- package/dist/src/core/devtools/index.js.map +1 -0
- package/dist/src/core/devtools/inspector.d.ts +31 -0
- package/dist/src/core/devtools/inspector.d.ts.map +1 -0
- package/dist/src/core/devtools/inspector.js +249 -0
- package/dist/src/core/devtools/inspector.js.map +1 -0
- package/dist/src/core/elements/Block.d.ts +9 -7
- package/dist/src/core/elements/Block.d.ts.map +1 -1
- package/dist/src/core/elements/Block.js +33 -9
- package/dist/src/core/elements/Block.js.map +1 -1
- package/dist/src/core/elements/BlockOutlet.d.ts +19 -8
- package/dist/src/core/elements/BlockOutlet.d.ts.map +1 -1
- package/dist/src/core/elements/BlockOutlet.js +57 -13
- package/dist/src/core/elements/BlockOutlet.js.map +1 -1
- package/dist/src/core/elements/Component.d.ts +83 -11
- package/dist/src/core/elements/Component.d.ts.map +1 -1
- package/dist/src/core/elements/Component.js +334 -13
- package/dist/src/core/elements/Component.js.map +1 -1
- package/dist/src/core/elements/ForeachSlotCache.d.ts +95 -0
- package/dist/src/core/elements/ForeachSlotCache.d.ts.map +1 -0
- package/dist/src/core/elements/ForeachSlotCache.js +149 -0
- package/dist/src/core/elements/ForeachSlotCache.js.map +1 -0
- package/dist/src/core/elements/Fragment.d.ts +19 -13
- package/dist/src/core/elements/Fragment.d.ts.map +1 -1
- package/dist/src/core/elements/Fragment.js +43 -20
- package/dist/src/core/elements/Fragment.js.map +1 -1
- package/dist/src/core/elements/Html.d.ts +89 -12
- package/dist/src/core/elements/Html.d.ts.map +1 -1
- package/dist/src/core/elements/Html.js +707 -106
- package/dist/src/core/elements/Html.js.map +1 -1
- package/dist/src/core/elements/Output.d.ts +34 -10
- package/dist/src/core/elements/Output.d.ts.map +1 -1
- package/dist/src/core/elements/Output.js +141 -26
- package/dist/src/core/elements/Output.js.map +1 -1
- package/dist/src/core/elements/Reactive.d.ts +85 -14
- package/dist/src/core/elements/Reactive.d.ts.map +1 -1
- package/dist/src/core/elements/Reactive.js +315 -41
- package/dist/src/core/elements/Reactive.js.map +1 -1
- package/dist/src/core/elements/TextElement.d.ts +11 -7
- package/dist/src/core/elements/TextElement.d.ts.map +1 -1
- package/dist/src/core/elements/TextElement.js +18 -11
- package/dist/src/core/elements/TextElement.js.map +1 -1
- package/dist/src/core/elements/Wrapper.d.ts +24 -13
- package/dist/src/core/elements/Wrapper.d.ts.map +1 -1
- package/dist/src/core/elements/Wrapper.js +65 -8
- package/dist/src/core/elements/Wrapper.js.map +1 -1
- package/dist/src/core/elements/Yield.d.ts +10 -9
- package/dist/src/core/elements/Yield.d.ts.map +1 -1
- package/dist/src/core/elements/Yield.js +31 -19
- package/dist/src/core/elements/Yield.js.map +1 -1
- package/dist/src/core/elements/helpers.d.ts +7 -7
- package/dist/src/core/elements/helpers.d.ts.map +1 -1
- package/dist/src/core/elements/helpers.js +4 -4
- package/dist/src/core/elements/index.d.ts +12 -11
- package/dist/src/core/elements/index.d.ts.map +1 -1
- package/dist/src/core/elements/index.js +12 -11
- package/dist/src/core/elements/index.js.map +1 -1
- package/dist/src/core/elements/reconcileChildren.d.ts +7 -0
- package/dist/src/core/elements/reconcileChildren.d.ts.map +1 -0
- package/dist/src/core/elements/reconcileChildren.js +74 -0
- package/dist/src/core/elements/reconcileChildren.js.map +1 -0
- package/dist/src/core/hellpers/utils.d.ts +2 -2
- package/dist/src/core/hellpers/utils.js +4 -4
- package/dist/src/core/helpers/ApiClient.d.ts +58 -0
- package/dist/src/core/helpers/ApiClient.d.ts.map +1 -0
- package/dist/src/core/helpers/ApiClient.js +131 -0
- package/dist/src/core/helpers/ApiClient.js.map +1 -0
- package/dist/src/core/helpers/app.d.ts +3 -0
- package/dist/src/core/helpers/app.d.ts.map +1 -0
- package/dist/src/core/helpers/app.js +63 -0
- package/dist/src/core/helpers/app.js.map +1 -0
- package/dist/src/core/helpers/transition.d.ts +34 -0
- package/dist/src/core/helpers/transition.d.ts.map +1 -0
- package/dist/src/core/helpers/transition.js +118 -0
- package/dist/src/core/helpers/transition.js.map +1 -0
- package/dist/src/core/helpers/utils.d.ts +26 -0
- package/dist/src/core/helpers/utils.d.ts.map +1 -0
- package/dist/src/core/helpers/utils.js +96 -0
- package/dist/src/core/helpers/utils.js.map +1 -0
- package/dist/src/core/helpers/view.d.ts +39 -0
- package/dist/src/core/helpers/view.d.ts.map +1 -0
- package/dist/src/core/helpers/view.js +286 -0
- package/dist/src/core/helpers/view.js.map +1 -0
- package/dist/src/core/routers/Router.d.ts +108 -7
- package/dist/src/core/routers/Router.d.ts.map +1 -1
- package/dist/src/core/routers/Router.js +444 -68
- package/dist/src/core/routers/Router.js.map +1 -1
- package/dist/src/core/routers/index.d.ts +2 -2
- package/dist/src/core/routers/index.d.ts.map +1 -1
- package/dist/src/core/routers/index.js +1 -1
- package/dist/src/core/routers/index.js.map +1 -1
- package/dist/src/core/services/AssetManager.d.ts +112 -0
- package/dist/src/core/services/AssetManager.d.ts.map +1 -0
- package/dist/src/core/services/AssetManager.js +361 -0
- package/dist/src/core/services/AssetManager.js.map +1 -0
- package/dist/src/core/services/BlockManager.d.ts +73 -2
- package/dist/src/core/services/BlockManager.d.ts.map +1 -1
- package/dist/src/core/services/BlockManager.js +270 -48
- package/dist/src/core/services/BlockManager.js.map +1 -1
- package/dist/src/core/services/DomService.js +3 -3
- package/dist/src/core/services/DomService.js.map +1 -1
- package/dist/src/core/services/HeadService.d.ts +24 -0
- package/dist/src/core/services/HeadService.d.ts.map +1 -0
- package/dist/src/core/services/HeadService.js +120 -0
- package/dist/src/core/services/HeadService.js.map +1 -0
- package/dist/src/core/services/HelperService.d.ts +14 -3
- package/dist/src/core/services/HelperService.d.ts.map +1 -1
- package/dist/src/core/services/HelperService.js +32 -2
- package/dist/src/core/services/HelperService.js.map +1 -1
- package/dist/src/core/services/HttpService.d.ts +8 -0
- package/dist/src/core/services/HttpService.d.ts.map +1 -1
- package/dist/src/core/services/HttpService.js +121 -47
- package/dist/src/core/services/HttpService.js.map +1 -1
- package/dist/src/core/services/MarkerRegistry.d.ts +51 -8
- package/dist/src/core/services/MarkerRegistry.d.ts.map +1 -1
- package/dist/src/core/services/MarkerRegistry.js +135 -14
- package/dist/src/core/services/MarkerRegistry.js.map +1 -1
- package/dist/src/core/services/PageCache.d.ts +101 -0
- package/dist/src/core/services/PageCache.d.ts.map +1 -0
- package/dist/src/core/services/PageCache.js +184 -0
- package/dist/src/core/services/PageCache.js.map +1 -0
- package/dist/src/core/services/SectionManager.d.ts +100 -0
- package/dist/src/core/services/SectionManager.d.ts.map +1 -0
- package/dist/src/core/services/SectionManager.js +408 -0
- package/dist/src/core/services/SectionManager.js.map +1 -0
- package/dist/src/core/services/StorageService.js +1 -1
- package/dist/src/core/services/TranslationService.d.ts +21 -0
- package/dist/src/core/services/TranslationService.d.ts.map +1 -0
- package/dist/src/core/services/TranslationService.js +95 -0
- package/dist/src/core/services/TranslationService.js.map +1 -0
- package/dist/src/core/services/index.d.ts +11 -8
- package/dist/src/core/services/index.d.ts.map +1 -1
- package/dist/src/core/services/index.js +11 -8
- package/dist/src/core/services/index.js.map +1 -1
- package/dist/src/core/types/utils.d.ts +8 -8
- package/dist/src/core/types/utils.d.ts.map +1 -1
- package/dist/src/core/view/LoopContext.d.ts +21 -1
- package/dist/src/core/view/LoopContext.d.ts.map +1 -1
- package/dist/src/core/view/LoopContext.js +36 -0
- package/dist/src/core/view/LoopContext.js.map +1 -1
- package/dist/src/core/view/ResourceScope.d.ts +38 -0
- package/dist/src/core/view/ResourceScope.d.ts.map +1 -0
- package/dist/src/core/view/ResourceScope.js +167 -0
- package/dist/src/core/view/ResourceScope.js.map +1 -0
- package/dist/src/core/view/Section.d.ts +17 -32
- package/dist/src/core/view/Section.d.ts.map +1 -1
- package/dist/src/core/view/Section.js +14 -55
- package/dist/src/core/view/Section.js.map +1 -1
- package/dist/src/core/view/View.d.ts +43 -7
- package/dist/src/core/view/View.d.ts.map +1 -1
- package/dist/src/core/view/View.js +22 -4
- package/dist/src/core/view/View.js.map +1 -1
- package/dist/src/core/view/ViewController.d.ts +313 -40
- package/dist/src/core/view/ViewController.d.ts.map +1 -1
- package/dist/src/core/view/ViewController.js +1002 -147
- package/dist/src/core/view/ViewController.js.map +1 -1
- package/dist/src/core/view/ViewManager.d.ts +318 -27
- package/dist/src/core/view/ViewManager.d.ts.map +1 -1
- package/dist/src/core/view/ViewManager.js +1199 -258
- package/dist/src/core/view/ViewManager.js.map +1 -1
- package/dist/src/core/view/ViewState.d.ts +185 -6
- package/dist/src/core/view/ViewState.d.ts.map +1 -1
- package/dist/src/core/view/ViewState.js +851 -123
- package/dist/src/core/view/ViewState.js.map +1 -1
- package/dist/src/core/view/index.d.ts +5 -5
- package/dist/src/core/view/index.d.ts.map +1 -1
- package/dist/src/core/view/index.js +5 -5
- package/dist/src/core/view/index.js.map +1 -1
- package/dist/src/testing/index.d.ts +69 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +140 -0
- package/dist/src/testing/index.js.map +1 -0
- package/index.ts +29 -5
- package/package.json +30 -21
- package/plugins.ts +2 -2
- package/src/core/app/Application.ts +58 -31
- package/src/core/bootstrap/app.ts +80 -6
- package/src/core/bootstrap/bundle.ts +101 -0
- package/src/core/bootstrap/providers/ApiServiceProvider.ts +4 -4
- package/src/core/bootstrap/providers/CoreServiceProvider.ts +14 -8
- package/src/core/bootstrap/providers/HelperServiceProvider.ts +8 -3
- package/src/core/bootstrap/providers/RouteServiceProvider.ts +15 -4
- package/src/core/bootstrap/providers/ServiceProvider.ts +8 -8
- package/src/core/bootstrap/providers/ViewServiceProvider.ts +3 -3
- package/src/core/bootstrap/providers/default-providers.ts +51 -16
- package/src/core/bootstrap/providers/index.ts +9 -9
- package/src/core/bootstrap/providers/provider-order.test.ts +16 -27
- package/src/core/bootstrap/providers/provider-order.ts +1 -1
- package/src/core/bootstrap/ssr.ts +136 -0
- package/src/core/cache/ViewCache.ts +1 -1
- package/src/core/cache/index.ts +1 -1
- package/src/core/contracts/ApplicationInterface.ts +13 -11
- package/src/core/contracts/BlockInterface.ts +18 -18
- package/src/core/contracts/BundleInterface.ts +40 -0
- package/src/core/contracts/ComponentInterface.ts +7 -6
- package/src/core/contracts/ElementInterface.ts +95 -27
- package/src/core/contracts/HeadServiceInterface.ts +41 -0
- package/src/core/contracts/HelperInterface.ts +9 -1
- package/src/core/contracts/HttpServiceInterface.ts +3 -0
- package/src/core/contracts/MarkerInterface.ts +22 -49
- package/src/core/contracts/ReactiveInterface.ts +9 -9
- package/src/core/contracts/RouterInterface.ts +5 -2
- package/src/core/contracts/SectionInterface.ts +49 -0
- package/src/core/contracts/ServiceProviderInterface.ts +3 -3
- package/src/core/contracts/ViewControllerInterface.ts +124 -16
- package/src/core/contracts/ViewInterface.ts +34 -5
- package/src/core/contracts/ViewManagerInterface.ts +40 -5
- package/src/core/contracts/ViewStateInterface.ts +39 -2
- package/src/core/contracts/common.ts +3 -3
- package/src/core/contracts/utils.ts +19 -19
- package/src/core/contracts/views.ts +2 -22
- package/src/core/devtools/hook.ts +142 -0
- package/src/core/devtools/index.ts +38 -0
- package/src/core/devtools/inspector.ts +270 -0
- package/src/core/elements/Block.ts +43 -21
- package/src/core/elements/BlockOutlet.ts +61 -21
- package/src/core/elements/Component.ts +352 -18
- package/src/core/elements/ElementManager.ts +9 -9
- package/src/core/elements/ForeachSlotCache.ts +170 -0
- package/src/core/elements/Fragment.ts +50 -32
- package/src/core/elements/Html.ts +696 -117
- package/src/core/elements/Output.ts +147 -30
- package/src/core/elements/Reactive.ts +329 -52
- package/src/core/elements/TextElement.ts +25 -16
- package/src/core/elements/Wrapper.ts +84 -18
- package/src/core/elements/Yield.ts +35 -24
- package/src/core/elements/helpers.ts +12 -12
- package/src/core/elements/index.ts +25 -24
- package/src/core/elements/reconcileChildren.ts +70 -0
- package/src/core/{hellpers → helpers}/ApiClient.ts +8 -1
- package/src/core/{hellpers → helpers}/app.ts +13 -2
- package/src/core/helpers/transition.ts +127 -0
- package/src/core/{hellpers → helpers}/utils.ts +4 -4
- package/src/core/helpers/view.ts +283 -0
- package/src/core/routers/Router.ts +458 -64
- package/src/core/routers/index.ts +2 -2
- package/src/core/services/AssetManager.ts +387 -0
- package/src/core/services/BlockManager.ts +272 -50
- package/src/core/services/DomService.ts +3 -3
- package/src/core/services/HeadService.ts +124 -0
- package/src/core/services/HelperService.ts +34 -4
- package/src/core/services/HttpService.ts +116 -55
- package/src/core/services/MarkerRegistry.ts +142 -16
- package/src/core/services/PageCache.ts +239 -0
- package/src/core/services/SectionManager.ts +387 -0
- package/src/core/services/StorageService.ts +1 -1
- package/src/core/services/TranslationService.ts +91 -0
- package/src/core/services/index.ts +11 -8
- package/src/core/types/utils.ts +8 -8
- package/src/core/view/LoopContext.ts +38 -1
- package/src/core/view/ResourceScope.ts +174 -0
- package/src/core/view/Section.ts +21 -68
- package/src/core/view/View.ts +57 -8
- package/src/core/view/ViewController.ts +1070 -186
- package/src/core/view/ViewManager.ts +1346 -272
- package/src/core/view/ViewState.ts +834 -116
- package/src/core/view/index.ts +5 -5
- package/src/testing/index.ts +184 -0
- package/dist/src/core/services/MarkerService.d.ts +0 -299
- package/dist/src/core/services/MarkerService.d.ts.map +0 -1
- package/dist/src/core/services/MarkerService.js +0 -723
- package/dist/src/core/services/MarkerService.js.map +0 -1
- package/src/core/services/MarkerService.ts +0 -923
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { BlockInterface, BlockRenderFactory } from "../contracts/BlockInterface";
|
|
2
|
-
import { InitMode } from "../contracts/common";
|
|
3
|
-
import type { FragmentInterface, HtmlInterface } from "../contracts/ElementInterface";
|
|
4
|
-
import type { ReactiveInterface } from "../contracts/ReactiveInterface";
|
|
5
|
-
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
|
|
6
|
-
import { generateUUID } from "../
|
|
7
|
-
import markerRegistry from "../services/MarkerRegistry";
|
|
8
|
-
import type {
|
|
9
|
-
import { Fragment } from "./Fragment";
|
|
1
|
+
import type { BlockInterface, BlockRenderFactory } from "../contracts/BlockInterface.js";
|
|
2
|
+
import { InitMode, InitModes } from "../contracts/common.js";
|
|
3
|
+
import type { FragmentInterface, HtmlInterface } from "../contracts/ElementInterface.js";
|
|
4
|
+
import type { ReactiveInterface } from "../contracts/ReactiveInterface.js";
|
|
5
|
+
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
|
|
6
|
+
import { generateUUID } from "../helpers/utils.js";
|
|
7
|
+
import markerRegistry from "../services/MarkerRegistry.js";
|
|
8
|
+
import type { SaoObjectType } from "../types/utils.js";
|
|
9
|
+
import { Fragment } from "./Fragment.js";
|
|
10
10
|
/**
|
|
11
11
|
* Block — a named mounting slot used in layout views.
|
|
12
12
|
*
|
|
@@ -26,23 +26,24 @@ import { Fragment } from "./Fragment";
|
|
|
26
26
|
* 4. Browser back → BlockManager restores cached content without re-render
|
|
27
27
|
*/
|
|
28
28
|
export class Block implements BlockInterface {
|
|
29
|
-
|
|
29
|
+
saoType: SaoObjectType = 'Block';
|
|
30
30
|
id: string;
|
|
31
31
|
name: string;
|
|
32
32
|
ctx: ViewControllerInterface;
|
|
33
33
|
viewId: string | null = null;
|
|
34
|
+
/** Key trả về bởi markerRegistry.register — destroy() dùng để gỡ lại */
|
|
35
|
+
private markerKey: string | null = null;
|
|
34
36
|
fragment: FragmentInterface | null = null;
|
|
35
37
|
contentRenderFactory: BlockRenderFactory | null = null;
|
|
36
38
|
openTag: Comment;
|
|
37
39
|
closeTag: Comment;
|
|
38
|
-
|
|
39
40
|
domChildren: Node[] = [];
|
|
40
41
|
initMode?: InitMode | undefined;
|
|
41
42
|
parentElement: HtmlInterface | null = null;
|
|
42
43
|
|
|
43
44
|
public readonly isOneBlock = true;
|
|
44
|
-
public readonly
|
|
45
|
-
|
|
45
|
+
public readonly isSaoElement = false;
|
|
46
|
+
|
|
46
47
|
constructor({
|
|
47
48
|
ctx,
|
|
48
49
|
name,
|
|
@@ -58,15 +59,27 @@ export class Block implements BlockInterface {
|
|
|
58
59
|
id?: string | null;
|
|
59
60
|
initMode?: InitMode;
|
|
60
61
|
}) {
|
|
61
|
-
this.id =
|
|
62
|
+
this.id = id ?? generateUUID(10); // Unique ID for debugging
|
|
62
63
|
this.ctx = ctx;
|
|
63
64
|
this.name = name;
|
|
64
65
|
this.viewId = viewId ?? ctx.viewId; // Associate block with current viewId
|
|
65
66
|
this.initMode = initMode;
|
|
66
67
|
this.contentRenderFactory = contentRenderFactory || ((parentElement: HtmlInterface) => []);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
// Hydrate: claim cặp marker server qua index O(1) của MarkerRegistry.
|
|
69
|
+
// (Bản cũ dùng chung một TreeWalker không reset nên chỉ block ĐẦU TIÊN
|
|
70
|
+
// claim được, các block sau lặng lẽ tạo marker mới.)
|
|
71
|
+
const claimed = (this.initMode === InitModes.HYDRATE)
|
|
72
|
+
? markerRegistry.claim('block', this.id)
|
|
73
|
+
: null;
|
|
74
|
+
|
|
75
|
+
if (claimed) {
|
|
76
|
+
this.openTag = claimed.open;
|
|
77
|
+
this.closeTag = claimed.close;
|
|
78
|
+
} else {
|
|
79
|
+
this.openTag = markerRegistry.createMarkerStart('block', this.id);
|
|
80
|
+
this.closeTag = markerRegistry.createMarkerEnd('block', this.id);
|
|
81
|
+
this.markerKey = markerRegistry.register('block', this.id, { name, viewId }); // Register block in marker registry
|
|
82
|
+
}
|
|
70
83
|
|
|
71
84
|
}
|
|
72
85
|
|
|
@@ -90,7 +103,7 @@ export class Block implements BlockInterface {
|
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
mount(mountCtx: ViewControllerInterface, parentElement: HtmlInterface): any {
|
|
93
|
-
if(this.fragment) {
|
|
106
|
+
if (this.fragment) {
|
|
94
107
|
this.fragment.parent = parentElement;
|
|
95
108
|
this.fragment.render();
|
|
96
109
|
}
|
|
@@ -99,14 +112,23 @@ export class Block implements BlockInterface {
|
|
|
99
112
|
// Unmount logic (e.g. hide or remove DOM nodes, stop reactions)
|
|
100
113
|
}
|
|
101
114
|
destroy(): void {
|
|
102
|
-
//
|
|
115
|
+
// MarkerRegistry là singleton toàn cục — không gỡ thì record sống qua navigate.
|
|
116
|
+
if (this.markerKey) {
|
|
117
|
+
markerRegistry.remove(this.markerKey);
|
|
118
|
+
this.markerKey = null;
|
|
119
|
+
}
|
|
120
|
+
// openTag/closeTag nay NẰM TRONG DOM (BlockManager.mountBlockIntoOutlet chèn
|
|
121
|
+
// chúng quanh content). Bỏ lại comment mồ côi thì lần hydrate sau
|
|
122
|
+
// markerRegistry.claim('block', id) có thể bắt trúng cặp cũ và claim nhầm vùng.
|
|
123
|
+
this.openTag.remove();
|
|
124
|
+
this.closeTag.remove();
|
|
103
125
|
}
|
|
104
126
|
update(): void {
|
|
105
127
|
// Update logic (e.g. re-render content on state change)
|
|
106
128
|
}
|
|
107
129
|
|
|
108
|
-
|
|
109
|
-
|
|
130
|
+
/** Set the block's parent element (used for mounting) */
|
|
131
|
+
setParentElement(parentElement: HtmlInterface | null): void {
|
|
110
132
|
this.parentElement = parentElement;
|
|
111
133
|
}
|
|
112
134
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type { BlockOutletInterface } from "../contracts/BlockInterface";
|
|
2
|
-
import { InitMode } from "../contracts/common";
|
|
3
|
-
import type { HtmlInterface } from "../contracts/ElementInterface";
|
|
4
|
-
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
|
|
5
|
-
import { generateUUID } from "../
|
|
6
|
-
import markerRegistry
|
|
7
|
-
import type {
|
|
1
|
+
import type { BlockOutletInterface } from "../contracts/BlockInterface.js";
|
|
2
|
+
import { InitMode, InitModes } from "../contracts/common.js";
|
|
3
|
+
import type { HtmlInterface } from "../contracts/ElementInterface.js";
|
|
4
|
+
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
|
|
5
|
+
import { generateUUID } from "../helpers/utils.js";
|
|
6
|
+
import markerRegistry from "../services/MarkerRegistry.js";
|
|
7
|
+
import type { SaoObjectType } from "../types/utils.js";
|
|
8
8
|
|
|
9
9
|
export class BlockOutlet implements BlockOutletInterface {
|
|
10
|
-
|
|
10
|
+
saoType: SaoObjectType = 'BlockOutlet';
|
|
11
11
|
id: string;
|
|
12
12
|
name: string;
|
|
13
13
|
openTag: Comment;
|
|
@@ -15,38 +15,78 @@ export class BlockOutlet implements BlockOutletInterface {
|
|
|
15
15
|
parent: HtmlInterface | null = null;
|
|
16
16
|
parentElement: HtmlInterface | null = null;
|
|
17
17
|
ctx: ViewControllerInterface;
|
|
18
|
-
initMode: InitMode =
|
|
19
|
-
constructor({ ctx, parentElement = null, name, id = null, initMode =
|
|
20
|
-
this.id =
|
|
18
|
+
initMode: InitMode = InitModes.CREATE;
|
|
19
|
+
constructor({ ctx, parentElement = null, name, id = null, initMode = InitModes.CREATE }: { ctx: ViewControllerInterface, parentElement?: HtmlInterface | null, name: string, id?: string | null, initMode?: InitMode }) {
|
|
20
|
+
this.id = id ?? generateUUID(10); // Unique ID for debugging
|
|
21
21
|
this.ctx = ctx;
|
|
22
22
|
this.name = name;
|
|
23
23
|
this.parent = parentElement;
|
|
24
24
|
this.parentElement = parentElement;
|
|
25
25
|
this.initMode = initMode;
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
if (this.initMode === InitModes.HYDRATE) {
|
|
28
|
+
// Claim cặp marker server <!--s:bo:{id}-s--> ... <!--s:bo:{id}-e-->
|
|
29
|
+
const claimed = this.claimSSRMarkers();
|
|
30
|
+
if (claimed) {
|
|
31
|
+
this.openTag = claimed.open;
|
|
32
|
+
this.closeTag = claimed.close;
|
|
33
|
+
} else {
|
|
34
|
+
this.openTag = markerRegistry.createMarkerStart('blockoutlet', this.id);
|
|
35
|
+
this.closeTag = markerRegistry.createMarkerEnd('blockoutlet', this.id);
|
|
36
|
+
this.markerKey = markerRegistry.register('blockoutlet', this.id, { name, viewId: ctx.viewId });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.openTag = markerRegistry.createMarkerStart('blockoutlet', this.id);
|
|
41
|
+
this.closeTag = markerRegistry.createMarkerEnd('blockoutlet', this.id);
|
|
42
|
+
this.markerKey = markerRegistry.register('blockoutlet', this.id, { name, viewId: ctx.viewId }); // Register this outlet in the MarkerRegistry
|
|
43
|
+
}
|
|
28
44
|
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tìm cặp marker outlet từ server-rendered HTML (format chuẩn §5.1):
|
|
49
|
+
* open: s:bo:{id}-s close: s:bo:{id}-e
|
|
50
|
+
* Tra index của MarkerRegistry, chặn trong parentElement nếu có.
|
|
51
|
+
*/
|
|
52
|
+
private claimSSRMarkers(): { open: Comment; close: Comment } | null {
|
|
53
|
+
return markerRegistry.claim('blockoutlet', this.id, this.parentElement?.element ?? null);
|
|
31
54
|
}
|
|
32
55
|
|
|
33
56
|
hydrate(): void {
|
|
34
57
|
// Hydration logic if needed (e.g. reattach event listeners)
|
|
35
58
|
}
|
|
36
59
|
|
|
60
|
+
/** Registry guard */
|
|
61
|
+
public __destroyed__: boolean = false;
|
|
62
|
+
/** Key trả về bởi markerRegistry.register — destroy() dùng để gỡ lại */
|
|
63
|
+
private markerKey: string | null = null;
|
|
64
|
+
|
|
65
|
+
/** Render — idempotent: markers đã trong DOM thì giữ nguyên (same-layout reuse) */
|
|
37
66
|
render(): void {
|
|
38
|
-
if (
|
|
67
|
+
if (this.__destroyed__) return;
|
|
68
|
+
if (this.openTag.parentNode) return; // đã đặt — không đặt lại
|
|
39
69
|
|
|
70
|
+
if (!this.parentElement || !this.parentElement.element) return;
|
|
40
71
|
const parentEl = this.parentElement.element;
|
|
41
|
-
|
|
42
|
-
// Place markers
|
|
43
72
|
parentEl.appendChild(this.openTag);
|
|
44
|
-
// Register this outlet in the MarkerRegistry for block mounting
|
|
45
73
|
parentEl.appendChild(this.closeTag);
|
|
46
|
-
|
|
47
74
|
}
|
|
48
75
|
|
|
49
76
|
destroy(): void {
|
|
77
|
+
this.__destroyed__ = true;
|
|
78
|
+
this.ctx.releaseElement?.(this);
|
|
79
|
+
if (this.markerKey) {
|
|
80
|
+
markerRegistry.remove(this.markerKey);
|
|
81
|
+
this.markerKey = null;
|
|
82
|
+
}
|
|
83
|
+
// Clear nội dung giữa markers (block content nếu còn)
|
|
84
|
+
let current: Node | null = this.openTag.nextSibling;
|
|
85
|
+
while (current && current !== this.closeTag) {
|
|
86
|
+
const next: Node | null = current.nextSibling;
|
|
87
|
+
(current as ChildNode).remove();
|
|
88
|
+
current = next;
|
|
89
|
+
}
|
|
50
90
|
// Remove markers from DOM
|
|
51
91
|
this.openTag.remove();
|
|
52
92
|
this.closeTag.remove();
|
|
@@ -65,11 +105,11 @@ export class BlockOutlet implements BlockOutletInterface {
|
|
|
65
105
|
this.parentElement = parentElement;
|
|
66
106
|
}
|
|
67
107
|
|
|
68
|
-
get
|
|
108
|
+
get isSaoElement(): boolean {
|
|
69
109
|
return true;
|
|
70
110
|
}
|
|
71
111
|
|
|
72
|
-
set
|
|
112
|
+
set isSaoElement(value: boolean) {
|
|
73
113
|
// No-op setter to satisfy OneElement interface
|
|
74
114
|
}
|
|
75
115
|
|
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { InitMode, InitModes } from "../contracts/common";
|
|
2
|
-
import { ComponentInterface } from "../contracts/ComponentInterface";
|
|
3
|
-
import { HtmlInterface,
|
|
4
|
-
import { ViewControllerInterface } from "../contracts/ViewControllerInterface";
|
|
5
|
-
import { ViewInterface } from "../contracts/ViewInterface";
|
|
6
|
-
import { generateUUID } from "../
|
|
7
|
-
import {
|
|
1
|
+
import { InitMode, InitModes } from "../contracts/common.js";
|
|
2
|
+
import { ComponentInterface } from "../contracts/ComponentInterface.js";
|
|
3
|
+
import { HtmlInterface, SaoChildrenFactory } from "../contracts/ElementInterface.js";
|
|
4
|
+
import { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
|
|
5
|
+
import { ViewInterface } from "../contracts/ViewInterface.js";
|
|
6
|
+
import { generateUUID } from "../helpers/utils.js";
|
|
7
|
+
import { activateView, claimHydratedView, commitView, mountChildrenBeforeAnchor } from "../helpers/view.js";
|
|
8
|
+
import markerRegistry from "../services/MarkerRegistry.js";
|
|
9
|
+
import { SaoObjectType } from "../types/utils.js";
|
|
8
10
|
|
|
9
11
|
export class Component implements ComponentInterface {
|
|
10
|
-
|
|
12
|
+
saoType: SaoObjectType = 'Component';
|
|
11
13
|
ctx: ViewControllerInterface;
|
|
12
14
|
parent: HtmlInterface | null;
|
|
13
15
|
domChildren: Node[] = []; // For compatibility with HtmlInterface; Component itself doesn't have a single root element
|
|
@@ -23,6 +25,18 @@ export class Component implements ComponentInterface {
|
|
|
23
25
|
initMode: InitMode = InitModes.CREATE; // Default initialization mode
|
|
24
26
|
subscribeFn: () => void = () => {};
|
|
25
27
|
unsubscribeFn: () => void = () => {};
|
|
28
|
+
/**
|
|
29
|
+
* Listener khai báo tại thẻ cha: `<mycomp @edit(openEditor(event))>`.
|
|
30
|
+
*
|
|
31
|
+
* Con gọi `emit('edit', payload)` → ViewController.emit tra Ở ĐÂY, không đi
|
|
32
|
+
* qua event bus: không rò sang instance khác, không phải gỡ đăng ký.
|
|
33
|
+
*
|
|
34
|
+
* Đọc lúc phát chứ không copy vào data con: mỗi lần cha render lại,
|
|
35
|
+
* `ViewController.include()` thay bảng này bằng closure mới, nên handler
|
|
36
|
+
* luôn nhìn thấy biến vòng lặp / prop của LƯỢT RENDER hiện tại — kể cả khi
|
|
37
|
+
* component không có prop reactive nào để kích hoạt updateData.
|
|
38
|
+
*/
|
|
39
|
+
listeners: Record<string, (...args: any[]) => any> = {};
|
|
26
40
|
dataFactory: ((parentElement: HtmlInterface | null) => Record<string, any>) | null = null;
|
|
27
41
|
|
|
28
42
|
constructor({
|
|
@@ -36,6 +50,7 @@ export class Component implements ComponentInterface {
|
|
|
36
50
|
type = 'default',
|
|
37
51
|
condition = null,
|
|
38
52
|
initMode = InitModes.CREATE,
|
|
53
|
+
listeners = {},
|
|
39
54
|
}: {
|
|
40
55
|
ctx: ViewControllerInterface;
|
|
41
56
|
parent?: HtmlInterface | null;
|
|
@@ -47,19 +62,46 @@ export class Component implements ComponentInterface {
|
|
|
47
62
|
type?: 'default' | 'if' | 'when';
|
|
48
63
|
condition?: {stateKeys: string[], checker: () => any} | null;
|
|
49
64
|
initMode?: InitMode;
|
|
65
|
+
listeners?: Record<string, (...args: any[]) => any>;
|
|
50
66
|
}) {
|
|
51
67
|
this.ctx = ctx;
|
|
52
68
|
this.parent = parent;
|
|
53
69
|
this.stateKeys = stateKeys;
|
|
54
70
|
this.data = data || {};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
this.id = `${ctx.viewId}-${id ?? generateUUID(10)}`;
|
|
71
|
+
// id khớp server: Blade emit @startMarker('component', '{hash}') →
|
|
72
|
+
// <!--s:c:{parentViewId}-{hash}-s--> (MarkerRegistryDirectiveService)
|
|
73
|
+
this.id = `${ctx.viewId}-${id ?? generateUUID(10)}`;
|
|
58
74
|
this.dataFactory = dataFactory;
|
|
59
75
|
this.path = path;
|
|
60
76
|
this.type = type;
|
|
61
77
|
this.condition = condition;
|
|
78
|
+
this.listeners = listeners || {};
|
|
62
79
|
this.initMode = initMode ?? InitModes.CREATE;
|
|
80
|
+
|
|
81
|
+
if (this.initMode === InitModes.HYDRATE) {
|
|
82
|
+
// ── Hydrate: claim cặp marker component server đã render ─────
|
|
83
|
+
const claimed = this.claimSSRMarkers();
|
|
84
|
+
if (claimed) {
|
|
85
|
+
this.openTag = claimed.open;
|
|
86
|
+
this.closeTag = claimed.close;
|
|
87
|
+
} else {
|
|
88
|
+
// Partial hydration fallback (server không render vùng này)
|
|
89
|
+
this.openTag = markerRegistry.createMarkerStart('component', this.id);
|
|
90
|
+
this.closeTag = markerRegistry.createMarkerEnd('component', this.id);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
// Format chuẩn s:c:{id}-s/-e — PHẢI khớp server để SSR/CSR cùng kết quả
|
|
94
|
+
this.openTag = markerRegistry.createMarkerStart('component', this.id);
|
|
95
|
+
this.closeTag = markerRegistry.createMarkerEnd('component', this.id);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Tìm cặp marker component từ server-rendered HTML (format chuẩn §5.1):
|
|
101
|
+
* open: s:c:{id}-s close: s:c:{id}-e
|
|
102
|
+
*/
|
|
103
|
+
private claimSSRMarkers(): { open: Comment; close: Comment } | null {
|
|
104
|
+
return markerRegistry.claim('component', this.id, this.parent?.element ?? null);
|
|
63
105
|
}
|
|
64
106
|
|
|
65
107
|
mergeData(newData: Record<string, any>): void {
|
|
@@ -75,6 +117,9 @@ export class Component implements ComponentInterface {
|
|
|
75
117
|
setStateKeys(stateKeys: string[]) {
|
|
76
118
|
this.stateKeys = stateKeys;
|
|
77
119
|
}
|
|
120
|
+
setListeners(listeners: Record<string, (...args: any[]) => any>) {
|
|
121
|
+
this.listeners = listeners || {};
|
|
122
|
+
}
|
|
78
123
|
setParentElement(parent: HtmlInterface | null): void {
|
|
79
124
|
this.parent = parent;
|
|
80
125
|
}
|
|
@@ -86,23 +131,312 @@ export class Component implements ComponentInterface {
|
|
|
86
131
|
this.parent = parent;
|
|
87
132
|
}
|
|
88
133
|
|
|
134
|
+
/** Registry guard */
|
|
135
|
+
public __destroyed__: boolean = false;
|
|
136
|
+
private _isStarted = false;
|
|
137
|
+
private unsubscribeData: (() => void) | null = null;
|
|
138
|
+
private unsubscribeCondition: (() => void) | null = null;
|
|
139
|
+
/** 'when' type: trạng thái mounted hiện tại của child */
|
|
140
|
+
private _childMounted = false;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Render — @include (RUNTIME_CONTRACT.md §2):
|
|
144
|
+
* 1. Đặt component markers (idempotent — caller có thể đã đặt đúng vị trí)
|
|
145
|
+
* 2. Resolve child view từ registry (App.View)
|
|
146
|
+
* 3. Render child wrapper GIỮA markers, liên kết parent ↔ child
|
|
147
|
+
* 4. commitData cho child (start sẽ do lifecycle cascade gọi)
|
|
148
|
+
*
|
|
149
|
+
* Hydrate mode (markers claim được từ SSR): child view được tạo với đúng
|
|
150
|
+
* viewId server đã dùng (discover từ marker view bên trong) → toàn bộ cây
|
|
151
|
+
* con CLAIM DOM server thay vì tạo mới — SSR/CSR cho cùng kết quả.
|
|
152
|
+
*/
|
|
153
|
+
render(): void {
|
|
154
|
+
if (this.__destroyed__) return;
|
|
155
|
+
|
|
156
|
+
// ── Hydrate path: markers đã có trong SSR DOM ──────────────────
|
|
157
|
+
if (this.initMode === InitModes.HYDRATE) {
|
|
158
|
+
if (this.openTag.parentNode) {
|
|
159
|
+
// Điều kiện đánh giá với state ĐÃ commit (hydrateView commit trước
|
|
160
|
+
// render) → kết quả khớp server-rendered output.
|
|
161
|
+
if (this.type === 'when' && this.condition && !this.condition.checker()) {
|
|
162
|
+
this.initMode = InitModes.CREATE;
|
|
163
|
+
return; // server cũng không render child (điều kiện falsy)
|
|
164
|
+
}
|
|
165
|
+
if (this.type === 'if') {
|
|
166
|
+
const viewManager = (this.ctx as any).App?.View;
|
|
167
|
+
if (!viewManager?.exists?.(this.path ?? '')) {
|
|
168
|
+
this.initMode = InitModes.CREATE;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.guardChildMount(() => this.hydrateChild());
|
|
173
|
+
this.initMode = InitModes.CREATE; // re-render sau này dùng CSR flow
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Partial hydration fallback: server không render component này → CSR
|
|
177
|
+
this.initMode = InitModes.CREATE;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 1. Markers
|
|
181
|
+
if (!this.openTag.parentNode) {
|
|
182
|
+
const parentEl = this.parent?.element;
|
|
183
|
+
if (!parentEl) return;
|
|
184
|
+
parentEl.appendChild(this.openTag);
|
|
185
|
+
parentEl.appendChild(this.closeTag);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 2. Điều kiện theo type
|
|
189
|
+
if (this.type === 'when' && this.condition) {
|
|
190
|
+
const shouldMount = !!this.condition.checker();
|
|
191
|
+
if (!shouldMount) {
|
|
192
|
+
this.unmountChild();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (this.type === 'if') {
|
|
197
|
+
const viewManager = (this.ctx as any).App?.View;
|
|
198
|
+
if (!viewManager?.exists?.(this.path ?? '')) {
|
|
199
|
+
return; // @includeIf: view không tồn tại → bỏ qua, không lỗi
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.guardChildMount(() => this.mountChild());
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Error boundary cho subtree con (@include). Boundary được tìm từ ctx —
|
|
208
|
+
* controller CHỨA @include này, không phải view con — nên onError của một
|
|
209
|
+
* view không bắt lỗi render của chính nó (giống React ErrorBoundary).
|
|
210
|
+
* Không boundary nào xử lý → rethrow, giữ nguyên hành vi cũ (bubble lên
|
|
211
|
+
* try/catch của renderPageView).
|
|
212
|
+
*/
|
|
213
|
+
private guardChildMount(mount: () => void): void {
|
|
214
|
+
try {
|
|
215
|
+
mount();
|
|
216
|
+
} catch (err) {
|
|
217
|
+
const result = this.ctx.handleError(err, { phase: 'render', path: this.path ?? '' });
|
|
218
|
+
if (!result.handled) throw err;
|
|
219
|
+
this.mountFallback(result.fallback);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Dọn DOM dở dang của lần render lỗi rồi chèn fallback giữa cặp marker. */
|
|
224
|
+
private mountFallback(fallback: any): void {
|
|
225
|
+
let current: Node | null = this.openTag.nextSibling;
|
|
226
|
+
while (current && current !== this.closeTag) {
|
|
227
|
+
const next: Node | null = current.nextSibling;
|
|
228
|
+
current.parentNode?.removeChild(current);
|
|
229
|
+
current = next;
|
|
230
|
+
}
|
|
231
|
+
if (fallback === null || fallback === undefined) return;
|
|
232
|
+
mountChildrenBeforeAnchor(
|
|
233
|
+
this.closeTag,
|
|
234
|
+
Array.isArray(fallback) ? fallback : [fallback],
|
|
235
|
+
this.parent,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Discover viewId server đã dùng cho child view: quét comment giữa cặp
|
|
241
|
+
* marker component, tìm marker view mở đầu tiên <!--s:v:{id}-s-->.
|
|
242
|
+
*/
|
|
243
|
+
private discoverChildViewId(): string | null {
|
|
244
|
+
let current: Node | null = this.openTag.nextSibling;
|
|
245
|
+
while (current && current !== this.closeTag) {
|
|
246
|
+
if (current.nodeType === Node.COMMENT_NODE) {
|
|
247
|
+
const parsed = markerRegistry.parseComment(current.nodeValue ?? '');
|
|
248
|
+
if (parsed && parsed.tag === 'view' && !parsed.isClose) return parsed.id;
|
|
249
|
+
} else if (current.nodeType === Node.ELEMENT_NODE) {
|
|
250
|
+
const walker = document.createTreeWalker(current, NodeFilter.SHOW_COMMENT);
|
|
251
|
+
let node: Comment | null;
|
|
252
|
+
while ((node = walker.nextNode() as Comment | null)) {
|
|
253
|
+
const parsed = markerRegistry.parseComment(node.nodeValue ?? '');
|
|
254
|
+
if (parsed && parsed.tag === 'view' && !parsed.isClose) return parsed.id;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
current = current.nextSibling;
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Hydrate child view — thứ tự chuẩn hydration (như ViewManager.hydrateView):
|
|
264
|
+
* discover viewId → tạo instance → commit state → flush discard →
|
|
265
|
+
* render claim DOM → mount() (hook + asset). KHÔNG chèn node mới.
|
|
266
|
+
*/
|
|
267
|
+
private hydrateChild(): void {
|
|
268
|
+
if (this._childMounted && this.viewRef) return;
|
|
269
|
+
|
|
270
|
+
// Không có marker view của server giữa cặp marker component → server
|
|
271
|
+
// KHÔNG render view con ở đây. Hydrate tiếp thì child giữ viewId do
|
|
272
|
+
// client sinh ('c…'), rồi đi tìm class "{c…}-{id}" và marker
|
|
273
|
+
// "s:v:c…" không tồn tại → dựng cây mới CẠNH cây server, tức nhân đôi
|
|
274
|
+
// DOM. Dọn phần server bỏ lại rồi đi đường CSR — giống nhánh
|
|
275
|
+
// "partial hydration fallback" ở render() khi marker component vắng.
|
|
276
|
+
const ssrViewId = this.discoverChildViewId();
|
|
277
|
+
if (!ssrViewId) {
|
|
278
|
+
this.unmountChild();
|
|
279
|
+
this.mountChild();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const childView = this.resolveChildView();
|
|
284
|
+
if (!childView) return;
|
|
285
|
+
const childCtrl = childView.__ctrl__;
|
|
286
|
+
|
|
287
|
+
// Ghi đè viewId = viewId server (trước render — Wrapper/Html/Output của
|
|
288
|
+
// child claim theo marker/class prefix bằng viewId này)
|
|
289
|
+
childCtrl.viewId = ssrViewId;
|
|
290
|
+
|
|
291
|
+
// Commit state TRƯỚC render (factory @if/@foreach sinh đúng cây khớp SSR);
|
|
292
|
+
// flush ngay khi chưa subscribe → discard pending, không phá DOM claim.
|
|
293
|
+
childCtrl.initMode = InitModes.HYDRATE;
|
|
294
|
+
commitView(childCtrl, true);
|
|
295
|
+
|
|
296
|
+
const wrapper = childCtrl.render();
|
|
297
|
+
if (wrapper && typeof wrapper === 'object' && 'openTag' in wrapper && this.parent) {
|
|
298
|
+
claimHydratedView(childCtrl, this.parent, wrapper as any);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
childCtrl.initMode = InitModes.CREATE;
|
|
302
|
+
this.finishChildMount(childCtrl);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Tạo + mount child view giữa markers (nếu chưa có) */
|
|
306
|
+
private mountChild(): void {
|
|
307
|
+
if (this._childMounted && this.viewRef) {
|
|
308
|
+
if (this.dataFactory) {
|
|
309
|
+
const ctrl = this.viewRef.__ctrl__;
|
|
310
|
+
ctrl.updateData(this.dataFactory(this.parent));
|
|
311
|
+
ctrl.states.__.flushNow();
|
|
312
|
+
}
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const childView = this.resolveChildView();
|
|
316
|
+
if (!childView) return;
|
|
317
|
+
const childCtrl = childView.__ctrl__;
|
|
318
|
+
|
|
319
|
+
// Render child tree giữa markers
|
|
320
|
+
const wrapper = childCtrl.render();
|
|
321
|
+
if (wrapper && typeof wrapper === 'object' && 'openTag' in wrapper) {
|
|
322
|
+
const insertBeforeClose = (node: Node) => {
|
|
323
|
+
this.closeTag.parentNode?.insertBefore(node, this.closeTag);
|
|
324
|
+
};
|
|
325
|
+
insertBeforeClose(wrapper.openTag);
|
|
326
|
+
insertBeforeClose(wrapper.closeTag);
|
|
327
|
+
// Wrapper render idempotent qua mountChildrenBeforeAnchor
|
|
328
|
+
mountChildrenBeforeAnchor(wrapper.closeTag, wrapper.render(), this.parent);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// mount(): nội dung child đã nằm giữa markers → fire mounting/mounted + acquire asset
|
|
332
|
+
childCtrl.mount();
|
|
333
|
+
commitView(childCtrl);
|
|
334
|
+
this.markChildMounted(childCtrl);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Resolve one child instance and establish ownership before either DOM strategy. */
|
|
338
|
+
private resolveChildView(): ViewInterface | null {
|
|
339
|
+
const viewManager = (this.ctx as any).App?.View;
|
|
340
|
+
if (!viewManager || !this.path) {
|
|
341
|
+
console.error(`[Component] Không resolve được view "${this.path}" — App.View chưa sẵn sàng.`);
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const data = this.dataFactory ? this.dataFactory(this.parent) : this.data;
|
|
346
|
+
// Sync: render tree không await được (xem ViewManager.resolveViewSync).
|
|
347
|
+
const childView = viewManager.resolveViewSync(this.path, data ?? {}, false);
|
|
348
|
+
if (!childView) {
|
|
349
|
+
console.error(`[Component] View "${this.path}" không tồn tại trong registry.`);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this.viewRef = childView;
|
|
354
|
+
const childCtrl = childView.__ctrl__;
|
|
355
|
+
childCtrl.ownerComponent = this;
|
|
356
|
+
childCtrl.setParent(this.ctx);
|
|
357
|
+
this.ctx.addChild(childCtrl);
|
|
358
|
+
childCtrl.setParentElement(this.parent);
|
|
359
|
+
return childView;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** Hydration mounts after commit/claim; CSR commits immediately after mount. */
|
|
363
|
+
private finishChildMount(childCtrl: ViewControllerInterface): void {
|
|
364
|
+
childCtrl.mount();
|
|
365
|
+
this.markChildMounted(childCtrl);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private markChildMounted(childCtrl: ViewControllerInterface): void {
|
|
369
|
+
this._childMounted = true;
|
|
370
|
+
if (this._isStarted) activateView(childCtrl);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Gỡ child (when=false hoặc destroy) */
|
|
374
|
+
private unmountChild(): void {
|
|
375
|
+
if (this.viewRef) {
|
|
376
|
+
this.viewRef.__ctrl__.destroy();
|
|
377
|
+
this.viewRef = null;
|
|
378
|
+
}
|
|
379
|
+
// Clear mọi node giữa markers
|
|
380
|
+
let current: Node | null = this.openTag.nextSibling;
|
|
381
|
+
while (current && current !== this.closeTag) {
|
|
382
|
+
const next: Node | null = current.nextSibling;
|
|
383
|
+
(current as ChildNode).remove();
|
|
384
|
+
current = next;
|
|
385
|
+
}
|
|
386
|
+
this._childMounted = false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Start — kích hoạt child + subscribe:
|
|
391
|
+
* - stateKeys: props reactive — đổi → dataFactory mới → child.updateData()
|
|
392
|
+
* - condition (type 'when'): đổi → mount/unmount child
|
|
393
|
+
*/
|
|
89
394
|
start(): void {
|
|
395
|
+
if (this._isStarted || this.__destroyed__) return;
|
|
396
|
+
this._isStarted = true;
|
|
397
|
+
|
|
398
|
+
if (this.viewRef) {
|
|
399
|
+
activateView(this.viewRef.__ctrl__);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (this.stateKeys.length > 0) {
|
|
403
|
+
this.unsubscribeData = this.ctx.states.__.subscribe(this.stateKeys, () => {
|
|
404
|
+
if (this.viewRef && this.dataFactory) {
|
|
405
|
+
this.viewRef.__ctrl__.updateData(this.dataFactory(this.parent));
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (this.type === 'when' && this.condition && this.condition.stateKeys.length > 0) {
|
|
411
|
+
this.unsubscribeCondition = this.ctx.states.__.subscribe(this.condition.stateKeys, () => {
|
|
412
|
+
this.render(); // re-evaluate checker → mount/unmount
|
|
413
|
+
});
|
|
414
|
+
}
|
|
90
415
|
}
|
|
91
416
|
|
|
92
417
|
stop(): void {
|
|
93
|
-
|
|
418
|
+
if (!this._isStarted) return;
|
|
419
|
+
this._isStarted = false;
|
|
94
420
|
|
|
95
|
-
|
|
96
|
-
|
|
421
|
+
if (this.unsubscribeData) { this.unsubscribeData(); this.unsubscribeData = null; }
|
|
422
|
+
if (this.unsubscribeCondition) { this.unsubscribeCondition(); this.unsubscribeCondition = null; }
|
|
423
|
+
this.viewRef?.__ctrl__.stop();
|
|
97
424
|
}
|
|
98
425
|
|
|
99
426
|
destroy(): void {
|
|
100
|
-
|
|
427
|
+
if (this.__destroyed__) return;
|
|
428
|
+
this.stop();
|
|
429
|
+
this.__destroyed__ = true;
|
|
430
|
+
this.ctx.releaseElement?.(this);
|
|
431
|
+
this.unmountChild();
|
|
432
|
+
this.openTag.remove();
|
|
433
|
+
this.closeTag.remove();
|
|
434
|
+
this.parent = null;
|
|
101
435
|
}
|
|
102
|
-
get
|
|
436
|
+
get isSaoElement(): boolean {
|
|
103
437
|
return true;
|
|
104
438
|
}
|
|
105
|
-
set
|
|
439
|
+
set isSaoElement(value: boolean) {
|
|
106
440
|
// No-op, just for type compatibility
|
|
107
441
|
}
|
|
108
442
|
get isOneComponent(): boolean {
|
|
@@ -111,4 +445,4 @@ export class Component implements ComponentInterface {
|
|
|
111
445
|
set isOneComponent(value: boolean) {
|
|
112
446
|
// No-op, just for type compatibility
|
|
113
447
|
}
|
|
114
|
-
}
|
|
448
|
+
}
|