@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,10 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
1
|
+
import { reconcileChildren } from './reconcileChildren.js';
|
|
2
|
+
import { InitMode, InitModes } from "../contracts/common.js";
|
|
3
|
+
import type { HtmlInterface, SaoChildrenFactoryOutput, SaoElementChildren } from "../contracts/ElementInterface.js";
|
|
4
|
+
import type { ReactiveInterface, ReactiveChildrenFactory, ReactiveRenderFn } from "../contracts/ReactiveInterface.js";
|
|
5
|
+
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
|
|
6
|
+
import { generateUUID } from "../helpers/utils.js";
|
|
7
|
+
import { mountChildrenBeforeAnchor } from "../helpers/view.js";
|
|
8
|
+
import markerRegistry from "../services/MarkerRegistry.js";
|
|
9
|
+
import type { SaoObjectType } from "../types/utils.js";
|
|
10
|
+
import { ForeachSlotCache } from "./ForeachSlotCache.js";
|
|
11
|
+
import { isLeaving } from "../helpers/transition.js";
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Reactive — a region in the DOM bounded by comment markers that
|
|
@@ -22,27 +26,38 @@ import type { OneObjectType } from "../types/utils";
|
|
|
22
26
|
*/
|
|
23
27
|
export class Reactive implements ReactiveInterface {
|
|
24
28
|
static class = 'Reactive';
|
|
25
|
-
|
|
29
|
+
saoType: SaoObjectType = 'Reactive';
|
|
26
30
|
public id: string; // Unique ID for debugging and marker registry
|
|
27
31
|
public type: string; // Custom type for different reactive behaviors (e.g. 'if', 'list')
|
|
28
32
|
public openTag: Comment;
|
|
29
33
|
public closeTag: Comment;
|
|
30
34
|
public parentElement: HtmlInterface | null;
|
|
31
35
|
public parentReactive: ReactiveInterface | null;
|
|
32
|
-
public parent: HtmlInterface | null = null; // Alias for parentReactive to satisfy
|
|
36
|
+
public parent: HtmlInterface | null = null; // Alias for parentReactive to satisfy SaoNodeInterface
|
|
33
37
|
|
|
34
38
|
public ctx: ViewControllerInterface;
|
|
35
39
|
public childrenFactory: ReactiveChildrenFactory;
|
|
36
|
-
public children:
|
|
40
|
+
public children: SaoElementChildren = [];
|
|
37
41
|
private mounted: boolean = false;
|
|
38
42
|
public stateKeys: string[];
|
|
39
|
-
public unsubscribe: () => void = () => {};
|
|
43
|
+
public unsubscribe: () => void = () => { };
|
|
40
44
|
private _isStarted = false;
|
|
45
|
+
/** Marker model (hydration) — gán bởi BlockManager/SSR khi cần; mặc định null. */
|
|
46
|
+
/** Key trả về bởi markerRegistry.register — destroy() dùng để gỡ lại */
|
|
47
|
+
private markerKey: string | null = null;
|
|
41
48
|
|
|
42
49
|
|
|
43
50
|
|
|
44
51
|
domChildren: Node[] = []; // For compatibility with HtmlInterface; Reactive itself doesn't have a single root element
|
|
45
52
|
initMode: InitMode = InitModes.CREATE;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Slot cache cho @foreach reconciliation (Phase 5).
|
|
56
|
+
* Được tạo khi type === 'foreach', null cho tất cả các type khác.
|
|
57
|
+
* Cache maps item ref → Saola elements — cho phép reuse elements
|
|
58
|
+
* khi item object reference không thay đổi.
|
|
59
|
+
*/
|
|
60
|
+
private _foreachCache: ForeachSlotCache | null = null;
|
|
46
61
|
constructor({
|
|
47
62
|
type = 'reactive',
|
|
48
63
|
id = null,
|
|
@@ -65,19 +80,49 @@ export class Reactive implements ReactiveInterface {
|
|
|
65
80
|
this.ctx = ctx;
|
|
66
81
|
this.parentElement = parentElement;
|
|
67
82
|
this.parentReactive = parentReactive;
|
|
68
|
-
this.id = `${ctx.viewId}-${
|
|
83
|
+
this.id = id ? id : `${ctx.viewId}-${generateUUID(5)}`;
|
|
69
84
|
this.type = type;
|
|
70
85
|
this.childrenFactory = childrenFactory;
|
|
71
86
|
this.stateKeys = stateKeys;
|
|
72
87
|
this.initMode = initMode;
|
|
73
|
-
markerRegistry.register('reactive', this.id, {
|
|
74
|
-
type: this.type,
|
|
75
|
-
stateKeys: this.stateKeys,
|
|
76
|
-
viewID: this.ctx.viewId,
|
|
77
|
-
});
|
|
78
88
|
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
// foreach type: khởi tạo slot cache để hỗ trợ identity-keyed reconciliation
|
|
90
|
+
if (type === 'foreach') {
|
|
91
|
+
this._foreachCache = new ForeachSlotCache();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (this.initMode === InitModes.CREATE) {
|
|
95
|
+
this.markerKey = markerRegistry.register('reactive', this.id, {
|
|
96
|
+
type: this.type,
|
|
97
|
+
stateKeys: this.stateKeys,
|
|
98
|
+
viewID: this.ctx.viewId,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.openTag = markerRegistry.createMarkerStart('reactive', this.id);
|
|
102
|
+
this.closeTag = markerRegistry.createMarkerEnd('reactive', this.id);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// ── Hydrate: tìm markers SSR trong DOM ──────────────────────
|
|
106
|
+
// Scan parent element cho cặp comment <!--r:id--> ... <!--/r:id-->
|
|
107
|
+
// (MarkerRegistry format). Fallback tạo mới nếu không tìm thấy.
|
|
108
|
+
const claimed = this.claimSSRMarkers();
|
|
109
|
+
if (claimed) {
|
|
110
|
+
this.openTag = claimed.open;
|
|
111
|
+
this.closeTag = claimed.close;
|
|
112
|
+
} else {
|
|
113
|
+
this.openTag = markerRegistry.createMarkerStart('reactive', this.id);
|
|
114
|
+
this.closeTag = markerRegistry.createMarkerEnd('reactive', this.id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Tìm cặp comment markers từ server-rendered HTML.
|
|
121
|
+
* Format MarkerRegistry: open = `r:id`, close = `/r:id`
|
|
122
|
+
* (r = shortcut cho 'reactive').
|
|
123
|
+
*/
|
|
124
|
+
private claimSSRMarkers(): { open: Comment; close: Comment } | null {
|
|
125
|
+
return markerRegistry.claim('reactive', this.id, this.parentElement?.element ?? null);
|
|
81
126
|
}
|
|
82
127
|
|
|
83
128
|
setParentElement(parent: HtmlInterface | null): void {
|
|
@@ -97,15 +142,62 @@ export class Reactive implements ReactiveInterface {
|
|
|
97
142
|
}
|
|
98
143
|
}
|
|
99
144
|
|
|
100
|
-
/**
|
|
145
|
+
/** Registry guard — element đã destroy không được reuse */
|
|
146
|
+
public __destroyed__: boolean = false;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Render — idempotent + position-aware (RUNTIME_CONTRACT.md §2):
|
|
150
|
+
* - Markers chưa trong DOM → đặt markers (trước closeTag của parentReactive
|
|
151
|
+
* nếu có, ngược lại append vào parentElement — đường mountElementList
|
|
152
|
+
* duyệt tuần tự nên vị trí đúng).
|
|
153
|
+
* - Markers đã trong DOM → chỉ thay nội dung GIỮA markers.
|
|
154
|
+
* Children loại marker-based (Output, nested Reactive, Fragment) được CALLER
|
|
155
|
+
* đặt markers vào đúng vị trí trước, rồi mới gọi child.render() — FIX(baseline#6).
|
|
156
|
+
* Children sinh ra khi đang active được start() ngay — FIX(baseline#7).
|
|
157
|
+
*/
|
|
101
158
|
render(): void {
|
|
102
|
-
|
|
103
|
-
|
|
159
|
+
try {
|
|
160
|
+
this._render();
|
|
161
|
+
} catch (err) {
|
|
162
|
+
// Lỗi ở vùng reactive thường xảy ra SAU khi trang đã sống (state đổi do
|
|
163
|
+
// user tương tác) — ngoài mọi try/catch của renderPageView. 'render' cho
|
|
164
|
+
// lần đầu, 'update' cho re-render.
|
|
165
|
+
const phase = this.mounted ? 'update' : 'render';
|
|
166
|
+
const result = this.ctx.handleError(err, { phase, path: this.ctx.path });
|
|
167
|
+
if (!result.handled) throw err;
|
|
168
|
+
this.clearContent();
|
|
169
|
+
if (result.fallback !== null && result.fallback !== undefined) {
|
|
170
|
+
mountChildrenBeforeAnchor(
|
|
171
|
+
this.closeTag,
|
|
172
|
+
Array.isArray(result.fallback) ? result.fallback : [result.fallback],
|
|
173
|
+
this.parentElement,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private _render(): void {
|
|
180
|
+
if (this.__destroyed__) return;
|
|
181
|
+
|
|
182
|
+
// ── Hydrate mode: markers đã trong DOM từ SSR ────────────────
|
|
183
|
+
// Tiền đề: ViewManager.hydrateView() đã gọi commitData() TRƯỚC bước này,
|
|
184
|
+
// nên closure state (vd `show`) đã có giá trị = trạng thái server.
|
|
185
|
+
// → childrenFactory sinh đúng element tree khớp với SSR DOM.
|
|
186
|
+
// _hydrateChildren tạo JS objects (Html claim DOM, Output claim markers)
|
|
187
|
+
// mà KHÔNG insert node mới — giữ nguyên DOM server-rendered.
|
|
188
|
+
if (this.initMode === InitModes.HYDRATE && this.openTag.parentNode) {
|
|
189
|
+
this._hydrateChildren();
|
|
190
|
+
this.mounted = true;
|
|
191
|
+
// Chuyển sang CREATE: re-render sau này (do state đổi) dùng CSR flow.
|
|
192
|
+
this.initMode = InitModes.CREATE;
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
104
195
|
|
|
105
|
-
if (!this.
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
196
|
+
if (!this.openTag.parentNode) {
|
|
197
|
+
// ── Lần đầu mount: đặt markers vào DOM ───────────────────────────
|
|
198
|
+
const target = this.getInsertionTarget();
|
|
199
|
+
if (!target) return;
|
|
200
|
+
if (this.parentReactive && this.parentReactive.closeTag.parentNode === target) {
|
|
109
201
|
target.insertBefore(this.openTag, this.parentReactive.closeTag);
|
|
110
202
|
target.insertBefore(this.closeTag, this.parentReactive.closeTag);
|
|
111
203
|
} else {
|
|
@@ -113,28 +205,114 @@ export class Reactive implements ReactiveInterface {
|
|
|
113
205
|
target.appendChild(this.closeTag);
|
|
114
206
|
}
|
|
115
207
|
this.mounted = true;
|
|
208
|
+
|
|
209
|
+
// Initial render — _renderChildren tự mở cửa sổ cache quanh factory call
|
|
210
|
+
this._renderChildren();
|
|
211
|
+
} else if (this._foreachCache) {
|
|
212
|
+
// ── Re-render @foreach: dùng reconciler ──────────────────────────
|
|
213
|
+
this.renderForeach();
|
|
116
214
|
} else {
|
|
117
|
-
// Re-render: clear
|
|
118
|
-
this.
|
|
215
|
+
// ── Re-render các type khác: clear + factory ──────────────────────
|
|
216
|
+
const output = this._runFactoryWithCache();
|
|
217
|
+
const next = output.filter(child => child != null).map(child =>
|
|
218
|
+
typeof child === 'string' || typeof child === 'number' ? document.createTextNode(String(child)) : child);
|
|
219
|
+
const parent = this.closeTag.parentNode;
|
|
220
|
+
if (parent) reconcileChildren(parent, this.children, next, this.closeTag);
|
|
221
|
+
this.children = next as any;
|
|
222
|
+
this._cleanOrphanNodes(next);
|
|
223
|
+
if (this._isStarted) for (const child of next) (child as any).start?.();
|
|
119
224
|
}
|
|
225
|
+
this.mounted = true;
|
|
226
|
+
}
|
|
120
227
|
|
|
121
|
-
|
|
122
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Phần render nội bộ: chạy childrenFactory, insert children vào DOM.
|
|
230
|
+
* Dùng cho initial render (mọi type) và re-render non-foreach.
|
|
231
|
+
* Precondition: markers đã trong DOM.
|
|
232
|
+
*/
|
|
233
|
+
private _renderChildren(): void {
|
|
234
|
+
// Cửa sổ cache chỉ mở quanh factory call — KHÔNG mở trong child.render()
|
|
235
|
+
// phía dưới, tránh nested @foreach (chạy lúc render con) ghi nhầm entry
|
|
236
|
+
// vào cache của loop ngoài rồi bị prunePass destroy oan ở pass sau.
|
|
237
|
+
const output = this._runFactoryWithCache();
|
|
238
|
+
const newChildren: any[] = [];
|
|
123
239
|
|
|
124
|
-
// Insert children between markers
|
|
125
240
|
for (const child of output) {
|
|
241
|
+
if (child === null || child === undefined) continue;
|
|
126
242
|
if (typeof child === 'string' || typeof child === 'number') {
|
|
127
243
|
const textNode = document.createTextNode(String(child));
|
|
128
244
|
this.insertBeforeClose(textNode);
|
|
129
245
|
this.children.push(textNode);
|
|
130
|
-
} else if (child
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
246
|
+
} else if (child instanceof Node) {
|
|
247
|
+
this.insertBeforeClose(child);
|
|
248
|
+
this.children.push(child as any);
|
|
249
|
+
} else if (typeof child === 'object') {
|
|
250
|
+
if ('element' in child && (child as any).element) {
|
|
251
|
+
// Html, TextElement — single root element
|
|
252
|
+
this.insertBeforeClose((child as any).element);
|
|
253
|
+
this.children.push(child);
|
|
254
|
+
newChildren.push(child);
|
|
255
|
+
child.render();
|
|
256
|
+
} else if ('openTag' in child) {
|
|
257
|
+
// Marker-based: Output, nested Reactive, Fragment...
|
|
258
|
+
this.insertBeforeClose((child as any).openTag);
|
|
259
|
+
this.insertBeforeClose((child as any).closeTag);
|
|
260
|
+
this.children.push(child);
|
|
261
|
+
newChildren.push(child);
|
|
262
|
+
child.render();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Vùng active: children mới cần start() ngay (có subscription/event)
|
|
268
|
+
if (this._isStarted) {
|
|
269
|
+
for (const child of newChildren) {
|
|
270
|
+
if (typeof (child as any).start === 'function') {
|
|
271
|
+
(child as any).start();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Chạy childrenFactory với cửa sổ cache foreach (nếu có) mở đúng quanh
|
|
279
|
+
* factory call. beginPass reset occurrence counters + touched set.
|
|
280
|
+
*/
|
|
281
|
+
private _runFactoryWithCache(): SaoChildrenFactoryOutput {
|
|
282
|
+
if (!this._foreachCache) {
|
|
283
|
+
return this.childrenFactory(this, this.parentElement);
|
|
284
|
+
}
|
|
285
|
+
this._foreachCache.beginPass();
|
|
286
|
+
this.ctx._currentForeachCache = this._foreachCache;
|
|
287
|
+
try {
|
|
288
|
+
return this.childrenFactory(this, this.parentElement);
|
|
289
|
+
} finally {
|
|
290
|
+
this.ctx._currentForeachCache = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Hydrate children — tạo JS element objects từ factory nhưng KHÔNG insert DOM.
|
|
296
|
+
* Html children đã claim server DOM nodes trong constructor.
|
|
297
|
+
* Output/Reactive children gọi render() để claim markers.
|
|
298
|
+
* Sau hydrate, re-render tiếp theo dùng flow CSR bình thường.
|
|
299
|
+
*/
|
|
300
|
+
private _hydrateChildren(): void {
|
|
301
|
+
const output = this._runFactoryWithCache();
|
|
302
|
+
|
|
303
|
+
for (const child of output) {
|
|
304
|
+
if (child === null || child === undefined) continue;
|
|
305
|
+
if (typeof child === 'string' || typeof child === 'number') continue;
|
|
306
|
+
if (child instanceof Node) continue;
|
|
307
|
+
|
|
308
|
+
if (typeof child === 'object') {
|
|
309
|
+
if ('element' in child && (child as any).element) {
|
|
134
310
|
this.children.push(child);
|
|
135
311
|
child.render();
|
|
136
312
|
} else if ('openTag' in child) {
|
|
137
|
-
|
|
313
|
+
if (typeof (child as any).setParentElement === 'function') {
|
|
314
|
+
(child as any).setParentElement(this.parentElement);
|
|
315
|
+
}
|
|
138
316
|
this.children.push(child);
|
|
139
317
|
child.render();
|
|
140
318
|
}
|
|
@@ -142,6 +320,100 @@ export class Reactive implements ReactiveInterface {
|
|
|
142
320
|
}
|
|
143
321
|
}
|
|
144
322
|
|
|
323
|
+
/**
|
|
324
|
+
* renderForeach — identity-keyed reconciliation cho @foreach (Phase 5).
|
|
325
|
+
*
|
|
326
|
+
* # Thuật toán
|
|
327
|
+
* 1. Snapshot cache hiện tại (prevSlots) — biết item nào đang hiển thị
|
|
328
|
+
* 2. Chạy childrenFactory với cache active → __foreach trả về:
|
|
329
|
+
* - cached elements nếu item ref giống (no DOM create)
|
|
330
|
+
* - elements mới nếu item mới (và lưu vào cache)
|
|
331
|
+
* 3. Diff: items nào KHÔNG xuất hiện trong kết quả mới → destroy + xoá cache
|
|
332
|
+
* 4. Reorder DOM: insertBefore(closeTag) cho tất cả elements mới (move hoặc append)
|
|
333
|
+
* 5. Cleanup orphan DOM nodes (nodes còn sót từ destroyed items)
|
|
334
|
+
* 6. Start elements mới nếu vùng đang active
|
|
335
|
+
*
|
|
336
|
+
* # Complexity
|
|
337
|
+
* - O(n) forEach pass trong __foreach
|
|
338
|
+
* - O(n) destroy pass cho removed items
|
|
339
|
+
* - O(n) DOM move pass (insertBefore là O(1) mỗi node)
|
|
340
|
+
* → Tổng O(n) so với O(2n) của clear+recreate, nhưng tiết kiệm DOM create/destroy
|
|
341
|
+
* cho unchanged items.
|
|
342
|
+
*/
|
|
343
|
+
private renderForeach(): void {
|
|
344
|
+
// ── Step 1: Ghi nhớ children đang hiển thị (phân biệt reuse/new) ─────
|
|
345
|
+
const cache = this._foreachCache!;
|
|
346
|
+
const prevChildren = new Set(this.children); // old Saola elements
|
|
347
|
+
|
|
348
|
+
// ── Step 2: Chạy factory với cache active (beginPass + claim/store) ──
|
|
349
|
+
const output = this._runFactoryWithCache();
|
|
350
|
+
|
|
351
|
+
// Flatten output thành danh sách children mới
|
|
352
|
+
const newChildren: any[] = [];
|
|
353
|
+
for (const child of output) {
|
|
354
|
+
if (child === null || child === undefined) continue;
|
|
355
|
+
if (typeof child === 'object') {
|
|
356
|
+
newChildren.push(child);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ── Step 3: Destroy slot không được claim/store trong pass ───────────
|
|
361
|
+
// = item đã rời list, hoặc bị thay bằng ref mới (key trùng nhưng ref đổi).
|
|
362
|
+
cache.prunePass((slot) => {
|
|
363
|
+
for (const el of slot.elements) {
|
|
364
|
+
if (el && typeof el.destroy === 'function') {
|
|
365
|
+
el.destroy();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const trulyNew = newChildren.filter(child => !prevChildren.has(child));
|
|
371
|
+
const parent = this.closeTag.parentNode;
|
|
372
|
+
if (parent) reconcileChildren(parent, this.children, newChildren, this.closeTag,
|
|
373
|
+
child => cache.refreshedElements.has(child));
|
|
374
|
+
this._cleanOrphanNodes(newChildren);
|
|
375
|
+
this.children = newChildren;
|
|
376
|
+
if (this._isStarted) {
|
|
377
|
+
for (const child of trulyNew) child.start?.();
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Xoá các DOM nodes "mồ côi" giữa openTag và closeTag của Reactive.
|
|
383
|
+
* Orphan = nodes không thuộc bất kỳ child nào trong newChildren.
|
|
384
|
+
*
|
|
385
|
+
* Build tập hợp "expected nodes" từ newChildren, sau đó scan DOM
|
|
386
|
+
* và remove nodes không nằm trong tập đó.
|
|
387
|
+
*/
|
|
388
|
+
private _cleanOrphanNodes(newChildren: any[]): void {
|
|
389
|
+
// Thu thập tất cả DOM nodes hợp lệ (thuộc new children)
|
|
390
|
+
const validNodes = new Set<Node>();
|
|
391
|
+
for (const child of newChildren) {
|
|
392
|
+
if ('element' in child && (child as any).element) {
|
|
393
|
+
validNodes.add((child as any).element);
|
|
394
|
+
} else if ('openTag' in child) {
|
|
395
|
+
// Marker-based: collect tất cả nodes trong block
|
|
396
|
+
let node: Node | null = (child as any).openTag;
|
|
397
|
+
while (node && node !== (child as any).closeTag) {
|
|
398
|
+
validNodes.add(node);
|
|
399
|
+
node = node.nextSibling;
|
|
400
|
+
}
|
|
401
|
+
if (node) validNodes.add(node); // closeTag
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Scan từ openTag → closeTag, remove nodes không hợp lệ.
|
|
406
|
+
// Node đang chạy leave PHẢI nằm lại tới khi animation xong — nó tự gỡ.
|
|
407
|
+
let current = this.openTag.nextSibling;
|
|
408
|
+
while (current && current !== this.closeTag) {
|
|
409
|
+
const next = current.nextSibling;
|
|
410
|
+
if (!validNodes.has(current) && !isLeaving(current)) {
|
|
411
|
+
current.remove();
|
|
412
|
+
}
|
|
413
|
+
current = next;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
145
417
|
/** Schedule a re-render through the ViewController */
|
|
146
418
|
update(): void {
|
|
147
419
|
this.ctx.scheduleUpdate(this);
|
|
@@ -157,11 +429,12 @@ export class Reactive implements ReactiveInterface {
|
|
|
157
429
|
}
|
|
158
430
|
this.children = [];
|
|
159
431
|
|
|
160
|
-
// Remove DOM nodes between markers
|
|
432
|
+
// Remove DOM nodes between markers — trừ node đang leave (child.destroy()
|
|
433
|
+
// phía trên vừa khởi động animation cho chúng; tự gỡ khi xong).
|
|
161
434
|
let current = this.openTag.nextSibling;
|
|
162
435
|
while (current && current !== this.closeTag) {
|
|
163
436
|
const next = current.nextSibling;
|
|
164
|
-
current.remove();
|
|
437
|
+
if (!isLeaving(current)) current.remove();
|
|
165
438
|
current = next;
|
|
166
439
|
}
|
|
167
440
|
}
|
|
@@ -208,7 +481,7 @@ export class Reactive implements ReactiveInterface {
|
|
|
208
481
|
|
|
209
482
|
if (this.unsubscribe) {
|
|
210
483
|
this.unsubscribe();
|
|
211
|
-
this.unsubscribe = () => {};
|
|
484
|
+
this.unsubscribe = () => { };
|
|
212
485
|
}
|
|
213
486
|
|
|
214
487
|
// Recursively stop children
|
|
@@ -219,18 +492,22 @@ export class Reactive implements ReactiveInterface {
|
|
|
219
492
|
}
|
|
220
493
|
}
|
|
221
494
|
|
|
222
|
-
/** Remove content but keep markers (for hide/show scenarios) */
|
|
223
|
-
hide(): void {
|
|
224
|
-
this.clearContent();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Re-render content (for show after hide) */
|
|
228
|
-
show(): void {
|
|
229
|
-
this.render();
|
|
230
|
-
}
|
|
231
|
-
|
|
232
495
|
destroy(): void {
|
|
496
|
+
this.__destroyed__ = true;
|
|
497
|
+
this.ctx.releaseElement?.(this);
|
|
498
|
+
// MarkerRegistry là singleton TOÀN CỤC (sống qua navigate). Reactive trong
|
|
499
|
+
// loop có id kèm item (`{viewId}-{hash}-${item.id}`) → không gỡ thì mỗi
|
|
500
|
+
// trang/mỗi lần refresh để lại một record vĩnh viễn.
|
|
501
|
+
if (this.markerKey) {
|
|
502
|
+
markerRegistry.remove(this.markerKey);
|
|
503
|
+
this.markerKey = null;
|
|
504
|
+
}
|
|
505
|
+
this.stop();
|
|
233
506
|
this.clearContent();
|
|
507
|
+
// Giải phóng cache — clearContent() đã destroy các elements
|
|
508
|
+
if (this._foreachCache) {
|
|
509
|
+
this._foreachCache.clear();
|
|
510
|
+
}
|
|
234
511
|
this.openTag.remove();
|
|
235
512
|
this.closeTag.remove();
|
|
236
513
|
this.mounted = false;
|
|
@@ -244,10 +521,10 @@ export class Reactive implements ReactiveInterface {
|
|
|
244
521
|
set isOneReactive(value: boolean) {
|
|
245
522
|
// No-op setter to satisfy the Interface; this property is always true for Reactive elements
|
|
246
523
|
}
|
|
247
|
-
set
|
|
524
|
+
set isSaoElement(value: boolean) {
|
|
248
525
|
// No-op setter to satisfy the Interface; Reactive is a type of OneElement
|
|
249
526
|
}
|
|
250
|
-
get
|
|
527
|
+
get isSaoElement(): boolean {
|
|
251
528
|
return true;
|
|
252
529
|
}
|
|
253
|
-
}
|
|
530
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { HtmlInterface, TextInterface } from "../contracts/ElementInterface";
|
|
2
|
-
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface";
|
|
3
|
-
import {
|
|
4
|
-
import { OneObjectType } from "../types/utils";
|
|
1
|
+
import type { HtmlInterface, TextInterface } from "../contracts/ElementInterface.js";
|
|
2
|
+
import type { ViewControllerInterface } from "../contracts/ViewControllerInterface.js";
|
|
3
|
+
import { SaoObjectType } from "../types/utils.js";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* TextElement — wraps a DOM Text node.
|
|
@@ -11,7 +10,7 @@ import { OneObjectType } from "../types/utils";
|
|
|
11
10
|
* no DOM removal/insertion needed.
|
|
12
11
|
*/
|
|
13
12
|
export class TextElement implements TextInterface {
|
|
14
|
-
|
|
13
|
+
saoType: SaoObjectType = 'TextElement';
|
|
15
14
|
public element: Text;
|
|
16
15
|
public parent: HtmlInterface | null;
|
|
17
16
|
private ctx: ViewControllerInterface;
|
|
@@ -22,6 +21,7 @@ export class TextElement implements TextInterface {
|
|
|
22
21
|
public shouldEscapeHTML: boolean = true; // Whether to escape HTML in text content
|
|
23
22
|
|
|
24
23
|
public isStarted: boolean = false;
|
|
24
|
+
readonly isStatic: boolean;
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
domChildren: Node[] = []; // For compatibility with HtmlInterface; Text itself doesn't have a single root element
|
|
@@ -30,11 +30,11 @@ export class TextElement implements TextInterface {
|
|
|
30
30
|
this.generateText = generateText;
|
|
31
31
|
this.parent = parent;
|
|
32
32
|
this.statekeys = stateKeys;
|
|
33
|
+
this.isStatic = stateKeys.length === 0;
|
|
33
34
|
this.shouldEscapeHTML = isEscapeHTML;
|
|
35
|
+
// FIX(baseline#3): KHÔNG escapeHTML khi ghi vào Text node — text node tự an toàn,
|
|
36
|
+
// escape thủ công gây double-escape. shouldEscapeHTML chỉ dùng cho SSR string path.
|
|
34
37
|
this._text = this.generateText();
|
|
35
|
-
if (this.shouldEscapeHTML) {
|
|
36
|
-
this._text = escapeHTML(this._text);
|
|
37
|
-
}
|
|
38
38
|
this.element = document.createTextNode(this._text);
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -57,28 +57,37 @@ export class TextElement implements TextInterface {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
setParentElement(parent: HtmlInterface | null): void {
|
|
60
|
-
|
|
60
|
+
this.parent = parent;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/** Update text content in-place */
|
|
64
64
|
update(newText: string): void {
|
|
65
65
|
if (this._text !== newText) {
|
|
66
66
|
this._text = newText;
|
|
67
|
-
this.element.textContent =
|
|
67
|
+
this.element.textContent = newText;
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
render():
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
render(): HTMLElement | Text | Comment {
|
|
72
|
+
const text = this.generateText();
|
|
73
|
+
this.update(text);
|
|
74
|
+
return this.element;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
replaceStaticText(text: string): void {
|
|
78
|
+
this.generateText = () => text;
|
|
79
|
+
this.update(text);
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
remove(): void {
|
|
78
83
|
this.element.remove();
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
/** Registry guard — element đã destroy không được reuse */
|
|
87
|
+
public __destroyed__: boolean = false;
|
|
88
|
+
|
|
81
89
|
destroy(): void {
|
|
90
|
+
this.__destroyed__ = true;
|
|
82
91
|
if (this.unsubscribe) {
|
|
83
92
|
this.unsubscribe();
|
|
84
93
|
this.unsubscribe = null;
|
|
@@ -95,10 +104,10 @@ export class TextElement implements TextInterface {
|
|
|
95
104
|
this.update(newText);
|
|
96
105
|
}
|
|
97
106
|
|
|
98
|
-
get
|
|
107
|
+
get isSaoElement(): boolean {
|
|
99
108
|
return true;
|
|
100
109
|
}
|
|
101
|
-
set
|
|
110
|
+
set isSaoElement(value: boolean) {
|
|
102
111
|
// No-op setter to satisfy the Interface; this property is always true for Text elements
|
|
103
112
|
}
|
|
104
113
|
get isOneText(): boolean {
|