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