neo.mjs 10.0.0-beta.4 → 10.0.0-beta.6
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/.github/RELEASE_NOTES/v10.0.0-beta.4.md +2 -2
- package/.github/RELEASE_NOTES/v10.0.0-beta.5.md +70 -0
- package/.github/RELEASE_NOTES/v10.0.0-beta.6.md +48 -0
- package/.github/epic-functional-components.md +498 -0
- package/.github/ticket-asymmetric-vdom-updates.md +122 -0
- package/README.md +0 -3
- package/ServiceWorker.mjs +2 -2
- package/apps/colors/store/Colors.mjs +1 -0
- package/apps/colors/view/GridContainer.mjs +3 -0
- package/apps/colors/view/HeaderToolbar.mjs +2 -0
- package/apps/colors/view/Viewport.mjs +3 -0
- package/apps/covid/view/FooterContainer.mjs +3 -0
- package/apps/covid/view/GalleryContainer.mjs +2 -0
- package/apps/covid/view/GalleryContainerController.mjs +1 -0
- package/apps/covid/view/HeaderContainer.mjs +2 -0
- package/apps/covid/view/HelixContainer.mjs +2 -0
- package/apps/covid/view/HelixContainerController.mjs +1 -0
- package/apps/covid/view/MainContainer.mjs +3 -0
- package/apps/covid/view/TableContainer.mjs +3 -0
- package/apps/covid/view/TableContainerController.mjs +1 -0
- package/apps/covid/view/WorldMapContainer.mjs +2 -0
- package/apps/covid/view/country/Gallery.mjs +3 -0
- package/apps/covid/view/country/Helix.mjs +8 -0
- package/apps/covid/view/country/HistoricalDataTable.mjs +1 -0
- package/apps/covid/view/country/Table.mjs +2 -0
- package/apps/covid/view/mapboxGl/Component.mjs +1 -0
- package/apps/covid/view/mapboxGl/Container.mjs +2 -0
- package/apps/email/EPIC_PLAN.md +58 -0
- package/apps/email/neo-config.json +2 -2
- package/apps/email/store/Emails.mjs +11 -1
- package/apps/email/view/ComposeView.mjs +44 -0
- package/apps/email/view/MainView.mjs +89 -0
- package/apps/email/view/Viewport.mjs +4 -33
- package/apps/email/view/ViewportStateProvider.mjs +3 -3
- package/apps/form/store/SideNav.mjs +1 -0
- package/apps/form/view/FormContainer.mjs +1 -0
- package/apps/form/view/FormPageContainer.mjs +2 -0
- package/apps/form/view/SideNavList.mjs +1 -0
- package/apps/form/view/Viewport.mjs +3 -0
- package/apps/portal/childapps/preview/MainContainer.mjs +1 -0
- package/apps/portal/index.html +1 -1
- package/apps/portal/store/BlogPosts.mjs +2 -0
- package/apps/portal/store/Content.mjs +1 -0
- package/apps/portal/store/ContentSections.mjs +1 -0
- package/apps/portal/store/Examples.mjs +1 -0
- package/apps/portal/view/HeaderToolbar.mjs +1 -0
- package/apps/portal/view/Viewport.mjs +5 -0
- package/apps/portal/view/ViewportController.mjs +8 -2
- package/apps/portal/view/about/Container.mjs +2 -0
- package/apps/portal/view/about/MemberContainer.mjs +7 -0
- package/apps/portal/view/blog/Container.mjs +2 -0
- package/apps/portal/view/blog/List.mjs +2 -0
- package/apps/portal/view/examples/List.mjs +1 -0
- package/apps/portal/view/examples/TabContainer.mjs +4 -0
- package/apps/portal/view/home/ContentBox.mjs +3 -0
- package/apps/portal/view/home/FeatureSection.mjs +8 -0
- package/apps/portal/view/home/FooterContainer.mjs +4 -1
- package/apps/portal/view/home/MainContainer.mjs +2 -0
- package/apps/portal/view/home/parts/AfterMath.mjs +2 -0
- package/apps/portal/view/home/parts/BaseContainer.mjs +1 -0
- package/apps/portal/view/home/parts/Colors.mjs +4 -0
- package/apps/portal/view/home/parts/Features.mjs +2 -0
- package/apps/portal/view/home/parts/Helix.mjs +5 -0
- package/apps/portal/view/home/parts/How.mjs +4 -0
- package/apps/portal/view/home/parts/MainNeo.mjs +1 -0
- package/apps/portal/view/home/parts/References.mjs +2 -0
- package/apps/portal/view/learn/ContentComponent.mjs +11 -5
- package/apps/portal/view/learn/ContentTreeList.mjs +2 -0
- package/apps/portal/view/learn/CubeLayoutButton.mjs +1 -0
- package/apps/portal/view/learn/MainContainer.mjs +4 -0
- package/apps/portal/view/learn/PageContainer.mjs +2 -0
- package/apps/portal/view/learn/PageSectionsContainer.mjs +3 -0
- package/apps/portal/view/learn/PageSectionsList.mjs +1 -0
- package/apps/portal/view/services/Component.mjs +1 -0
- package/apps/realworld/api/Base.mjs +1 -0
- package/apps/realworld/view/HeaderComponent.mjs +4 -0
- package/apps/realworld/view/HomeComponent.mjs +7 -0
- package/apps/realworld/view/MainContainer.mjs +2 -0
- package/apps/realworld/view/MainContainerController.mjs +2 -0
- package/apps/realworld/view/article/CommentComponent.mjs +3 -0
- package/apps/realworld/view/article/Component.mjs +17 -10
- package/apps/realworld/view/article/CreateCommentComponent.mjs +2 -0
- package/apps/realworld/view/article/CreateComponent.mjs +5 -0
- package/apps/realworld/view/article/PreviewComponent.mjs +9 -0
- package/apps/realworld/view/article/TagListComponent.mjs +2 -0
- package/apps/realworld/view/user/ProfileComponent.mjs +7 -0
- package/apps/realworld/view/user/SettingsComponent.mjs +5 -0
- package/apps/realworld/view/user/SignUpComponent.mjs +3 -0
- package/apps/realworld2/api/Base.mjs +1 -0
- package/apps/realworld2/view/FooterComponent.mjs +1 -0
- package/apps/realworld2/view/HeaderToolbar.mjs +3 -0
- package/apps/realworld2/view/HomeContainer.mjs +1 -0
- package/apps/realworld2/view/MainContainer.mjs +2 -0
- package/apps/realworld2/view/MainContainerController.mjs +1 -0
- package/apps/realworld2/view/article/Helix.mjs +1 -0
- package/apps/realworld2/view/article/PreviewComponent.mjs +9 -0
- package/apps/realworld2/view/article/PreviewList.mjs +1 -0
- package/apps/realworld2/view/article/TagListComponent.mjs +2 -0
- package/apps/route/view/CenterContainer.mjs +1 -0
- package/apps/route/view/MainView.mjs +1 -0
- package/apps/sharedcovid/childapps/sharedcovidchart/MainContainer.mjs +1 -0
- package/apps/sharedcovid/childapps/sharedcovidgallery/MainContainer.mjs +1 -0
- package/apps/sharedcovid/childapps/sharedcovidhelix/MainContainer.mjs +1 -0
- package/apps/sharedcovid/childapps/sharedcovidmap/MainContainer.mjs +1 -0
- package/apps/sharedcovid/view/FooterContainer.mjs +3 -0
- package/apps/sharedcovid/view/GalleryContainer.mjs +2 -0
- package/apps/sharedcovid/view/GalleryContainerController.mjs +1 -0
- package/apps/sharedcovid/view/HeaderContainer.mjs +2 -0
- package/apps/sharedcovid/view/HelixContainer.mjs +2 -0
- package/apps/sharedcovid/view/HelixContainerController.mjs +1 -0
- package/apps/sharedcovid/view/MainContainer.mjs +3 -0
- package/apps/sharedcovid/view/TableContainer.mjs +3 -0
- package/apps/sharedcovid/view/TableContainerController.mjs +1 -0
- package/apps/sharedcovid/view/WorldMapContainer.mjs +2 -0
- package/apps/sharedcovid/view/country/Gallery.mjs +3 -0
- package/apps/sharedcovid/view/country/Helix.mjs +8 -0
- package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -0
- package/apps/sharedcovid/view/country/Table.mjs +2 -0
- package/apps/sharedcovid/view/mapboxGl/Component.mjs +1 -0
- package/apps/sharedcovid/view/mapboxGl/Container.mjs +2 -0
- package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +2 -0
- package/apps/shareddialog/view/DemoDialog.mjs +2 -0
- package/apps/shareddialog/view/MainContainer.mjs +2 -0
- package/apps/shareddialog/view/MainContainerController.mjs +1 -0
- package/buildScripts/addReactiveTags.mjs +191 -0
- package/buildScripts/checkReactiveTags.mjs +160 -0
- package/docs/app/store/Api.mjs +1 -0
- package/docs/app/store/Examples.mjs +1 -0
- package/docs/app/view/ApiTreeList.mjs +1 -0
- package/docs/app/view/ContentTabContainer.mjs +2 -0
- package/docs/app/view/ExamplesTreeList.mjs +2 -0
- package/docs/app/view/HeaderContainer.mjs +3 -0
- package/docs/app/view/MainContainer.mjs +5 -0
- package/docs/app/view/classdetails/HeaderComponent.mjs +1 -0
- package/docs/app/view/classdetails/MainContainer.mjs +3 -0
- package/docs/app/view/classdetails/MembersList.mjs +5 -0
- package/docs/app/view/classdetails/SourceViewComponent.mjs +2 -0
- package/examples/ConfigurationViewport.mjs +14 -8
- package/examples/button/effect/MainContainer.mjs +207 -0
- package/examples/button/effect/app.mjs +6 -0
- package/examples/button/effect/index.html +11 -0
- package/examples/button/effect/neo-config.json +6 -0
- package/examples/calendar/weekview/MainContainer.mjs +4 -0
- package/examples/component/coronaGallery/CountryGallery.mjs +2 -0
- package/examples/component/coronaGallery/CountryStore.mjs +1 -0
- package/examples/component/coronaGallery/Viewport.mjs +3 -0
- package/examples/component/coronaGallery/ViewportController.mjs +1 -0
- package/examples/component/coronaHelix/CountryHelix.mjs +7 -0
- package/examples/component/coronaHelix/MainContainer.mjs +1 -0
- package/examples/component/gallery/ImageStore.mjs +1 -0
- package/examples/component/helix/ImageStore.mjs +1 -0
- package/examples/component/helix/Viewport.mjs +3 -0
- package/examples/component/helix/ViewportController.mjs +1 -0
- package/examples/component/multiWindowCoronaGallery/childapp/Viewport.mjs +1 -0
- package/examples/component/multiWindowHelix/childapp/Viewport.mjs +1 -0
- package/examples/component/wrapper/googleMaps/MapComponent.mjs +2 -0
- package/examples/core/config/MainContainer.mjs +2 -0
- package/examples/dialog/DemoDialog.mjs +2 -0
- package/examples/dialog/MainContainer.mjs +1 -0
- package/examples/form/field/color/MainStore.mjs +1 -0
- package/examples/functional/defineComponent/Component.mjs +18 -0
- package/examples/functional/defineComponent/MainContainer.mjs +41 -0
- package/examples/functional/defineComponent/app.mjs +6 -0
- package/examples/functional/defineComponent/index.html +11 -0
- package/examples/functional/defineComponent/neo-config.json +6 -0
- package/examples/functional/hostComponent/Component.mjs +32 -0
- package/examples/functional/hostComponent/MainContainer.mjs +48 -0
- package/examples/functional/hostComponent/app.mjs +6 -0
- package/examples/functional/hostComponent/index.html +11 -0
- package/examples/functional/hostComponent/neo-config.json +6 -0
- package/examples/grid/animatedRowSorting/Viewport.mjs +1 -1
- package/examples/grid/bigData/ControlsContainer.mjs +3 -0
- package/examples/grid/bigData/GridContainer.mjs +4 -2
- package/examples/grid/bigData/MainContainer.mjs +2 -0
- package/examples/grid/bigData/MainModel.mjs +1 -0
- package/examples/grid/bigData/MainStore.mjs +3 -0
- package/examples/grid/cellEditing/MainContainer.mjs +1 -1
- package/examples/grid/container/MainContainer.mjs +1 -1
- package/examples/grid/covid/GridContainer.mjs +3 -0
- package/examples/grid/covid/MainContainer.mjs +2 -0
- package/examples/grid/covid/Store.mjs +1 -0
- package/examples/grid/nestedRecordFields/EditUserDialog.mjs +3 -0
- package/examples/grid/nestedRecordFields/Viewport.mjs +3 -1
- package/examples/list/animate/List.mjs +4 -0
- package/examples/list/animate/MainContainer.mjs +2 -0
- package/examples/list/circle/MainStore.mjs +1 -0
- package/examples/list/color/MainStore.mjs +1 -0
- package/examples/preloadingAssets/view/MainContainer.mjs +2 -0
- package/examples/stateProvider/advanced/MainContainer.mjs +1 -0
- package/examples/stateProvider/dialog/EditUserDialog.mjs +2 -0
- package/examples/stateProvider/dialog/MainContainer.mjs +1 -0
- package/examples/stateProvider/extendedClass/MainContainer.mjs +2 -0
- package/examples/stateProvider/inline/MainContainer.mjs +1 -0
- package/examples/stateProvider/inlineNoStateProvider/MainContainer.mjs +1 -0
- package/examples/stateProvider/inlineNoStateProvider/MainContainerController.mjs +2 -0
- package/examples/stateProvider/multiWindow/EditUserDialog.mjs +3 -0
- package/examples/stateProvider/multiWindow/MainContainer.mjs +1 -0
- package/examples/stateProvider/multiWindow/Viewport.mjs +1 -0
- package/examples/stateProvider/nestedData/MainContainer.mjs +1 -0
- package/examples/stateProvider/table/MainContainer.mjs +1 -0
- package/examples/table/covid/MainContainer.mjs +2 -0
- package/examples/table/covid/Store.mjs +1 -0
- package/examples/table/covid/TableContainer.mjs +3 -0
- package/examples/table/nestedRecordFields/EditUserDialog.mjs +3 -0
- package/examples/table/nestedRecordFields/Viewport.mjs +1 -0
- package/examples/todoList/version1/MainComponent.mjs +1 -1
- package/examples/toolbar/breadcrumb/view/MainContainer.mjs +2 -0
- package/examples/toolbar/paging/store/Users.mjs +1 -0
- package/examples/toolbar/paging/view/AddUserDialog.mjs +3 -0
- package/examples/toolbar/paging/view/MainContainer.mjs +3 -0
- package/examples/treeAccordion/MainContainer.mjs +2 -2
- package/examples/worker/task/MainContainer.mjs +1 -0
- package/learn/Glossary.md +1 -0
- package/learn/UsingTheseTopics.md +1 -0
- package/learn/benefits/ConfigSystem.md +2 -0
- package/learn/benefits/Effort.md +1 -0
- package/learn/benefits/Features.md +1 -0
- package/learn/benefits/FormsEngine.md +1 -0
- package/learn/benefits/FourEnvironments.md +2 -0
- package/learn/benefits/Introduction.md +2 -0
- package/learn/benefits/MultiWindow.md +3 -1
- package/learn/benefits/OffTheMainThread.md +2 -0
- package/learn/benefits/Quick.md +2 -0
- package/learn/benefits/RPCLayer.md +2 -0
- package/learn/benefits/Speed.md +2 -0
- package/learn/comparisons/NeoVsAngular.md +90 -0
- package/learn/comparisons/NeoVsExtJs.md +178 -0
- package/learn/comparisons/NeoVsNextJs.md +124 -0
- package/learn/comparisons/NeoVsReact.md +95 -0
- package/learn/comparisons/NeoVsSolid.md +78 -0
- package/learn/comparisons/NeoVsVue.md +92 -0
- package/learn/comparisons/Overview.md +46 -0
- package/learn/gettingstarted/ComponentModels.md +2 -0
- package/learn/gettingstarted/Config.md +2 -0
- package/learn/gettingstarted/DescribingTheUI.md +2 -0
- package/learn/gettingstarted/Events.md +2 -0
- package/learn/gettingstarted/Extending.md +2 -0
- package/learn/gettingstarted/References.md +2 -0
- package/learn/gettingstarted/Setup.md +3 -2
- package/learn/gettingstarted/Workspaces.md +2 -0
- package/learn/guides/datahandling/Collections.md +1 -0
- package/learn/guides/datahandling/Records.md +1 -0
- package/learn/guides/datahandling/StateProviders.md +131 -16
- package/learn/guides/datahandling/Tables.md +1 -1
- package/learn/guides/fundamentals/ConfigSystemDeepDive.md +1 -0
- package/learn/guides/fundamentals/DeclarativeComponentTreesVsImperativeVdom.md +2 -0
- package/learn/guides/fundamentals/DeclarativeVDOMWithEffects.md +168 -0
- package/learn/guides/fundamentals/ExtendingNeoClasses.md +1 -0
- package/learn/guides/fundamentals/InstanceLifecycle.md +3 -1
- package/learn/guides/fundamentals/MainThreadAddons.md +2 -0
- package/learn/guides/specificfeatures/Mixins.md +3 -1
- package/learn/guides/specificfeatures/MultiWindow.md +3 -1
- package/learn/guides/specificfeatures/PortalApp.md +2 -0
- package/learn/guides/uibuildingblocks/ComponentsAndContainers.md +2 -0
- package/learn/guides/uibuildingblocks/CustomComponents.md +2 -0
- package/learn/guides/uibuildingblocks/Layouts.md +2 -0
- package/learn/guides/uibuildingblocks/WorkingWithVDom.md +2 -0
- package/learn/guides/userinteraction/Forms.md +2 -0
- package/learn/guides/userinteraction/events/CustomEvents.md +2 -1
- package/learn/guides/userinteraction/events/DomEvents.md +2 -0
- package/learn/javascript/ClassFeatures.md +4 -3
- package/learn/javascript/Classes.md +10 -13
- package/learn/javascript/Overrides.md +10 -6
- package/learn/javascript/Super.md +12 -8
- package/learn/tree.json +71 -63
- package/learn/tutorials/Earthquakes.md +2 -0
- package/learn/tutorials/RSP.md +3 -1
- package/learn/tutorials/TodoList.md +103 -7
- package/package.json +6 -4
- package/resources/scss/src/apps/email/ComposeView.scss +16 -0
- package/resources/scss/src/apps/email/MainView.scss +5 -0
- package/resources/scss/src/apps/portal/learn/ContentComponent.scss +5 -4
- package/src/DefaultConfig.mjs +12 -2
- package/src/Main.mjs +1 -0
- package/src/Neo.mjs +377 -178
- package/src/Xhr.mjs +1 -0
- package/src/button/Base.mjs +13 -0
- package/src/button/Effect.mjs +449 -0
- package/src/button/Split.mjs +2 -0
- package/src/calendar/store/Calendars.mjs +1 -0
- package/src/calendar/store/Colors.mjs +1 -0
- package/src/calendar/store/Events.mjs +1 -0
- package/src/calendar/view/DayComponent.mjs +2 -0
- package/src/calendar/view/EditEventContainer.mjs +4 -1
- package/src/calendar/view/MainContainer.mjs +13 -0
- package/src/calendar/view/MainContainerStateProvider.mjs +14 -28
- package/src/calendar/view/SettingsContainer.mjs +1 -0
- package/src/calendar/view/YearComponent.mjs +16 -0
- package/src/calendar/view/calendars/ColorsList.mjs +2 -0
- package/src/calendar/view/calendars/Container.mjs +2 -0
- package/src/calendar/view/calendars/EditContainer.mjs +1 -0
- package/src/calendar/view/month/Component.mjs +11 -0
- package/src/calendar/view/settings/GeneralContainer.mjs +1 -0
- package/src/calendar/view/settings/MonthContainer.mjs +1 -0
- package/src/calendar/view/settings/WeekContainer.mjs +1 -0
- package/src/calendar/view/settings/YearContainer.mjs +1 -0
- package/src/calendar/view/week/Component.mjs +15 -1
- package/src/calendar/view/week/TimeAxisComponent.mjs +4 -0
- package/src/code/LivePreview.mjs +51 -23
- package/src/collection/Base.mjs +14 -12
- package/src/collection/Filter.mjs +6 -0
- package/src/collection/Sorter.mjs +3 -0
- package/src/component/Base.mjs +156 -802
- package/src/component/Canvas.mjs +1 -0
- package/src/component/Chip.mjs +4 -0
- package/src/component/Circle.mjs +14 -0
- package/src/component/Clock.mjs +4 -0
- package/src/component/DateSelector.mjs +12 -0
- package/src/component/Gallery.mjs +11 -0
- package/src/component/Helix.mjs +24 -0
- package/src/component/Label.mjs +1 -0
- package/src/component/Legend.mjs +3 -0
- package/src/component/MagicMoveText.mjs +4 -0
- package/src/component/Progress.mjs +3 -0
- package/src/component/Splitter.mjs +3 -0
- package/src/component/StatusBadge.mjs +6 -0
- package/src/component/Timer.mjs +4 -0
- package/src/component/Toast.mjs +6 -0
- package/src/component/Video.mjs +1 -0
- package/src/component/mwc/Button.mjs +7 -0
- package/src/component/mwc/TextField.mjs +9 -0
- package/src/component/wrapper/AmChart.mjs +2 -0
- package/src/component/wrapper/GoogleMaps.mjs +3 -0
- package/src/component/wrapper/MapboxGL.mjs +5 -0
- package/src/component/wrapper/MonacoEditor.mjs +12 -0
- package/src/container/Accordion.mjs +2 -0
- package/src/container/Base.mjs +34 -26
- package/src/container/Panel.mjs +1 -0
- package/src/container/Viewport.mjs +1 -0
- package/src/controller/Application.mjs +1 -0
- package/src/controller/Base.mjs +1 -0
- package/src/controller/Component.mjs +1 -0
- package/src/core/Base.mjs +193 -22
- package/src/core/Compare.mjs +4 -7
- package/src/core/Config.mjs +137 -33
- package/src/core/Effect.mjs +193 -0
- package/src/core/EffectBatchManager.mjs +67 -0
- package/src/core/EffectManager.mjs +60 -0
- package/src/core/IdGenerator.mjs +13 -44
- package/src/data/Model.mjs +2 -0
- package/src/data/Store.mjs +7 -0
- package/src/data/connection/WebSocket.mjs +2 -0
- package/src/date/DayViewComponent.mjs +2 -0
- package/src/date/SelectorContainer.mjs +14 -0
- package/src/dialog/Base.mjs +8 -0
- package/src/draggable/DragZone.mjs +5 -0
- package/src/draggable/tree/DragZone.mjs +1 -0
- package/src/filter/BooleanContainer.mjs +2 -0
- package/src/filter/NumberContainer.mjs +3 -0
- package/src/filter/ToggleOperatorsButton.mjs +2 -0
- package/src/form/Fieldset.mjs +6 -0
- package/src/form/field/Base.mjs +7 -0
- package/src/form/field/CheckBox.mjs +18 -0
- package/src/form/field/Chip.mjs +1 -0
- package/src/form/field/ComboBox.mjs +8 -0
- package/src/form/field/Country.mjs +1 -0
- package/src/form/field/Currency.mjs +2 -0
- package/src/form/field/Date.mjs +4 -0
- package/src/form/field/Display.mjs +1 -0
- package/src/form/field/Email.mjs +1 -0
- package/src/form/field/FileUpload.mjs +7 -0
- package/src/form/field/Hidden.mjs +1 -0
- package/src/form/field/Number.mjs +7 -0
- package/src/form/field/Password.mjs +1 -0
- package/src/form/field/Phone.mjs +3 -0
- package/src/form/field/Picker.mjs +2 -0
- package/src/form/field/Radio.mjs +1 -0
- package/src/form/field/Range.mjs +3 -0
- package/src/form/field/Search.mjs +2 -0
- package/src/form/field/Text.mjs +32 -0
- package/src/form/field/TextArea.mjs +7 -0
- package/src/form/field/Time.mjs +6 -0
- package/src/form/field/Url.mjs +3 -0
- package/src/form/field/ZipCode.mjs +2 -0
- package/src/form/field/trigger/Base.mjs +3 -0
- package/src/form/field/trigger/Clear.mjs +2 -0
- package/src/form/field/trigger/CopyToClipboard.mjs +2 -0
- package/src/form/field/trigger/Date.mjs +1 -0
- package/src/form/field/trigger/Picker.mjs +1 -0
- package/src/form/field/trigger/Search.mjs +1 -0
- package/src/form/field/trigger/SpinDown.mjs +2 -0
- package/src/form/field/trigger/SpinUp.mjs +1 -0
- package/src/form/field/trigger/Time.mjs +2 -0
- package/src/functional/_export.mjs +6 -0
- package/src/functional/component/Base.mjs +499 -0
- package/src/functional/defineComponent.mjs +102 -0
- package/src/functional/useConfig.mjs +52 -0
- package/src/functional/useEvent.mjs +43 -0
- package/src/grid/Body.mjs +20 -1
- package/src/grid/Container.mjs +57 -63
- package/src/grid/ScrollManager.mjs +2 -0
- package/src/grid/VerticalScrollbar.mjs +2 -0
- package/src/grid/column/Base.mjs +2 -0
- package/src/grid/column/Component.mjs +1 -1
- package/src/grid/header/Button.mjs +7 -0
- package/src/grid/header/Toolbar.mjs +6 -0
- package/src/grid/plugin/AnimateRows.mjs +2 -0
- package/src/layout/Base.mjs +3 -0
- package/src/layout/Card.mjs +1 -0
- package/src/layout/Cube.mjs +11 -1
- package/src/layout/Fit.mjs +1 -0
- package/src/layout/Flexbox.mjs +7 -0
- package/src/layout/Form.mjs +2 -0
- package/src/layout/Grid.mjs +1 -0
- package/src/layout/HBox.mjs +1 -0
- package/src/layout/VBox.mjs +1 -0
- package/src/list/Base.mjs +13 -0
- package/src/list/Chip.mjs +1 -0
- package/src/list/Circle.mjs +2 -0
- package/src/list/Color.mjs +1 -0
- package/src/list/plugin/Animate.mjs +2 -0
- package/src/main/DeltaUpdates.mjs +1 -0
- package/src/main/DomEvents.mjs +2 -0
- package/src/main/addon/CloneNode.mjs +1 -0
- package/src/main/addon/Cookie.mjs +1 -0
- package/src/main/addon/GoogleMaps.mjs +1 -0
- package/src/main/addon/LocalStorage.mjs +1 -0
- package/src/main/addon/MapboxGL.mjs +1 -0
- package/src/main/addon/Markdown.mjs +1 -0
- package/src/main/addon/Navigator.mjs +1 -0
- package/src/main/addon/Popover.mjs +1 -0
- package/src/main/addon/Stylesheet.mjs +1 -0
- package/src/main/addon/WindowPosition.mjs +1 -0
- package/src/manager/Component.mjs +0 -71
- package/src/manager/VDomUpdate.mjs +235 -0
- package/src/menu/List.mjs +6 -0
- package/src/menu/Model.mjs +1 -0
- package/src/menu/Panel.mjs +3 -0
- package/src/menu/Store.mjs +1 -0
- package/src/mixin/DomEvents.mjs +130 -0
- package/src/mixin/VdomLifecycle.mjs +667 -0
- package/src/plugin/Base.mjs +1 -0
- package/src/plugin/Resizable.mjs +2 -0
- package/src/selection/Model.mjs +15 -18
- package/src/selection/grid/BaseModel.mjs +1 -0
- package/src/sitemap/Component.mjs +1 -0
- package/src/state/Provider.mjs +376 -457
- package/src/state/createHierarchicalDataProxy.mjs +138 -0
- package/src/tab/Container.mjs +6 -0
- package/src/tab/Strip.mjs +1 -0
- package/src/tab/header/Button.mjs +2 -0
- package/src/tab/header/EffectButton.mjs +77 -0
- package/src/tab/header/Toolbar.mjs +1 -0
- package/src/table/Body.mjs +3 -0
- package/src/table/Container.mjs +10 -0
- package/src/table/header/Button.mjs +8 -0
- package/src/table/header/Toolbar.mjs +5 -0
- package/src/table/plugin/CellEditing.mjs +1 -0
- package/src/toolbar/Base.mjs +4 -0
- package/src/toolbar/Breadcrumb.mjs +3 -0
- package/src/toolbar/Paging.mjs +5 -0
- package/src/tooltip/Base.mjs +2 -0
- package/src/tree/List.mjs +3 -0
- package/src/util/HashHistory.mjs +1 -0
- package/src/util/KeyNavigation.mjs +2 -0
- package/src/util/Matrix.mjs +1 -0
- package/src/util/VDom.mjs +7 -1
- package/src/util/VNode.mjs +7 -1
- package/src/util/vdom/TreeBuilder.mjs +129 -0
- package/src/vdom/Helper.mjs +44 -33
- package/src/vdom/VNode.mjs +5 -7
- package/src/worker/App.mjs +1 -5
- package/src/worker/Base.mjs +2 -0
- package/src/worker/Manager.mjs +2 -0
- package/src/worker/ServiceBase.mjs +6 -1
- package/test/siesta/siesta.js +36 -1
- package/test/siesta/tests/CollectionBase.mjs +10 -10
- package/test/siesta/tests/VdomCalendar.mjs +13 -9
- package/test/siesta/tests/VdomHelper.mjs +22 -59
- package/test/siesta/tests/config/AfterSetConfig.mjs +100 -0
- package/test/siesta/tests/{ReactiveConfigs.mjs → config/Basic.mjs} +58 -21
- package/test/siesta/tests/config/CircularDependencies.mjs +166 -0
- package/test/siesta/tests/config/CustomFunctions.mjs +69 -0
- package/test/siesta/tests/config/Hierarchy.mjs +94 -0
- package/test/siesta/tests/config/MemoryLeak.mjs +92 -0
- package/test/siesta/tests/config/MultiLevelHierarchy.mjs +85 -0
- package/test/siesta/tests/core/Effect.mjs +127 -0
- package/test/siesta/tests/core/EffectBatching.mjs +310 -0
- package/test/siesta/tests/neo/MixinStaticConfig.mjs +138 -0
- package/test/siesta/tests/state/Provider.mjs +537 -0
- package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +255 -0
- package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +204 -0
- package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +366 -0
- package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +249 -0
- package/learn/javascript/NewNode.md +0 -31
package/src/core/Base.mjs
CHANGED
@@ -3,7 +3,8 @@ import Compare from '../core/Co
|
|
3
3
|
import Util from '../core/Util.mjs';
|
4
4
|
import Config from './Config.mjs';
|
5
5
|
import {isDescriptor} from './ConfigSymbols.mjs';
|
6
|
-
import IdGenerator from './IdGenerator.mjs'
|
6
|
+
import IdGenerator from './IdGenerator.mjs';
|
7
|
+
import EffectBatchManager from './EffectBatchManager.mjs';
|
7
8
|
|
8
9
|
const configSymbol = Symbol.for('configSymbol'),
|
9
10
|
forceAssignConfigs = Symbol('forceAssignConfigs'),
|
@@ -57,7 +58,26 @@ class Base {
|
|
57
58
|
*/
|
58
59
|
static overwrittenMethods = {}
|
59
60
|
/**
|
60
|
-
*
|
61
|
+
* Defines the default configuration properties for the class. These configurations are
|
62
|
+
* merged throughout the class hierarchy and can be overridden at the instance level.
|
63
|
+
*
|
64
|
+
* There are two main types of configs:
|
65
|
+
*
|
66
|
+
* 1. **Reactive Configs:** Property names ending with a trailing underscore (e.g., `myConfig_`).
|
67
|
+
* The framework automatically generates a public getter and setter, removing the underscore
|
68
|
+
* from the property name (e.g., `this.myConfig`). This system enables powerful, optional
|
69
|
+
* lifecycle hooks that are called automatically if they are implemented on the class:
|
70
|
+
* - `beforeGetMyConfig(value)`: Executed before the getter returns. Can be used to dynamically modify the returned value.
|
71
|
+
* - `beforeSetMyConfig(newValue, oldValue)`: Executed before a new value is set. Can be used for validation or transformation. Returning `undefined` from this hook will cancel the update.
|
72
|
+
* - `afterSetMyConfig(newValue, oldValue)`: Executed after a new value has been successfully set. Ideal for triggering side effects.
|
73
|
+
*
|
74
|
+
* 2. **Non-Reactive (Prototype-based) Configs:** Property names without a trailing underscore.
|
75
|
+
* These are applied directly to the class's **prototype** during the `Neo.setupClass`
|
76
|
+
* process. This is highly memory-efficient as the value is shared across all instances.
|
77
|
+
* It also allows for powerful, application-wide modifications of default behaviors
|
78
|
+
* by using the `Neo.overwrites` mechanism, which modifies these prototype values at
|
79
|
+
* load time.
|
80
|
+
*
|
61
81
|
* @returns {Object} config
|
62
82
|
*/
|
63
83
|
static config = {
|
@@ -82,6 +102,7 @@ class Base {
|
|
82
102
|
/**
|
83
103
|
* The unique component id
|
84
104
|
* @member {String|null} id_=null
|
105
|
+
* @reactive
|
85
106
|
*/
|
86
107
|
id_: null,
|
87
108
|
/**
|
@@ -98,12 +119,21 @@ class Base {
|
|
98
119
|
* @protected
|
99
120
|
*/
|
100
121
|
isConstructed: false,
|
122
|
+
/**
|
123
|
+
* This config will be set to `true` as the very first action within the `destroy()` method.
|
124
|
+
* Effects can observe this config to clean themselves up.
|
125
|
+
* @member {Boolean} isDestroying_=false
|
126
|
+
* @protected
|
127
|
+
* @reactive
|
128
|
+
*/
|
129
|
+
isDestroying_: false,
|
101
130
|
/**
|
102
131
|
* The config will get set to `true` once the Promise of `async initAsync()` is resolved.
|
103
132
|
* You can use `afterSetIsReady()` to get notified once the ready state is reached.
|
104
133
|
* Since not all classes use the Observable mixin, Neo will not fire an event.
|
105
134
|
* method body.
|
106
|
-
* @member {Boolean}
|
135
|
+
* @member {Boolean} isReady_=false
|
136
|
+
* @reactive
|
107
137
|
*/
|
108
138
|
isReady_: false,
|
109
139
|
/**
|
@@ -125,6 +155,7 @@ class Base {
|
|
125
155
|
*
|
126
156
|
* @member {Object|null} remote_=null
|
127
157
|
* @protected
|
158
|
+
* @reactive
|
128
159
|
*/
|
129
160
|
remote_: null
|
130
161
|
}
|
@@ -135,6 +166,12 @@ class Base {
|
|
135
166
|
* @private
|
136
167
|
*/
|
137
168
|
#configs = {};
|
169
|
+
/**
|
170
|
+
* Internal cache for all config subscription cleanup functions.
|
171
|
+
* @member {Function[]} #configSubscriptionCleanups=[]
|
172
|
+
* @private
|
173
|
+
*/
|
174
|
+
#configSubscriptionCleanups = []
|
138
175
|
/**
|
139
176
|
* Internal cache for all timeout ids when using this.timeout()
|
140
177
|
* @member {Number[]} timeoutIds=[]
|
@@ -239,7 +276,7 @@ class Base {
|
|
239
276
|
if (oldValue) {
|
240
277
|
if (hasManager) {
|
241
278
|
Neo.manager.Instance.unregister(oldValue)
|
242
|
-
} else {
|
279
|
+
} else if (Neo.idMap) {
|
243
280
|
delete Neo.idMap[oldValue]
|
244
281
|
}
|
245
282
|
}
|
@@ -249,7 +286,7 @@ class Base {
|
|
249
286
|
Neo.manager.Instance.register(me);
|
250
287
|
} else {
|
251
288
|
Neo.idMap ??= {};
|
252
|
-
Neo.idMap[
|
289
|
+
Neo.idMap[value] = me
|
253
290
|
}
|
254
291
|
}
|
255
292
|
}
|
@@ -276,9 +313,38 @@ class Base {
|
|
276
313
|
}
|
277
314
|
|
278
315
|
/**
|
279
|
-
*
|
280
|
-
*
|
316
|
+
* This static method is called by `Neo.setupClass()` during the class creation process.
|
317
|
+
* It allows for modifying a class's default prototype-based configs from outside the
|
318
|
+
* class hierarchy, which is a powerful way to avoid boilerplate code.
|
319
|
+
*
|
320
|
+
* It looks for a matching entry in the global `Neo.overwrites` object based on the
|
321
|
+
* class's `className`. If found, it merges the properties from the overwrite object
|
322
|
+
* into the class's static `config`. This provides a powerful mechanism for theming
|
323
|
+
* or applying application-wide customizations to framework or library classes without
|
324
|
+
* needing to extend them.
|
325
|
+
*
|
326
|
+
* @example
|
327
|
+
* // Imagine you have hundreds of buttons in your app, and you want all of them
|
328
|
+
* // to have `labelPosition: 'top'` instead of the default `'left'`.
|
329
|
+
* // Instead of configuring each instance, you can define an overwrite.
|
330
|
+
*
|
331
|
+
* // inside an Overwrites.mjs file loaded by your app:
|
332
|
+
* Neo.overwrites = {
|
333
|
+
* Neo: {
|
334
|
+
* button: {
|
335
|
+
* Base: {
|
336
|
+
* labelPosition: 'top'
|
337
|
+
* }
|
338
|
+
* }
|
339
|
+
* }
|
340
|
+
* };
|
341
|
+
*
|
342
|
+
* // Now, every `Neo.button.Base` (and any class that extends it) will have this
|
343
|
+
* // new default value on its prototype.
|
344
|
+
*
|
345
|
+
* @param {Object} cfg The static `config` object of the class being processed.
|
281
346
|
* @protected
|
347
|
+
* @static
|
282
348
|
*/
|
283
349
|
static applyOverwrites(cfg) {
|
284
350
|
let overwrites = Neo.ns(cfg.className, false, Neo.overwrites),
|
@@ -386,10 +452,16 @@ class Base {
|
|
386
452
|
destroy() {
|
387
453
|
let me = this;
|
388
454
|
|
455
|
+
me.isDestroying = true;
|
456
|
+
|
389
457
|
me.#timeoutIds.forEach(id => {
|
390
458
|
clearTimeout(id)
|
391
459
|
});
|
392
460
|
|
461
|
+
me.#configSubscriptionCleanups.forEach(cleanup => {
|
462
|
+
cleanup()
|
463
|
+
});
|
464
|
+
|
393
465
|
if (Base.instanceManagerAvailable === true) {
|
394
466
|
Neo.manager.Instance.unregister(me)
|
395
467
|
} else if (Neo.idMap) {
|
@@ -423,7 +495,7 @@ class Base {
|
|
423
495
|
let me = this;
|
424
496
|
|
425
497
|
if (!me.#configs[key] && me.isConfig(key)) {
|
426
|
-
me.#configs[key] = new Config()
|
498
|
+
me.#configs[key] = new Config(me.constructor.configDescriptors?.[key])
|
427
499
|
}
|
428
500
|
|
429
501
|
return me.#configs[key]
|
@@ -488,7 +560,7 @@ class Base {
|
|
488
560
|
Object.assign(me[configSymbol], me.mergeConfig(config, preventOriginalConfig));
|
489
561
|
delete me[configSymbol].id;
|
490
562
|
me.processConfigs();
|
491
|
-
me.isConfiguring = false
|
563
|
+
me.isConfiguring = false
|
492
564
|
}
|
493
565
|
|
494
566
|
/**
|
@@ -524,10 +596,12 @@ class Base {
|
|
524
596
|
* @returns {Boolean}
|
525
597
|
*/
|
526
598
|
isConfig(key) {
|
527
|
-
|
599
|
+
let me = this;
|
600
|
+
// If a `core.Config` controller is already created, return true (fastest possible check).
|
601
|
+
// If not, a config is considered "reactive" if it has a generated property setter
|
528
602
|
// AND it is present as a defined config in the merged static config hierarchy.
|
529
603
|
// Neo.setupClass() removes the underscore from the static config keys.
|
530
|
-
return Neo.hasPropertySetter(
|
604
|
+
return me.#configs[key] || (Neo.hasPropertySetter(me, key) && (key in me.constructor.config))
|
531
605
|
}
|
532
606
|
|
533
607
|
/**
|
@@ -539,7 +613,8 @@ class Base {
|
|
539
613
|
*/
|
540
614
|
mergeConfig(config, preventOriginalConfig) {
|
541
615
|
let me = this,
|
542
|
-
ctor = me.constructor
|
616
|
+
ctor = me.constructor,
|
617
|
+
configDescriptors, staticConfig;
|
543
618
|
|
544
619
|
if (!ctor.config) {
|
545
620
|
throw new Error('Neo.applyClassConfig has not been run on ' + me.className)
|
@@ -549,7 +624,70 @@ class Base {
|
|
549
624
|
me.originalConfig = Neo.clone(config, true, true)
|
550
625
|
}
|
551
626
|
|
552
|
-
|
627
|
+
configDescriptors = ctor.configDescriptors;
|
628
|
+
staticConfig = ctor.config;
|
629
|
+
|
630
|
+
if (configDescriptors) {
|
631
|
+
Object.entries(config).forEach(([key, instanceValue]) => {
|
632
|
+
const descriptor = configDescriptors[key];
|
633
|
+
|
634
|
+
if (descriptor?.merge) {
|
635
|
+
config[key] = Neo.mergeConfig(staticConfig[key], instanceValue, descriptor.merge)
|
636
|
+
}
|
637
|
+
})
|
638
|
+
}
|
639
|
+
|
640
|
+
return {...staticConfig, ...config}
|
641
|
+
}
|
642
|
+
|
643
|
+
/**
|
644
|
+
* Subscribes *this* instance (the subscriber) to changes of a specific config property on another instance (the publisher).
|
645
|
+
* Ensures automatic cleanup when *this* instance (the subscriber) is destroyed.
|
646
|
+
*
|
647
|
+
* @param {String|Neo.core.Base} publisher - The ID of the publisher instance or the instance reference itself.
|
648
|
+
* @param {String} configName - The name of the config property on the publisher to subscribe to (e.g., 'myConfig').
|
649
|
+
* @param {Function} fn - The callback function to execute when the config changes.
|
650
|
+
* @returns {Function} A cleanup function to manually unsubscribe if needed before this instance's destruction.
|
651
|
+
*
|
652
|
+
* @example
|
653
|
+
* // Subscribing to a config on another instance
|
654
|
+
* this.observeConfig(someOtherInstance, 'myConfig', (newValue, oldValue) => {
|
655
|
+
* console.log('myConfig changed:', newValue);
|
656
|
+
* });
|
657
|
+
*
|
658
|
+
* // Discouraged: Self-observation. Use afterSet<ConfigName>() hooks instead.
|
659
|
+
* this.observeConfig(this, 'myOwnConfig', (newValue, oldValue) => {
|
660
|
+
* console.log('myOwnConfig changed:', newValue);
|
661
|
+
* });
|
662
|
+
*/
|
663
|
+
observeConfig(publisher, configName, fn) {
|
664
|
+
let publisherInstance = publisher;
|
665
|
+
|
666
|
+
if (Neo.isString(publisher)) {
|
667
|
+
publisherInstance = Neo.get(publisher);
|
668
|
+
if (!publisherInstance) {
|
669
|
+
console.warn(`Publisher instance with ID '${publisher}' not found. Cannot subscribe.`);
|
670
|
+
return Neo.emptyFn
|
671
|
+
}
|
672
|
+
}
|
673
|
+
|
674
|
+
if (!(publisherInstance instanceof Neo.core.Base)) {
|
675
|
+
console.warn(`Invalid publisher provided. Must be a Neo.core.Base instance or its ID.`);
|
676
|
+
return Neo.emptyFn
|
677
|
+
}
|
678
|
+
|
679
|
+
const configController = publisherInstance.getConfig(configName);
|
680
|
+
|
681
|
+
if (!configController) {
|
682
|
+
console.warn(`Config '${configName}' not found on publisher instance ${publisherInstance.id}. Cannot subscribe.`);
|
683
|
+
return Neo.emptyFn
|
684
|
+
}
|
685
|
+
|
686
|
+
const cleanup = configController.subscribe({id: this.id, fn});
|
687
|
+
|
688
|
+
this.#configSubscriptionCleanups.push(cleanup);
|
689
|
+
|
690
|
+
return cleanup
|
553
691
|
}
|
554
692
|
|
555
693
|
/**
|
@@ -670,24 +808,58 @@ class Base {
|
|
670
808
|
}
|
671
809
|
|
672
810
|
/**
|
673
|
-
*
|
811
|
+
* set() accepts the following input as keys:
|
812
|
+
* 1. Non-reactive configs
|
813
|
+
* 2. Reactive configs
|
814
|
+
* 3. Class fields defined via value
|
815
|
+
* 4. Class fields defined via get() & set()
|
816
|
+
* 5. "Anything else" will get directly get assigned to the instance
|
817
|
+
*
|
818
|
+
* The logic resolves circular dependencies as good as possible and ensures that config related hooks:
|
819
|
+
* - beforeGet<Config>
|
820
|
+
* - beforeSet<Config>
|
821
|
+
* - afterSet<Config>
|
822
|
+
* can access all new values from the batch operation.
|
674
823
|
* @param {Object} values={}
|
675
824
|
*/
|
676
825
|
set(values={}) {
|
677
|
-
let me
|
826
|
+
let me = this,
|
827
|
+
classFieldsViaSet = {};
|
828
|
+
|
829
|
+
// Start batching for effects
|
830
|
+
EffectBatchManager.startBatch();
|
678
831
|
|
679
832
|
values = me.setFields(values);
|
680
833
|
|
681
834
|
// If the initial config processing is still running,
|
682
835
|
// finish this one first before dropping new values into the configSymbol.
|
683
|
-
//
|
836
|
+
// See: https://github.com/neomjs/neo/issues/2201
|
684
837
|
if (me[forceAssignConfigs] !== true && Object.keys(me[configSymbol]).length > 0) {
|
685
838
|
me.processConfigs()
|
686
839
|
}
|
687
840
|
|
841
|
+
// Store class fields which are defined via get() & set() and ensure they won't get added to the config symbol.
|
842
|
+
Object.entries(values).forEach(([key, value]) => {
|
843
|
+
if (!me.isConfig(key)) {
|
844
|
+
classFieldsViaSet[key] = value;
|
845
|
+
delete values[key]
|
846
|
+
}
|
847
|
+
})
|
848
|
+
|
849
|
+
// Add reactive configs to the configSymbol
|
688
850
|
Object.assign(me[configSymbol], values);
|
689
851
|
|
690
|
-
|
852
|
+
// Process class fields which are defined via get() & set() => now they can access the latest values
|
853
|
+
// for reactive and non-reactive configs, as well as class fields defined with values.
|
854
|
+
Object.entries(classFieldsViaSet).forEach(([key, value]) => {
|
855
|
+
me[key] = value
|
856
|
+
})
|
857
|
+
|
858
|
+
// Process reactive configs
|
859
|
+
me.processConfigs(true);
|
860
|
+
|
861
|
+
// End batching for effects
|
862
|
+
EffectBatchManager.endBatch();
|
691
863
|
}
|
692
864
|
|
693
865
|
/**
|
@@ -698,15 +870,14 @@ class Base {
|
|
698
870
|
* @protected
|
699
871
|
*/
|
700
872
|
setFields(config) {
|
701
|
-
let me
|
702
|
-
configNames = me.constructor.config;
|
873
|
+
let me = this;
|
703
874
|
|
704
875
|
Object.entries(config).forEach(([key, value]) => {
|
705
|
-
if (!
|
876
|
+
if (!me.isConfig(key) && !Neo.hasPropertySetter(me, key)) {
|
706
877
|
me[key] = value;
|
707
878
|
delete config[key]
|
708
879
|
}
|
709
|
-
})
|
880
|
+
});
|
710
881
|
|
711
882
|
return config
|
712
883
|
}
|
@@ -748,7 +919,7 @@ class Base {
|
|
748
919
|
* @returns {String}
|
749
920
|
*/
|
750
921
|
get [Symbol.toStringTag]() {
|
751
|
-
return
|
922
|
+
return this.className
|
752
923
|
}
|
753
924
|
|
754
925
|
/**
|
package/src/core/Compare.mjs
CHANGED
@@ -163,10 +163,7 @@ class Compare {
|
|
163
163
|
}
|
164
164
|
}
|
165
165
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
Neo.isEqual = Compare.isEqual;
|
171
|
-
|
172
|
-
export default Compare;
|
166
|
+
export default Neo.gatekeep(Compare, 'Neo.core.Compare', () => {
|
167
|
+
// alias
|
168
|
+
Neo.isEqual = Compare.isEqual
|
169
|
+
});
|
package/src/core/Config.mjs
CHANGED
@@ -1,45 +1,65 @@
|
|
1
|
+
import EffectManager from './EffectManager.mjs';
|
1
2
|
import {isDescriptor} from './ConfigSymbols.mjs';
|
2
3
|
|
3
4
|
/**
|
4
|
-
* @src/util/ClassSystem.mjs Neo.core.Config
|
5
|
-
* @private
|
6
|
-
* @internal
|
7
|
-
*
|
8
5
|
* Represents an observable container for a config property.
|
9
6
|
* This class manages the value of a config, its subscribers, and custom behaviors
|
10
7
|
* like merge strategies and equality checks defined via a descriptor object.
|
11
8
|
*
|
12
9
|
* The primary purpose of this class is to enable fine-grained reactivity and
|
13
10
|
* decoupled cross-instance state sharing within the Neo.mjs framework.
|
11
|
+
* @class Neo.core.Config
|
12
|
+
* @private
|
13
|
+
* @internal
|
14
14
|
*/
|
15
15
|
class Config {
|
16
16
|
/**
|
17
|
-
*
|
17
|
+
* Stores all subscriptions for this Config instance.
|
18
|
+
* The data structure is a Map where:
|
19
|
+
* - The key is the ID of the subscription owner (e.g., a component's `id`).
|
20
|
+
* - The value is another Map (the subscriberMap).
|
21
|
+
*
|
22
|
+
* The nested subscriberMap is structured as:
|
23
|
+
* - The key is the callback function (`fn`).
|
24
|
+
* - The value is a Set of scopes (`scopeSet`).
|
25
|
+
*
|
26
|
+
* This nested structure `Map<string, Map<function, Set<scope>>>` is intentionally chosen
|
27
|
+
* to robustly handle the edge case where the same function is subscribed multiple times
|
28
|
+
* with different scopes, all under the same owner ID. It ensures that each
|
29
|
+
* `fn`-`scope` combination is unique and that cleanup is precise.
|
30
|
+
* @member {Object} #subscribers={}
|
18
31
|
* @private
|
19
|
-
* @apps/portal/view/about/MemberContainer.mjs {any} #value
|
20
32
|
*/
|
21
|
-
#
|
22
|
-
|
33
|
+
#subscribers = {}
|
23
34
|
/**
|
24
|
-
*
|
35
|
+
* The internal value of the config property.
|
36
|
+
* @member {*} #value
|
25
37
|
* @private
|
26
|
-
* @apps/portal/view/about/MemberContainer.mjs {Set<Function>} #subscribers
|
27
38
|
*/
|
28
|
-
#
|
39
|
+
#value
|
40
|
+
/**
|
41
|
+
* The cloning strategy to use when setting a new value.
|
42
|
+
* Supported values: 'deep', 'shallow', 'none'.
|
43
|
+
* @member {String} clone='deep'
|
44
|
+
*/
|
29
45
|
|
30
46
|
/**
|
31
|
-
* The strategy to use when
|
32
|
-
*
|
33
|
-
* @
|
47
|
+
* The cloning strategy to use when getting a value.
|
48
|
+
* Supported values: 'deep', 'shallow', 'none'.
|
49
|
+
* @member {String} cloneOnGet=null
|
34
50
|
*/
|
35
|
-
mergeStrategy = 'deep';
|
36
51
|
|
37
52
|
/**
|
38
53
|
* The function used to compare new and old values for equality.
|
39
54
|
* Defaults to `Neo.isEqual`. Can be overridden via a descriptor.
|
40
|
-
* @
|
55
|
+
* @member {Function} isEqual=Neo.isEqual
|
56
|
+
*/
|
57
|
+
|
58
|
+
/**
|
59
|
+
* The strategy to use when merging new values into this config.
|
60
|
+
* Defaults to 'replace'. Can be overridden via a descriptor merge property.
|
61
|
+
* @member {Function|String} mergeStrategy='replace'
|
41
62
|
*/
|
42
|
-
isEqual = Neo.isEqual;
|
43
63
|
|
44
64
|
/**
|
45
65
|
* Creates an instance of Config.
|
@@ -58,34 +78,68 @@ class Config {
|
|
58
78
|
* @returns {any} The current value.
|
59
79
|
*/
|
60
80
|
get() {
|
61
|
-
|
81
|
+
// Registers this Config instance as a dependency with the currently active Effect,
|
82
|
+
// enabling automatic re-execution when this Config's value changes.
|
83
|
+
EffectManager.addDependency(this);
|
84
|
+
return this.#value
|
62
85
|
}
|
63
86
|
|
64
87
|
/**
|
65
88
|
* Initializes the `Config` instance using a descriptor object.
|
66
|
-
* Extracts `mergeStrategy` and `isEqual` from the descriptor.
|
89
|
+
* Extracts `clone`, `mergeStrategy` and `isEqual` from the descriptor.
|
67
90
|
* The internal `#value` is NOT set by this method.
|
68
|
-
* @param {Object}
|
69
|
-
* @param {
|
70
|
-
* @param {string}
|
91
|
+
* @param {Object} descriptor - The descriptor object for the config.
|
92
|
+
* @param {string} [descriptor.clone='deep'] - The clone strategy for set.
|
93
|
+
* @param {string} [descriptor.cloneOnGet] - The clone strategy for get. Defaults to 'shallow' if clone is 'deep' or 'shallow', and 'none' if clone is 'none'.
|
71
94
|
* @param {Function} [descriptor.isEqual=Neo.isEqual] - The equality comparison function.
|
95
|
+
* @param {string} [descriptor.merge='deep'] - The merge strategy.
|
96
|
+
* @param {any} descriptor.value - The default value for the config (not set by this method).
|
72
97
|
*/
|
73
|
-
initDescriptor({isEqual, merge
|
98
|
+
initDescriptor({clone, cloneOnGet, isEqual, merge}) {
|
74
99
|
let me = this;
|
75
100
|
|
76
|
-
me
|
77
|
-
|
78
|
-
|
101
|
+
if (clone && clone !== me.clone) {
|
102
|
+
Object.defineProperty(me, 'clone', {
|
103
|
+
configurable: true, enumerable: true, value: clone, writable: true
|
104
|
+
})
|
105
|
+
}
|
106
|
+
|
107
|
+
if (cloneOnGet && cloneOnGet !== me.cloneOnGet) {
|
108
|
+
Object.defineProperty(me, 'cloneOnGet', {
|
109
|
+
configurable: true, enumerable: true, value: cloneOnGet, writable: true
|
110
|
+
})
|
111
|
+
}
|
112
|
+
|
113
|
+
if (isEqual && isEqual !== me.isEqual) {
|
114
|
+
Object.defineProperty(me, 'isEqual', {
|
115
|
+
configurable: true, enumerable: true, value: isEqual, writable: true
|
116
|
+
})
|
117
|
+
}
|
118
|
+
|
119
|
+
if (merge && merge !== me.mergeStrategy) {
|
120
|
+
Object.defineProperty(me, 'mergeStrategy', {
|
121
|
+
configurable: true, enumerable: true, value: merge, writable: true
|
122
|
+
})
|
123
|
+
}
|
79
124
|
}
|
80
125
|
|
81
126
|
/**
|
82
127
|
* Notifies all subscribed callbacks about a change in the config's value.
|
128
|
+
* It iterates through the nested subscriber structure to ensure each callback
|
129
|
+
* is executed with its intended scope.
|
83
130
|
* @param {any} newValue - The new value of the config.
|
84
131
|
* @param {any} oldValue - The old value of the config.
|
85
132
|
*/
|
86
133
|
notify(newValue, oldValue) {
|
87
|
-
for (const
|
88
|
-
|
134
|
+
for (const id in this.#subscribers) {
|
135
|
+
if (this.#subscribers.hasOwnProperty(id)) {
|
136
|
+
const subscriberMap = this.#subscribers[id];
|
137
|
+
for (const [fn, scopeSet] of subscriberMap) {
|
138
|
+
for (const scope of scopeSet) {
|
139
|
+
fn.call(scope || null, newValue, oldValue)
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
89
143
|
}
|
90
144
|
}
|
91
145
|
|
@@ -127,13 +181,63 @@ class Config {
|
|
127
181
|
/**
|
128
182
|
* Subscribes a callback function to changes in this config's value.
|
129
183
|
* The callback will be invoked with `(newValue, oldValue)` whenever the config changes.
|
130
|
-
* @param {
|
184
|
+
* @param {Object} options - An object containing the subscription details.
|
185
|
+
* @param {String} options.id - The ID of the subscription owner (e.g., a Neo.core.Base instance's id).
|
186
|
+
* @param {Function} options.fn - The callback function.
|
187
|
+
* @param {Object} [options.scope] - The scope to execute the callback in.
|
131
188
|
* @returns {Function} A cleanup function to unsubscribe the callback.
|
132
189
|
*/
|
133
|
-
subscribe(
|
134
|
-
|
135
|
-
|
190
|
+
subscribe({id, fn, scope}) {
|
191
|
+
if (typeof id !== 'string' || id.length === 0 || typeof fn !== 'function') {
|
192
|
+
throw new Error([
|
193
|
+
'Config.subscribe: options must be an object with a non-empty string `id` ',
|
194
|
+
'(the subscription owner\'s id), and a callback function `fn`.'
|
195
|
+
].join(''))
|
196
|
+
}
|
197
|
+
|
198
|
+
const me = this;
|
199
|
+
|
200
|
+
// Get or create the top-level Map for the subscription owner.
|
201
|
+
if (!me.#subscribers[id]) {
|
202
|
+
me.#subscribers[id] = new Map()
|
203
|
+
}
|
204
|
+
|
205
|
+
const subscriberMap = me.#subscribers[id];
|
206
|
+
|
207
|
+
// Get or create the Set of scopes for the specific callback function.
|
208
|
+
if (!subscriberMap.has(fn)) {
|
209
|
+
subscriberMap.set(fn, new Set())
|
210
|
+
}
|
211
|
+
|
212
|
+
const scopeSet = subscriberMap.get(fn);
|
213
|
+
scopeSet.add(scope);
|
214
|
+
|
215
|
+
// The returned cleanup function is precise. It removes only the specific
|
216
|
+
// scope for the function, and cleans up the parent data structures
|
217
|
+
// (the Set and the Maps) only if they become empty.
|
218
|
+
return () => {
|
219
|
+
const currentSubscriberMap = me.#subscribers[id];
|
220
|
+
if (currentSubscriberMap) {
|
221
|
+
const currentScopeSet = currentSubscriberMap.get(fn);
|
222
|
+
if (currentScopeSet) {
|
223
|
+
currentScopeSet.delete(scope);
|
224
|
+
if (currentScopeSet.size === 0) {
|
225
|
+
currentSubscriberMap.delete(fn);
|
226
|
+
if (currentSubscriberMap.size === 0) {
|
227
|
+
delete me.#subscribers[id]
|
228
|
+
}
|
229
|
+
}
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
136
233
|
}
|
137
234
|
}
|
138
235
|
|
139
|
-
|
236
|
+
Object.defineProperties(Config.prototype, {
|
237
|
+
clone : {configurable: true, enumerable: false, value: 'deep', writable: false},
|
238
|
+
cloneOnGet : {configurable: true, enumerable: false, value: null, writable: false},
|
239
|
+
isEqual : {configurable: true, enumerable: false, value: Neo.isEqual, writable: false},
|
240
|
+
mergeStrategy: {configurable: true, enumerable: false, value: 'replace', writable: false}
|
241
|
+
});
|
242
|
+
|
243
|
+
export default Neo.gatekeep(Config, 'Neo.core.Config');
|