@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
|
@@ -13,16 +13,53 @@ export interface ViewStateInterface {
|
|
|
13
13
|
export interface StateManagerInterface {
|
|
14
14
|
/** Create reactive state — returns [value, setter, key] */
|
|
15
15
|
useState(value: any, key?: string | number): [any, (newValue: any) => void, string | number];
|
|
16
|
+
/**
|
|
17
|
+
* State dẫn xuất có memo hoá — chỉ tính lại khi `deps` đổi, và lazy
|
|
18
|
+
* (đánh dấu bẩn lúc dep đổi, tính lúc đọc). Đọc qua `getStateByKey(key)`
|
|
19
|
+
* hoặc `subscribe([key])` như state thường.
|
|
20
|
+
*/
|
|
21
|
+
computed<T>(key: string, fn: () => T, deps?: string[]): () => T;
|
|
16
22
|
/** Update state by key */
|
|
17
23
|
updateStateByKey(key: string | number, value: any): any;
|
|
18
24
|
/** Get state value by key (supports nested paths: 'user.name') */
|
|
19
25
|
getStateByKey(key: string | number): any;
|
|
26
|
+
getStateVersion(key: string): number;
|
|
20
27
|
/** Subscribe to state changes */
|
|
21
28
|
subscribe(key: string | number | string[] | Record<string, (value: any) => void>, callback?: (value: any) => void): () => void;
|
|
22
29
|
/** Unsubscribe */
|
|
23
30
|
unsubscribe(key: string | number | string[] | Record<string, (value: any) => void>, callback?: (value: any) => void): void;
|
|
24
|
-
/**
|
|
25
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Register shorthand — pre-declare a state slot, returns setter.
|
|
33
|
+
* Value is optional — compiler calls register(key) with 1 arg;
|
|
34
|
+
* initial value is set later via update$xxx() in commitConstructorData.
|
|
35
|
+
*/
|
|
36
|
+
register(key: string | number, value?: any): (newValue: any) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Setter map — exposed for direct access by compiled views.
|
|
39
|
+
* e.g. __STATE__.__.setters.setCount = setCount;
|
|
40
|
+
*/
|
|
41
|
+
setters: Record<string | number, (value: any) => void>;
|
|
42
|
+
/**
|
|
43
|
+
* Flag — true while commitConstructorData is running, false after lockUpdateRealState().
|
|
44
|
+
* Compiler-generated update$xxx() checks this before calling updateStateByKey.
|
|
45
|
+
*/
|
|
46
|
+
readonly canUpdateStateByKey: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Lock — called at end of commitConstructorData() to prevent update$xxx from
|
|
49
|
+
* being accidentally triggered after initialization.
|
|
50
|
+
*/
|
|
51
|
+
lockUpdateRealState(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Unlock — called before updateVariableData() to allow update$xxx again.
|
|
54
|
+
*/
|
|
55
|
+
unlockUpdateRealState(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Flush đồng bộ pending state changes (huỷ RAF đang chờ nếu có).
|
|
58
|
+
* Dùng sau commitData()/mount để DOM phản ánh state ngay trong cùng tick.
|
|
59
|
+
*/
|
|
60
|
+
flushNow(): void;
|
|
61
|
+
/** Opt into expensive mutation diagnostics; disabled by default in browsers. */
|
|
62
|
+
setMutationDiagnostics(enabled: boolean): void;
|
|
26
63
|
/** Destroy — cleanup all listeners and states */
|
|
27
64
|
destroy(): void;
|
|
28
65
|
}
|
|
@@ -4,13 +4,13 @@ export enum InitModes {
|
|
|
4
4
|
HYDRATE = 'hydrate'
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
* Common types and utilities for
|
|
8
|
-
* This file defines shared types and enums used across
|
|
7
|
+
* Common types and utilities for SaoView contracts
|
|
8
|
+
* This file defines shared types and enums used across SaoView's contracts, such as initialization modes and system keys for HTML elements.
|
|
9
9
|
*
|
|
10
10
|
* Key exports:
|
|
11
11
|
*/
|
|
12
12
|
/**
|
|
13
|
-
* System keys for HTML elements used by
|
|
13
|
+
* System keys for HTML elements used by SaoView.
|
|
14
14
|
* These keys are used to store metadata on HTML elements, such as IDs and view information.
|
|
15
15
|
* @name ESK (Element System Keys)
|
|
16
16
|
* @enum {string}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
// ─── Barrel Re-exports ───────────────────────────────────────────
|
|
2
2
|
// All interfaces and related types re-exported for backward compatibility
|
|
3
3
|
|
|
4
|
-
export type { LoopContextInterface } from "./LoopContextInterface";
|
|
5
|
-
export type { ViewStateInterface, StateManagerInterface, StateListener, MultiKeyStateListener, StateItem } from "./ViewStateInterface";
|
|
6
|
-
export type {
|
|
7
|
-
export type { ReactiveInterface, ReactiveRenderFn, ReactiveChildrenFactory, ReactiveConfig } from "./ReactiveInterface";
|
|
8
|
-
export type { BlockInterface, BlockOutletInterface, BlockManagerInterface, BlockRenderFactory } from "./BlockInterface";
|
|
9
|
-
export type { ViewControllerInterface, ViewType, ViewConfig, ViewRuntimeConfig } from "./ViewControllerInterface";
|
|
10
|
-
export type { ViewInterface, ViewRenderFactory, ViewConstructor, ViewLifecycleHooks } from "./ViewInterface";
|
|
11
|
-
export type { ComponentInterface } from "./ComponentInterface";
|
|
12
|
-
export type { ApplicationInterface, AppFactory, ServiceKey, ServiceFactory, ServiceBinding } from "./ApplicationInterface";
|
|
13
|
-
export type { ServiceProviderInterface } from "./ServiceProviderInterface";
|
|
14
|
-
export type { ViewCacheInterface } from "./ViewCacheInterface";
|
|
15
|
-
export type { ViewManagerInterface, ActiveViewInfo } from "./ViewManagerInterface";
|
|
16
|
-
export type { RouterInterface } from "./RouterInterface";
|
|
17
|
-
export type { HelperInterface, CollectionProxyInterface } from "./HelperInterface";
|
|
18
|
-
export type { EventServiceInterface, EventCallback } from "./EventServiceInterface";
|
|
19
|
-
export type { HttpServiceInterface, HttpRequestConfig, HttpResponse, HttpInterceptor } from "./HttpServiceInterface";
|
|
20
|
-
export type { StoreServiceInterface } from "./StoreServiceInterface";
|
|
21
|
-
export type { StorageServiceInterface, StorageEventCallback } from "./StorageServiceInterface";
|
|
22
|
-
export type { LoggerServiceInterface, LoggerConfig, LogEntry, LogLevel } from "./LoggerServiceInterface";
|
|
4
|
+
export type { LoopContextInterface } from "./LoopContextInterface.js";
|
|
5
|
+
export type { ViewStateInterface, StateManagerInterface, StateListener, MultiKeyStateListener, StateItem } from "./ViewStateInterface.js";
|
|
6
|
+
export type { SaoNodeInterface, HtmlInterface, TextInterface, OutputInterface, FragmentInterface, SaoElementConfig, HtmlElementConfig, SaoElementEventHandler, SaoElementChildren, SaoChildrenFactoryOutput, SaoChildrenFactory } from "./ElementInterface.js";
|
|
7
|
+
export type { ReactiveInterface, ReactiveRenderFn, ReactiveChildrenFactory, ReactiveConfig } from "./ReactiveInterface.js";
|
|
8
|
+
export type { BlockInterface, BlockOutletInterface, BlockManagerInterface, BlockRenderFactory } from "./BlockInterface.js";
|
|
9
|
+
export type { ViewControllerInterface, ViewType, ViewConfig, ViewRuntimeConfig } from "./ViewControllerInterface.js";
|
|
10
|
+
export type { ViewInterface, ViewRenderFactory, ViewConstructor, ViewLifecycleHooks } from "./ViewInterface.js";
|
|
11
|
+
export type { ComponentInterface } from "./ComponentInterface.js";
|
|
12
|
+
export type { ApplicationInterface, AppFactory, ServiceKey, ServiceFactory, ServiceBinding } from "./ApplicationInterface.js";
|
|
13
|
+
export type { ServiceProviderInterface } from "./ServiceProviderInterface.js";
|
|
14
|
+
export type { ViewCacheInterface } from "./ViewCacheInterface.js";
|
|
15
|
+
export type { ViewManagerInterface, ActiveViewInfo } from "./ViewManagerInterface.js";
|
|
16
|
+
export type { RouterInterface } from "./RouterInterface.js";
|
|
17
|
+
export type { HelperInterface, CollectionProxyInterface } from "./HelperInterface.js";
|
|
18
|
+
export type { EventServiceInterface, EventCallback } from "./EventServiceInterface.js";
|
|
19
|
+
export type { HttpServiceInterface, HttpRequestConfig, HttpResponse, HttpInterceptor } from "./HttpServiceInterface.js";
|
|
20
|
+
export type { StoreServiceInterface } from "./StoreServiceInterface.js";
|
|
21
|
+
export type { StorageServiceInterface, StorageEventCallback } from "./StorageServiceInterface.js";
|
|
22
|
+
export type { LoggerServiceInterface, LoggerConfig, LogEntry, LogLevel } from "./LoggerServiceInterface.js";
|
|
23
23
|
|
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
import { HtmlInterface,
|
|
2
|
-
import { ViewControllerInterface } from "./utils";
|
|
1
|
+
import { HtmlInterface, SaoElementChildren } from "./ElementInterface.js";
|
|
2
|
+
import { ViewControllerInterface } from "./utils.js";
|
|
3
3
|
|
|
4
|
-
export type SectionItemType = 'static' | 'dynamic' | 'async' | 'reactive';
|
|
5
|
-
export type SectionContentType = 'text' | 'html'
|
|
6
|
-
export type SectionContentRenderer = (parentElement?: HtmlInterface | null | undefined) => string | OneElementChildren;
|
|
7
|
-
export type SectionConstruvtorArgs = {
|
|
8
|
-
ctx?: ViewControllerInterface | null;
|
|
9
|
-
name: string;
|
|
10
|
-
type: SectionItemType;
|
|
11
|
-
contentType?: SectionContentType;
|
|
12
|
-
stateKeys?: string[];
|
|
13
|
-
renderFactory: SectionContentRenderer;
|
|
14
|
-
[key: string]: any;
|
|
15
|
-
}
|
|
16
|
-
export interface SectionInterface {
|
|
17
|
-
ctx? : ViewControllerInterface | null;
|
|
18
|
-
name: string;
|
|
19
|
-
type: SectionItemType;
|
|
20
|
-
contentType?: SectionContentType;
|
|
21
|
-
stateKeys?: string[];
|
|
22
|
-
renderFactory?: SectionContentRenderer;
|
|
23
|
-
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools runtime hook (GAP-05).
|
|
3
|
+
*
|
|
4
|
+
* Nền tảng cho MỌI UI devtools (overlay in-page, browser extension sau này):
|
|
5
|
+
* runtime chỉ phát sự kiện + cho phép đọc snapshot, KHÔNG biết gì về UI.
|
|
6
|
+
*
|
|
7
|
+
* Thiết kế:
|
|
8
|
+
* - **Tắt mặc định, zero-cost.** `emit()` thoát ngay ở dòng đầu khi chưa bật.
|
|
9
|
+
* Không tạo object, không serialize — không được làm chậm production.
|
|
10
|
+
* - Bật bằng `App.devtools.enable()` hoặc đặt sẵn `window.__SAOLA_DEVTOOLS_HOOK__`
|
|
11
|
+
* TRƯỚC khi app boot (cách extension sẽ dùng: content script inject sớm).
|
|
12
|
+
* - Snapshot đọc từ ViewManager/ViewController, không giữ state riêng →
|
|
13
|
+
* không có nguồn sự thật thứ hai để lệch.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export type DevtoolsEventType =
|
|
17
|
+
| 'view:mounted' | 'view:destroyed'
|
|
18
|
+
| 'state:changed'
|
|
19
|
+
| 'error';
|
|
20
|
+
|
|
21
|
+
export interface DevtoolsEvent {
|
|
22
|
+
type: DevtoolsEventType;
|
|
23
|
+
/** ms từ lúc trang load — đủ để xếp thứ tự trên timeline */
|
|
24
|
+
at: number;
|
|
25
|
+
viewId?: string;
|
|
26
|
+
path?: string;
|
|
27
|
+
/** state:changed → key nào đổi; error → phase */
|
|
28
|
+
detail?: any;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DevtoolsViewNode {
|
|
32
|
+
viewId: string;
|
|
33
|
+
path: string;
|
|
34
|
+
viewType: string;
|
|
35
|
+
lifecycleState: string;
|
|
36
|
+
/** Ảnh chụp state hiện tại (đã lọc giá trị không serialize được) */
|
|
37
|
+
state: Record<string, any>;
|
|
38
|
+
/** Số element đang giữ trong registry — dò rò rỉ */
|
|
39
|
+
elementCount: number;
|
|
40
|
+
children: DevtoolsViewNode[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Listener = (e: DevtoolsEvent) => void;
|
|
44
|
+
|
|
45
|
+
const MAX_LOG = 200;
|
|
46
|
+
|
|
47
|
+
class DevtoolsHook {
|
|
48
|
+
private enabled = false;
|
|
49
|
+
private listeners: Listener[] = [];
|
|
50
|
+
/** Vòng đệm — devtools mở muộn vẫn xem được lịch sử gần đây */
|
|
51
|
+
private log: DevtoolsEvent[] = [];
|
|
52
|
+
/** ViewManager, gán lúc boot; dùng any để tránh vòng import */
|
|
53
|
+
private viewManager: any = null;
|
|
54
|
+
|
|
55
|
+
isEnabled(): boolean { return this.enabled; }
|
|
56
|
+
|
|
57
|
+
enable(): void { this.enabled = true; }
|
|
58
|
+
disable(): void { this.enabled = false; this.log = []; }
|
|
59
|
+
|
|
60
|
+
/** ViewManager tự đăng ký lúc init — hook không đi tìm global. */
|
|
61
|
+
attach(viewManager: any): void { this.viewManager = viewManager; }
|
|
62
|
+
|
|
63
|
+
subscribe(fn: Listener): () => void {
|
|
64
|
+
this.listeners.push(fn);
|
|
65
|
+
return () => { this.listeners = this.listeners.filter(l => l !== fn); };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Hot path — phải rẻ khi tắt. */
|
|
69
|
+
emit(type: DevtoolsEventType, data?: Omit<DevtoolsEvent, 'type' | 'at'>): void {
|
|
70
|
+
if (!this.enabled) return;
|
|
71
|
+
const event: DevtoolsEvent = { type, at: Math.round(performance.now()), ...data };
|
|
72
|
+
this.log.push(event);
|
|
73
|
+
if (this.log.length > MAX_LOG) this.log.shift();
|
|
74
|
+
for (const fn of this.listeners) {
|
|
75
|
+
try { fn(event); } catch (e) { console.error('[Devtools] listener error:', e); }
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getLog(): DevtoolsEvent[] { return [...this.log]; }
|
|
80
|
+
clearLog(): void { this.log = []; }
|
|
81
|
+
|
|
82
|
+
/** Cây view đang mount: layout chain (ngoài → trong) rồi tới page. */
|
|
83
|
+
getViewTree(): DevtoolsViewNode[] {
|
|
84
|
+
const vm = this.viewManager;
|
|
85
|
+
if (!vm) return [];
|
|
86
|
+
const roots: any[] = [...(vm.getLayoutChain?.() ?? []), vm.getCurrentView?.()].filter(Boolean);
|
|
87
|
+
// Layout chain và page nối nhau theo thứ tự mount, không phải quan hệ
|
|
88
|
+
// parent/children (quan hệ đó chỉ dùng cho @include) → dựng phẳng ở
|
|
89
|
+
// gốc, rồi mỗi node mở rộng theo ctrl.children thật.
|
|
90
|
+
return roots.map(v => this.toNode(v.__ctrl__));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private toNode(ctrl: any): DevtoolsViewNode {
|
|
94
|
+
return {
|
|
95
|
+
viewId: ctrl.viewId,
|
|
96
|
+
path: ctrl.path,
|
|
97
|
+
viewType: ctrl.viewType,
|
|
98
|
+
lifecycleState: ctrl.lifecycleState,
|
|
99
|
+
state: this.snapshotState(ctrl),
|
|
100
|
+
elementCount: ctrl.elements?.size ?? 0,
|
|
101
|
+
children: (ctrl.children ?? []).map((c: any) => this.toNode(c)),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Ảnh chụp state — mọi giá trị đi qua JSON round-trip để cắt tham chiếu
|
|
107
|
+
* DOM/hàm/vòng lặp: devtools không được giữ sống object của app, và
|
|
108
|
+
* không được nổ khi state chứa cấu trúc vòng.
|
|
109
|
+
*/
|
|
110
|
+
private snapshotState(ctrl: any): Record<string, any> {
|
|
111
|
+
const out: Record<string, any> = {};
|
|
112
|
+
const states = ctrl.states?.__?.states ?? {};
|
|
113
|
+
for (const key of Object.keys(states)) {
|
|
114
|
+
try {
|
|
115
|
+
const value = states[key]?.value; // computed: getter → tính lại ở đây
|
|
116
|
+
out[key] = value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
117
|
+
} catch {
|
|
118
|
+
out[key] = '[không serialize được]';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const devtools = new DevtoolsHook();
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Nếu môi trường đã cắm sẵn `window.__SAOLA_DEVTOOLS_HOOK__` trước khi app boot
|
|
129
|
+
* (browser extension làm vậy), tự bật và nối sự kiện sang đó.
|
|
130
|
+
*/
|
|
131
|
+
if (typeof window !== 'undefined') {
|
|
132
|
+
const external = (window as any).__SAOLA_DEVTOOLS_HOOK__;
|
|
133
|
+
if (external) {
|
|
134
|
+
devtools.enable();
|
|
135
|
+
devtools.subscribe((e) => { try { external.onEvent?.(e); } catch { /* của bên thứ 3 */ } });
|
|
136
|
+
external.getViewTree = () => devtools.getViewTree();
|
|
137
|
+
external.getLog = () => devtools.getLog();
|
|
138
|
+
}
|
|
139
|
+
(window as any).__SAOLA_DEVTOOLS__ = devtools;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export default devtools;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools — API công khai, gắn vào DI container dưới tên `Devtools`
|
|
3
|
+
* (dùng `app('Devtools')` hoặc `App.devtools`).
|
|
4
|
+
*
|
|
5
|
+
* Tắt hoàn toàn mặc định: `emit()` thoát ngay ở dòng đầu khi chưa bật, nên
|
|
6
|
+
* production không trả phí gì ngoài vài hàm không bao giờ chạy.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* App.devtools.open(); // bật + mở panel
|
|
10
|
+
* App.devtools.enableShortcut(); // Ctrl+Shift+D bật/tắt panel
|
|
11
|
+
* App.devtools.getViewTree(); // đọc cây view bằng code
|
|
12
|
+
*/
|
|
13
|
+
import devtools from './hook.js';
|
|
14
|
+
import inspector, { bindDevtoolsShortcut } from './inspector.js';
|
|
15
|
+
|
|
16
|
+
export const Devtools = {
|
|
17
|
+
/** Bật thu thập sự kiện (chưa mở UI) — dùng khi chỉ cần đọc bằng code. */
|
|
18
|
+
enable: () => devtools.enable(),
|
|
19
|
+
disable: () => { inspector.close(); devtools.disable(); },
|
|
20
|
+
isEnabled: () => devtools.isEnabled(),
|
|
21
|
+
|
|
22
|
+
/** Bật + mở panel in-page. */
|
|
23
|
+
open: () => inspector.open(),
|
|
24
|
+
close: () => inspector.close(),
|
|
25
|
+
toggle: () => inspector.toggle(),
|
|
26
|
+
isOpen: () => inspector.isOpen(),
|
|
27
|
+
|
|
28
|
+
enableShortcut: () => bindDevtoolsShortcut(),
|
|
29
|
+
|
|
30
|
+
getViewTree: () => devtools.getViewTree(),
|
|
31
|
+
getLog: () => devtools.getLog(),
|
|
32
|
+
clearLog: () => devtools.clearLog(),
|
|
33
|
+
subscribe: (fn: Parameters<typeof devtools.subscribe>[0]) => devtools.subscribe(fn),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { devtools, inspector };
|
|
37
|
+
export type { DevtoolsEvent, DevtoolsEventType, DevtoolsViewNode } from './hook.js';
|
|
38
|
+
export default Devtools;
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevTools inspector — panel in-page, không cần cài extension.
|
|
3
|
+
*
|
|
4
|
+
* Vì sao overlay chứ không phải browser extension: extension cần scaffolding
|
|
5
|
+
* riêng (manifest, content script, bridge, publish store) mà vẫn hiển thị đúng
|
|
6
|
+
* dữ liệu này. Overlay chạy ngay ở mọi môi trường (kể cả máy khác, mobile
|
|
7
|
+
* webview) với chi phí nhỏ hơn nhiều. Hook (`devtools/hook.ts`) đã tách riêng
|
|
8
|
+
* nên extension vẫn dựng được sau này trên CÙNG nguồn dữ liệu.
|
|
9
|
+
*
|
|
10
|
+
* Bật: App.devtools.open() — hoặc Ctrl+Shift+D sau khi App.devtools.enable()
|
|
11
|
+
*
|
|
12
|
+
* Toàn bộ DOM của panel dựng bằng createElement + textContent — panel hiển thị
|
|
13
|
+
* state/đường dẫn/thông báo lỗi do người dùng kiểm soát, nội suy chuỗi vào
|
|
14
|
+
* innerHTML ở đây là đường tiêm HTML (đúng lỗi đã vá ở GAP-07).
|
|
15
|
+
*/
|
|
16
|
+
import devtools, { DevtoolsEvent, DevtoolsViewNode } from './hook.js';
|
|
17
|
+
|
|
18
|
+
const PANEL_ID = '__saola_devtools_panel__';
|
|
19
|
+
const HIGHLIGHT_ID = '__saola_devtools_highlight__';
|
|
20
|
+
|
|
21
|
+
class Inspector {
|
|
22
|
+
private panel: HTMLElement | null = null;
|
|
23
|
+
private unsubscribe: (() => void) | null = null;
|
|
24
|
+
private refreshQueued = false;
|
|
25
|
+
private selectedViewId: string | null = null;
|
|
26
|
+
|
|
27
|
+
isOpen(): boolean { return this.panel !== null; }
|
|
28
|
+
|
|
29
|
+
open(): void {
|
|
30
|
+
if (this.panel || typeof document === 'undefined') return;
|
|
31
|
+
devtools.enable();
|
|
32
|
+
|
|
33
|
+
this.panel = this.buildPanel();
|
|
34
|
+
document.body.appendChild(this.panel);
|
|
35
|
+
|
|
36
|
+
// Gom nhiều sự kiện trong cùng frame thành 1 lần vẽ — state đổi có thể
|
|
37
|
+
// bắn hàng loạt, vẽ lại từng cái sẽ tự làm chậm chính app đang debug.
|
|
38
|
+
this.unsubscribe = devtools.subscribe(() => this.queueRefresh());
|
|
39
|
+
this.refresh();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
close(): void {
|
|
43
|
+
this.unsubscribe?.();
|
|
44
|
+
this.unsubscribe = null;
|
|
45
|
+
this.panel?.remove();
|
|
46
|
+
this.panel = null;
|
|
47
|
+
this.clearHighlight();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
toggle(): void { this.isOpen() ? this.close() : this.open(); }
|
|
51
|
+
|
|
52
|
+
private queueRefresh(): void {
|
|
53
|
+
if (this.refreshQueued) return;
|
|
54
|
+
this.refreshQueued = true;
|
|
55
|
+
requestAnimationFrame(() => { this.refreshQueued = false; this.refresh(); });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Dựng khung ────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
private buildPanel(): HTMLElement {
|
|
61
|
+
const panel = document.createElement('div');
|
|
62
|
+
panel.id = PANEL_ID;
|
|
63
|
+
panel.style.cssText = [
|
|
64
|
+
'position:fixed', 'right:12px', 'bottom:12px', 'width:380px', 'max-height:70vh',
|
|
65
|
+
'z-index:2147483647', 'display:flex', 'flex-direction:column',
|
|
66
|
+
'background:#1e1e2e', 'color:#cdd6f4', 'border:1px solid #45475a', 'border-radius:8px',
|
|
67
|
+
'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace',
|
|
68
|
+
'box-shadow:0 8px 32px rgba(0,0,0,.4)', 'overflow:hidden',
|
|
69
|
+
].join(';');
|
|
70
|
+
|
|
71
|
+
const header = document.createElement('div');
|
|
72
|
+
header.style.cssText = 'display:flex;align-items:center;gap:8px;padding:8px 10px;background:#181825;border-bottom:1px solid #45475a';
|
|
73
|
+
const title = document.createElement('strong');
|
|
74
|
+
title.textContent = 'Saola DevTools';
|
|
75
|
+
title.style.cssText = 'flex:1;color:#89b4fa';
|
|
76
|
+
const btnClear = this.button('Xoá log', () => { devtools.clearLog(); this.refresh(); });
|
|
77
|
+
const btnClose = this.button('✕', () => this.close());
|
|
78
|
+
header.append(title, btnClear, btnClose);
|
|
79
|
+
|
|
80
|
+
const body = document.createElement('div');
|
|
81
|
+
body.setAttribute('data-role', 'body');
|
|
82
|
+
body.style.cssText = 'overflow:auto;padding:8px 10px';
|
|
83
|
+
|
|
84
|
+
panel.append(header, body);
|
|
85
|
+
return panel;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private button(label: string, onClick: () => void): HTMLButtonElement {
|
|
89
|
+
const b = document.createElement('button');
|
|
90
|
+
b.textContent = label;
|
|
91
|
+
b.style.cssText = 'background:#313244;color:#cdd6f4;border:1px solid #45475a;border-radius:4px;padding:2px 8px;cursor:pointer;font:inherit';
|
|
92
|
+
b.addEventListener('click', onClick);
|
|
93
|
+
return b;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private section(label: string): HTMLElement {
|
|
97
|
+
const h = document.createElement('div');
|
|
98
|
+
h.textContent = label;
|
|
99
|
+
h.style.cssText = 'margin:10px 0 4px;color:#a6adc8;text-transform:uppercase;font-size:10px;letter-spacing:.08em';
|
|
100
|
+
return h;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Vẽ nội dung ───────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
private refresh(): void {
|
|
106
|
+
const body = this.panel?.querySelector('[data-role="body"]') as HTMLElement | null;
|
|
107
|
+
if (!body) return;
|
|
108
|
+
body.replaceChildren();
|
|
109
|
+
|
|
110
|
+
const tree = devtools.getViewTree();
|
|
111
|
+
body.append(this.section(`Cây view (${tree.length} gốc)`));
|
|
112
|
+
if (tree.length === 0) {
|
|
113
|
+
body.append(this.muted('Chưa có view nào mount.'));
|
|
114
|
+
} else {
|
|
115
|
+
for (const node of tree) body.append(this.renderNode(node, 0));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const log = devtools.getLog().slice(-25).reverse();
|
|
119
|
+
body.append(this.section(`Sự kiện gần đây (${log.length})`));
|
|
120
|
+
if (log.length === 0) body.append(this.muted('Chưa có sự kiện.'));
|
|
121
|
+
else for (const e of log) body.append(this.renderEvent(e));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private muted(text: string): HTMLElement {
|
|
125
|
+
const d = document.createElement('div');
|
|
126
|
+
d.textContent = text;
|
|
127
|
+
d.style.cssText = 'color:#6c7086;padding:2px 0';
|
|
128
|
+
return d;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private renderNode(node: DevtoolsViewNode, depth: number): HTMLElement {
|
|
132
|
+
const wrap = document.createElement('div');
|
|
133
|
+
wrap.style.cssText = `margin-left:${depth * 12}px`;
|
|
134
|
+
|
|
135
|
+
const row = document.createElement('div');
|
|
136
|
+
row.style.cssText = 'display:flex;gap:6px;align-items:baseline;padding:2px 4px;border-radius:4px;cursor:pointer';
|
|
137
|
+
row.addEventListener('mouseenter', () => this.highlight(node.viewId));
|
|
138
|
+
row.addEventListener('mouseleave', () => this.clearHighlight());
|
|
139
|
+
row.addEventListener('click', () => {
|
|
140
|
+
this.selectedViewId = this.selectedViewId === node.viewId ? null : node.viewId;
|
|
141
|
+
this.refresh();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const path = document.createElement('span');
|
|
145
|
+
path.textContent = node.path || '(không tên)';
|
|
146
|
+
path.style.cssText = 'color:#a6e3a1;flex:1';
|
|
147
|
+
|
|
148
|
+
const meta = document.createElement('span');
|
|
149
|
+
meta.textContent = `${node.viewType} · ${node.lifecycleState} · ${node.elementCount}el`;
|
|
150
|
+
meta.style.cssText = `color:${node.lifecycleState === 'active' ? '#6c7086' : '#f9e2af'};font-size:10px`;
|
|
151
|
+
|
|
152
|
+
row.append(path, meta);
|
|
153
|
+
wrap.append(row);
|
|
154
|
+
|
|
155
|
+
// Bấm để mở/đóng state — mặc định đóng cho gọn khi cây lớn
|
|
156
|
+
if (this.selectedViewId === node.viewId) {
|
|
157
|
+
const keys = Object.keys(node.state);
|
|
158
|
+
if (keys.length === 0) {
|
|
159
|
+
wrap.append(this.muted(' (không có state)'));
|
|
160
|
+
} else {
|
|
161
|
+
for (const key of keys) {
|
|
162
|
+
const line = document.createElement('div');
|
|
163
|
+
line.style.cssText = 'display:flex;gap:6px;padding-left:10px';
|
|
164
|
+
const k = document.createElement('span');
|
|
165
|
+
k.textContent = key + ':';
|
|
166
|
+
k.style.cssText = 'color:#f5c2e7';
|
|
167
|
+
const v = document.createElement('span');
|
|
168
|
+
// textContent — state có thể chứa chuỗi do người dùng nhập
|
|
169
|
+
v.textContent = JSON.stringify(node.state[key]) ?? 'undefined';
|
|
170
|
+
v.style.cssText = 'color:#cdd6f4;word-break:break-all';
|
|
171
|
+
line.append(k, v);
|
|
172
|
+
wrap.append(line);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const child of node.children) wrap.append(this.renderNode(child, depth + 1));
|
|
178
|
+
return wrap;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private renderEvent(e: DevtoolsEvent): HTMLElement {
|
|
182
|
+
const colors: Record<string, string> = {
|
|
183
|
+
'view:mounted': '#a6e3a1', 'view:destroyed': '#f38ba8',
|
|
184
|
+
'state:changed': '#89b4fa', 'error': '#fab387',
|
|
185
|
+
};
|
|
186
|
+
const row = document.createElement('div');
|
|
187
|
+
row.style.cssText = 'display:flex;gap:6px;padding:1px 0';
|
|
188
|
+
|
|
189
|
+
const t = document.createElement('span');
|
|
190
|
+
t.textContent = String(e.at).padStart(6, ' ');
|
|
191
|
+
t.style.cssText = 'color:#6c7086;flex-shrink:0';
|
|
192
|
+
|
|
193
|
+
const type = document.createElement('span');
|
|
194
|
+
type.textContent = e.type;
|
|
195
|
+
type.style.cssText = `color:${colors[e.type] ?? '#cdd6f4'};flex-shrink:0`;
|
|
196
|
+
|
|
197
|
+
const detail = document.createElement('span');
|
|
198
|
+
detail.textContent = this.describe(e);
|
|
199
|
+
detail.style.cssText = 'color:#a6adc8;word-break:break-all';
|
|
200
|
+
|
|
201
|
+
row.append(t, type, detail);
|
|
202
|
+
return row;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
private describe(e: DevtoolsEvent): string {
|
|
206
|
+
if (e.type === 'state:changed') return `${e.path ?? ''} [${(e.detail?.keys ?? []).join(', ')}]`;
|
|
207
|
+
if (e.type === 'error') return `${e.detail?.phase ?? ''} ${e.path ?? ''}: ${e.detail?.message ?? ''}`;
|
|
208
|
+
return e.path ?? '';
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── Highlight vùng DOM của view theo marker ───────────────────
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* View không có 1 element bọc ngoài (kiến trúc marker, no-VDOM) → lấy
|
|
215
|
+
* bounding box của TẤT CẢ node giữa cặp marker `<!--s:v:{viewId}-s/-e-->`.
|
|
216
|
+
*/
|
|
217
|
+
private highlight(viewId: string): void {
|
|
218
|
+
this.clearHighlight();
|
|
219
|
+
const range = this.rangeOfView(viewId);
|
|
220
|
+
if (!range) return;
|
|
221
|
+
const rect = range.getBoundingClientRect();
|
|
222
|
+
if (rect.width === 0 && rect.height === 0) return;
|
|
223
|
+
|
|
224
|
+
const box = document.createElement('div');
|
|
225
|
+
box.id = HIGHLIGHT_ID;
|
|
226
|
+
box.style.cssText = [
|
|
227
|
+
'position:fixed', `left:${rect.left}px`, `top:${rect.top}px`,
|
|
228
|
+
`width:${rect.width}px`, `height:${rect.height}px`,
|
|
229
|
+
'background:rgba(137,180,250,.25)', 'border:1px solid #89b4fa',
|
|
230
|
+
'pointer-events:none', 'z-index:2147483646',
|
|
231
|
+
].join(';');
|
|
232
|
+
document.body.appendChild(box);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private rangeOfView(viewId: string): Range | null {
|
|
236
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_COMMENT);
|
|
237
|
+
let open: Comment | null = null;
|
|
238
|
+
let node: Comment | null;
|
|
239
|
+
while ((node = walker.nextNode() as Comment | null)) {
|
|
240
|
+
const v = node.nodeValue?.trim() ?? '';
|
|
241
|
+
if (v === `s:v:${viewId}-s`) { open = node; continue; }
|
|
242
|
+
if (open && v === `s:v:${viewId}-e`) {
|
|
243
|
+
const range = document.createRange();
|
|
244
|
+
range.setStartAfter(open);
|
|
245
|
+
range.setEndBefore(node);
|
|
246
|
+
return range;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
private clearHighlight(): void {
|
|
253
|
+
document.getElementById(HIGHLIGHT_ID)?.remove();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export const inspector = new Inspector();
|
|
258
|
+
|
|
259
|
+
/** Ctrl+Shift+D bật/tắt panel — chỉ gắn khi devtools đã enable. */
|
|
260
|
+
export function bindDevtoolsShortcut(): void {
|
|
261
|
+
if (typeof window === 'undefined') return;
|
|
262
|
+
window.addEventListener('keydown', (e) => {
|
|
263
|
+
if (e.ctrlKey && e.shiftKey && (e.key === 'D' || e.key === 'd')) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
inspector.toggle();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export default inspector;
|