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