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