neo.mjs 10.0.0-beta.5 → 10.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/RELEASE_NOTES/v10.0.0-beta.1.md +20 -0
- package/.github/RELEASE_NOTES/v10.0.0-beta.2.md +73 -0
- package/.github/RELEASE_NOTES/v10.0.0-beta.3.md +39 -0
- 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/RELEASE_NOTES/v10.0.0.md +52 -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 +11 -3
- 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 +29 -19
- 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/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/button/base/MainContainer.mjs +207 -0
- package/examples/functional/button/base/app.mjs +6 -0
- package/examples/functional/button/base/index.html +11 -0
- package/examples/functional/button/base/neo-config.json +6 -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/blog/v10-deep-dive-functional-components.md +293 -0
- package/learn/blog/v10-deep-dive-reactivity.md +522 -0
- package/learn/blog/v10-deep-dive-state-provider.md +432 -0
- package/learn/blog/v10-deep-dive-vdom-revolution.md +194 -0
- package/learn/blog/v10-post1-love-story.md +383 -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 +130 -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 +10 -8
- 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 +28 -2
- 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 -64
- package/learn/tutorials/Earthquakes.md +2 -0
- package/learn/tutorials/RSP.md +3 -1
- package/learn/tutorials/TodoList.md +103 -7
- package/package.json +8 -6
- 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 +219 -166
- package/src/Xhr.mjs +1 -0
- package/src/button/Base.mjs +13 -0
- package/src/button/Effect.mjs +16 -2
- 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 +7 -10
- package/src/collection/Filter.mjs +6 -0
- package/src/collection/Sorter.mjs +3 -0
- package/src/component/Abstract.mjs +412 -0
- package/src/component/Base.mjs +48 -1077
- 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 +7 -3
- 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 +86 -33
- package/src/core/Compare.mjs +4 -7
- package/src/core/Config.mjs +65 -52
- package/src/core/Effect.mjs +86 -24
- package/src/core/EffectManager.mjs +117 -8
- package/src/core/IdGenerator.mjs +13 -44
- package/src/core/Observable.mjs +69 -65
- 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 +43 -5
- 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/button/Base.mjs +384 -0
- package/src/functional/component/Base.mjs +405 -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 +50 -60
- 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/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 +18 -4
- 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 +320 -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 +670 -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 +129 -87
- package/src/state/createHierarchicalDataProxy.mjs +39 -25
- 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 +2 -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 +18 -5
- package/src/util/VNode.mjs +7 -1
- package/src/util/vdom/TreeBuilder.mjs +105 -0
- package/src/vdom/Helper.mjs +35 -23
- package/src/vdom/VNode.mjs +4 -6
- package/src/worker/App.mjs +1 -0
- package/src/worker/Base.mjs +2 -0
- package/src/worker/Manager.mjs +2 -0
- package/src/worker/ServiceBase.mjs +6 -1
- package/src/worker/mixin/RemoteMethodAccess.mjs +1 -6
- package/test/siesta/siesta.js +17 -2
- package/test/siesta/tests/VdomCalendar.mjs +19 -15
- package/test/siesta/tests/VdomHelper.mjs +7 -7
- package/test/siesta/tests/classic/Button.mjs +113 -0
- package/test/siesta/tests/core/Effect.mjs +10 -14
- package/test/siesta/tests/core/EffectBatching.mjs +72 -79
- package/test/siesta/tests/functional/Button.mjs +113 -0
- package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +314 -0
- package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +42 -55
- package/test/siesta/tests/vdom/Advanced.mjs +14 -8
- package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +366 -0
- package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +249 -0
- package/test/siesta/tests/vdom/layout/Cube.mjs +11 -7
- package/test/siesta/tests/vdom/table/Container.mjs +9 -5
- package/learn/javascript/NewNode.md +0 -31
- package/src/core/EffectBatchManager.mjs +0 -68
@@ -0,0 +1,405 @@
|
|
1
|
+
import Abstract from '../../component/Abstract.mjs';
|
2
|
+
import Effect from '../../core/Effect.mjs';
|
3
|
+
import NeoArray from '../../util/Array.mjs';
|
4
|
+
|
5
|
+
const
|
6
|
+
activeDomListenersSymbol = Symbol.for('activeDomListeners'),
|
7
|
+
hookIndexSymbol = Symbol.for('hookIndex'),
|
8
|
+
hooksSymbol = Symbol.for('hooks'),
|
9
|
+
pendingDomEventsSymbol = Symbol.for('pendingDomEvents'),
|
10
|
+
vdomToApplySymbol = Symbol('vdomToApply');
|
11
|
+
|
12
|
+
/**
|
13
|
+
* @class Neo.functional.component.Base
|
14
|
+
* @extends Neo.component.Abstract
|
15
|
+
*/
|
16
|
+
class FunctionalBase extends Abstract {
|
17
|
+
static config = {
|
18
|
+
/**
|
19
|
+
* @member {String} className='Neo.functional.component.Base'
|
20
|
+
* @protected
|
21
|
+
*/
|
22
|
+
className: 'Neo.functional.component.Base',
|
23
|
+
/**
|
24
|
+
* @member {String} ntype='functional-component'
|
25
|
+
* @protected
|
26
|
+
*/
|
27
|
+
ntype: 'functional-component',
|
28
|
+
/**
|
29
|
+
* The vdom markup for this component.
|
30
|
+
* @member {Object} vdom={}
|
31
|
+
*/
|
32
|
+
vdom: {}
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Neo component instances, which got defined inside createVdom()
|
37
|
+
* @member {Map|null} childComponents=null
|
38
|
+
*/
|
39
|
+
childComponents = null
|
40
|
+
/**
|
41
|
+
* Internal Map to store the next set of components after the createVdom() Effect has run.
|
42
|
+
* @member {Map|null} nextChildComponents=null
|
43
|
+
* @private
|
44
|
+
*/
|
45
|
+
#nextChildComponents = null
|
46
|
+
|
47
|
+
/**
|
48
|
+
* @param {Object} config
|
49
|
+
*/
|
50
|
+
construct(config) {
|
51
|
+
super.construct(config);
|
52
|
+
|
53
|
+
let me = this,
|
54
|
+
opts = {configurable: true, enumerable: false, writable: true};
|
55
|
+
|
56
|
+
Object.defineProperties(me, {
|
57
|
+
[activeDomListenersSymbol]: {...opts, value: []},
|
58
|
+
[hookIndexSymbol] : {...opts, value: 0},
|
59
|
+
[hooksSymbol] : {...opts, value: []},
|
60
|
+
[pendingDomEventsSymbol] : {...opts, value: []},
|
61
|
+
[vdomToApplySymbol] : {...opts, value: null}
|
62
|
+
});
|
63
|
+
|
64
|
+
// Creates a reactive effect that re-executes createVdom() when dependencies change.
|
65
|
+
me.vdomEffect = new Effect({
|
66
|
+
fn: () => {
|
67
|
+
me[hookIndexSymbol] = 0;
|
68
|
+
me[pendingDomEventsSymbol] = []; // Clear pending events for new render
|
69
|
+
me[vdomToApplySymbol] = me.createVdom(me)
|
70
|
+
},
|
71
|
+
componentId: me.id,
|
72
|
+
subscriber : {
|
73
|
+
id : me.id,
|
74
|
+
fn : me.onEffectRunStateChange,
|
75
|
+
scope: me
|
76
|
+
}
|
77
|
+
})
|
78
|
+
}
|
79
|
+
|
80
|
+
/**
|
81
|
+
* Triggered after the mounted config got changed
|
82
|
+
* @param {Boolean} value
|
83
|
+
* @param {Boolean} oldValue
|
84
|
+
* @protected
|
85
|
+
*/
|
86
|
+
afterSetMounted(value, oldValue) {
|
87
|
+
super.afterSetMounted(value, oldValue);
|
88
|
+
|
89
|
+
if (oldValue !== undefined) {
|
90
|
+
const me = this;
|
91
|
+
|
92
|
+
if (value) { // mount
|
93
|
+
// Initial registration of DOM event listeners when component mounts
|
94
|
+
me.applyPendingDomListeners();
|
95
|
+
}
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
/**
|
100
|
+
* Triggered after the windowId config got changed
|
101
|
+
* @param {Number|null} value
|
102
|
+
* @param {Number|null} oldValue
|
103
|
+
* @protected
|
104
|
+
*/
|
105
|
+
afterSetWindowId(value, oldValue) {
|
106
|
+
super.afterSetWindowId(value, oldValue);
|
107
|
+
|
108
|
+
this.childComponents?.forEach(childData => {
|
109
|
+
childData.instance.windowId = value
|
110
|
+
})
|
111
|
+
}
|
112
|
+
|
113
|
+
/**
|
114
|
+
* Applies the pending DOM event listeners and updates the active list.
|
115
|
+
* @private
|
116
|
+
*/
|
117
|
+
applyPendingDomListeners() {
|
118
|
+
const
|
119
|
+
me = this,
|
120
|
+
activeEvents = me[activeDomListenersSymbol],
|
121
|
+
pendingEvents = me[pendingDomEventsSymbol];
|
122
|
+
|
123
|
+
if (pendingEvents.length > 0) {
|
124
|
+
if (!Neo.isEqual(activeEvents, pendingEvents)) {
|
125
|
+
if (activeEvents?.length > 0) {
|
126
|
+
// Remove old dynamic listeners
|
127
|
+
me.removeDomListeners(me[activeDomListenersSymbol])
|
128
|
+
}
|
129
|
+
|
130
|
+
me.addDomListeners([...pendingEvents]);
|
131
|
+
|
132
|
+
me[activeDomListenersSymbol] = [...pendingEvents]
|
133
|
+
}
|
134
|
+
|
135
|
+
// Clear pending events for next `createVdom()` Effect run
|
136
|
+
me[pendingDomEventsSymbol] = []
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
/**
|
141
|
+
* A lifecycle hook that runs after a state change has been detected but before the
|
142
|
+
* VDOM update is dispatched. It provides a dedicated place for logic that needs to
|
143
|
+
* execute before rendering, such as calculating derived data or caching values.
|
144
|
+
*
|
145
|
+
* You can prevent the VDOM update by returning `false` from this method. This is
|
146
|
+
* useful for advanced cases where you might want to manually trigger a different
|
147
|
+
* update after modifying other component configs.
|
148
|
+
*
|
149
|
+
* **IMPORTANT**: Do not change the value of any config that is used as a dependency
|
150
|
+
* within the `createVdom` method from inside this hook, as it will cause an
|
151
|
+
* infinite update loop. This hook is for one-way data flow, not for triggering
|
152
|
+
* cascading reactive changes.
|
153
|
+
*
|
154
|
+
* @returns {Boolean|undefined} Return `false` to cancel the upcoming VDOM update.
|
155
|
+
* @example
|
156
|
+
* beforeUpdate() {
|
157
|
+
* // Perform an expensive calculation and cache the result on the instance
|
158
|
+
* this.processedData = this.processRawData(this.rawData);
|
159
|
+
*
|
160
|
+
* // Example of conditionally cancelling an update
|
161
|
+
* if (this.processedData.length === 0 && this.vdom.cn?.length === 0) {
|
162
|
+
* return false; // Don't re-render if there's nothing to show
|
163
|
+
* }
|
164
|
+
* }
|
165
|
+
*/
|
166
|
+
beforeUpdate() {
|
167
|
+
// This method can be overridden by subclasses
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Override this method in your functional component to return its VDOM structure.
|
172
|
+
* This method will be automatically re-executed when any of the component's configs change.
|
173
|
+
* To access data from a state provider, use `config.data`.
|
174
|
+
* @param {Neo.functional.component.Base} config - Mental model: while it contains the instance, it makes it clear to access configs
|
175
|
+
* @returns {Object} The VDOM structure for the component.
|
176
|
+
*/
|
177
|
+
createVdom(config) {
|
178
|
+
// This method should be overridden by subclasses
|
179
|
+
return {}
|
180
|
+
}
|
181
|
+
|
182
|
+
/**
|
183
|
+
*
|
184
|
+
*/
|
185
|
+
destroy() {
|
186
|
+
const me = this;
|
187
|
+
|
188
|
+
me.vdomEffect?.destroy();
|
189
|
+
|
190
|
+
// Destroy all classic components instantiated by this functional component
|
191
|
+
me.childComponents?.forEach(childData => {
|
192
|
+
childData.instance.destroy()
|
193
|
+
});
|
194
|
+
me.childComponents?.clear();
|
195
|
+
|
196
|
+
// Remove any pending DOM event listeners that might not have been mounted
|
197
|
+
me[pendingDomEventsSymbol] = null;
|
198
|
+
|
199
|
+
super.destroy()
|
200
|
+
}
|
201
|
+
|
202
|
+
/**
|
203
|
+
* This method recursively compares the new VDOM config with the last applied config
|
204
|
+
* for a given component instance and its sub-instances.
|
205
|
+
* @param {Neo.core.Base} instance The component instance to update.
|
206
|
+
* @param {Object} newConfig The new configuration object from the VDOM.
|
207
|
+
* @param {Object} lastConfig The last applied configuration object.
|
208
|
+
* @private
|
209
|
+
*/
|
210
|
+
diffAndSet(instance, newConfig, lastConfig) {
|
211
|
+
const deltaConfig = {};
|
212
|
+
|
213
|
+
for (const key in newConfig) {
|
214
|
+
const newValue = newConfig[key],
|
215
|
+
oldValue = lastConfig[key];
|
216
|
+
|
217
|
+
if (!Neo.isEqual(newValue, oldValue)) {
|
218
|
+
// If the config property is an object and it maps to a sub-component instance, recurse.
|
219
|
+
if (Neo.typeOf(newValue) === 'Object' && Neo.typeOf(instance[key]) === 'NeoInstance') {
|
220
|
+
this.diffAndSet(instance[key], newValue, oldValue || {})
|
221
|
+
} else {
|
222
|
+
// Otherwise, add it to the delta to be set on the current instance.
|
223
|
+
deltaConfig[key] = newValue
|
224
|
+
}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
228
|
+
// Only call set() if there are actual changes for the current instance.
|
229
|
+
if (Object.keys(deltaConfig).length > 0) {
|
230
|
+
instance.set(deltaConfig)
|
231
|
+
}
|
232
|
+
}
|
233
|
+
|
234
|
+
/**
|
235
|
+
* This handler runs when the effect's `isRunning` state changes.
|
236
|
+
* It runs outside the effect's tracking scope, preventing feedback loops.
|
237
|
+
* @param {Boolean} value
|
238
|
+
* @param {Boolean} oldValue
|
239
|
+
*/
|
240
|
+
onEffectRunStateChange(value, oldValue) {
|
241
|
+
// When the effect has just finished running...
|
242
|
+
if (value === false) {
|
243
|
+
const me = this,
|
244
|
+
newVdom = me[vdomToApplySymbol];
|
245
|
+
|
246
|
+
if (newVdom) {
|
247
|
+
// Create a new map for components instantiated in this render cycle
|
248
|
+
me.#nextChildComponents = new Map();
|
249
|
+
|
250
|
+
// Process the newVdom to instantiate components
|
251
|
+
// The parentId for these components will be the functional component's id
|
252
|
+
const processedVdom = me.processVdomForComponents(newVdom, me.id);
|
253
|
+
|
254
|
+
// Destroy components that are no longer present in the new VDOM
|
255
|
+
if (me.childComponents?.size > 0) {
|
256
|
+
[...me.childComponents].forEach(([key, childData]) => {
|
257
|
+
if (!me.#nextChildComponents.has(key)) {
|
258
|
+
me.childComponents.delete(key);
|
259
|
+
childData.instance.destroy()
|
260
|
+
}
|
261
|
+
})
|
262
|
+
}
|
263
|
+
|
264
|
+
// If this component created other classic or functional components,
|
265
|
+
// include their full vdom into the next update cycle.
|
266
|
+
const oldKeys = me.childComponents ? new Set(me.childComponents.keys()) : new Set();
|
267
|
+
let hasNewChildren = false;
|
268
|
+
|
269
|
+
for (const newKey of me.#nextChildComponents.keys()) {
|
270
|
+
if (!oldKeys.has(newKey)) {
|
271
|
+
hasNewChildren = true;
|
272
|
+
break
|
273
|
+
}
|
274
|
+
}
|
275
|
+
|
276
|
+
if (hasNewChildren) {
|
277
|
+
// When new child components are created, we need to send their full VDOM
|
278
|
+
// to the vdom-worker, so they can get rendered.
|
279
|
+
// Subsequent updates will be granular via diffAndSet() => set() on the child.
|
280
|
+
me.updateDepth = -1;
|
281
|
+
}
|
282
|
+
|
283
|
+
// Update the main map of instantiated components
|
284
|
+
me.childComponents = me.#nextChildComponents;
|
285
|
+
|
286
|
+
// Clear the old vdom properties
|
287
|
+
for (const key in me.vdom) {
|
288
|
+
delete me.vdom[key]
|
289
|
+
}
|
290
|
+
|
291
|
+
// Assign the new properties
|
292
|
+
Object.assign(me.vdom, processedVdom); // Use processedVdom here
|
293
|
+
|
294
|
+
me[vdomToApplySymbol] = null;
|
295
|
+
|
296
|
+
const root = me.getVdomRoot();
|
297
|
+
|
298
|
+
if (me.cls) {
|
299
|
+
root.cls = NeoArray.union(me.cls, root.cls)
|
300
|
+
}
|
301
|
+
|
302
|
+
if (me.id) {
|
303
|
+
root.id = me.id
|
304
|
+
}
|
305
|
+
|
306
|
+
// Re-hydrate the new vdom with stable IDs from the previous vnode tree.
|
307
|
+
// This is crucial for functional components where the vdom is recreated on every render,
|
308
|
+
// ensuring the diffing algorithm can track nodes correctly.
|
309
|
+
me.syncVdomIds();
|
310
|
+
|
311
|
+
if (me.beforeUpdate() !== false) {
|
312
|
+
me.updateVdom()
|
313
|
+
}
|
314
|
+
|
315
|
+
// Update DOM event listeners based on the new render
|
316
|
+
if (me.mounted) {
|
317
|
+
me.applyPendingDomListeners()
|
318
|
+
}
|
319
|
+
}
|
320
|
+
}
|
321
|
+
}
|
322
|
+
|
323
|
+
/**
|
324
|
+
* Recursively processes a VDOM node to instantiate components defined within it.
|
325
|
+
* @param {Object} vdomTree The VDOM node to process.
|
326
|
+
* @param {String} parentId The ID of the parent component (the functional component hosting it).
|
327
|
+
* @param {Number} [parentIndex] The index of the vdomNode within its parent's children.
|
328
|
+
* @returns {Object} The processed VDOM node, potentially replaced with a component reference.
|
329
|
+
* @private
|
330
|
+
*/
|
331
|
+
processVdomForComponents(vdomTree, parentId, parentIndex) {
|
332
|
+
if (!vdomTree) {
|
333
|
+
return vdomTree
|
334
|
+
}
|
335
|
+
|
336
|
+
// If it's already a component reference, no need to process further
|
337
|
+
if (vdomTree.componentId) {
|
338
|
+
return vdomTree
|
339
|
+
}
|
340
|
+
|
341
|
+
const me = this;
|
342
|
+
|
343
|
+
// Check if it's a component definition (functional or classic)
|
344
|
+
if (vdomTree.className || vdomTree.module || vdomTree.ntype) {
|
345
|
+
// Components are reconciled based on their `id` property in the VDOM definition.
|
346
|
+
// If no `id` is provided, a new instance will be created on every render.
|
347
|
+
const componentKey = vdomTree.id;
|
348
|
+
|
349
|
+
if (!componentKey) {
|
350
|
+
console.error([
|
351
|
+
'Component definition in functional component VDOM is missing an "id". For stable reconciliation, ',
|
352
|
+
'especially in dynamic lists, provide a unique "id" property.'
|
353
|
+
].join(''),
|
354
|
+
vdomTree
|
355
|
+
)
|
356
|
+
}
|
357
|
+
|
358
|
+
let childData = me.childComponents?.get(componentKey),
|
359
|
+
newConfig = {...vdomTree}, // Shallow copy
|
360
|
+
instance;
|
361
|
+
|
362
|
+
delete newConfig.className;
|
363
|
+
delete newConfig.id;
|
364
|
+
delete newConfig.module;
|
365
|
+
delete newConfig.ntype;
|
366
|
+
|
367
|
+
if (!childData) {
|
368
|
+
me.childComponents ??= new Map();
|
369
|
+
|
370
|
+
// Instantiate the component
|
371
|
+
instance = Neo[(vdomTree.className || vdomTree.module) ? 'create' : 'ntype']({
|
372
|
+
...vdomTree,
|
373
|
+
parentId,
|
374
|
+
parentIndex,
|
375
|
+
windowId: me.windowId
|
376
|
+
});
|
377
|
+
} else {
|
378
|
+
instance = childData.instance;
|
379
|
+
|
380
|
+
// Recursively diff and set configs
|
381
|
+
this.diffAndSet(instance, newConfig, childData.lastConfig);
|
382
|
+
}
|
383
|
+
|
384
|
+
// Add to the new map for tracking in this render cycle
|
385
|
+
me.#nextChildComponents.set(componentKey, {
|
386
|
+
instance,
|
387
|
+
lastConfig: newConfig
|
388
|
+
});
|
389
|
+
|
390
|
+
// Replace the definition with a reference using the component's own method
|
391
|
+
return instance.createVdomReference();
|
392
|
+
}
|
393
|
+
|
394
|
+
// Recursively process children
|
395
|
+
if (vdomTree.cn && Array.isArray(vdomTree.cn)) {
|
396
|
+
vdomTree.cn = vdomTree.cn.map((child, index) =>
|
397
|
+
me.processVdomForComponents(child, parentId, index)
|
398
|
+
)
|
399
|
+
}
|
400
|
+
|
401
|
+
return vdomTree
|
402
|
+
}
|
403
|
+
}
|
404
|
+
|
405
|
+
export default Neo.setupClass(FunctionalBase);
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import FunctionalBase from './component/Base.mjs';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Factory function to create a functional component class from a specification object.
|
5
|
+
* This enables a "Beginner Mode" for creating components without writing a class,
|
6
|
+
* while still providing access to the full class-based feature set.
|
7
|
+
*
|
8
|
+
* It's important to understand the two ways of managing state:
|
9
|
+
*
|
10
|
+
* 1. **Named Configs (Public API):** Defined in the `config` object (e.g., `text_`).
|
11
|
+
* - **Purpose:** Defines the component's public API for external control, similar to props.
|
12
|
+
* - **Use Case:** For data that a parent component will set or change.
|
13
|
+
* - **Features:** Integrates with the full config system (e.g., `afterSetText()` hooks).
|
14
|
+
*
|
15
|
+
* 2. **`useConfig()` (Internal State):** Used inside `createVdom` or other methods.
|
16
|
+
* - **Purpose:** Manages private, encapsulated state that is internal to the component.
|
17
|
+
* - **Use Case:** For state that the component manages itself and is not controlled by the parent.
|
18
|
+
* - **Features:** Provides a simple, concise way to handle local reactive state.
|
19
|
+
*
|
20
|
+
* @param {Object} spec - The specification object for the component.
|
21
|
+
* @returns {Neo.functional.component.Base} The generated component class.
|
22
|
+
*
|
23
|
+
* @example
|
24
|
+
* import { defineComponent } from 'neo/functional/defineComponent.mjs';
|
25
|
+
* import { useConfig } from 'neo/functional/useConfig.mjs';
|
26
|
+
*
|
27
|
+
* const MyComponent = defineComponent({
|
28
|
+
* // 1. Define the Public API via the `config` object.
|
29
|
+
* config: {
|
30
|
+
* className: 'MyApp.MyFunctionalComponent',
|
31
|
+
* ntype : 'my-functional-component',
|
32
|
+
*
|
33
|
+
* // `text_` is a NAMED CONFIG. It's part of the component's public API.
|
34
|
+
* // A parent can create this component with a `text` config.
|
35
|
+
* // It is reactive and will generate `afterSetText()` and `beforeSetText()` hooks.
|
36
|
+
* text_: 'Hello World'
|
37
|
+
* },
|
38
|
+
*
|
39
|
+
* // 2. Define the component's logic and VDOM.
|
40
|
+
* createVdom(config) {
|
41
|
+
* // `useConfig` creates ANONYMOUS, INTERNAL STATE.
|
42
|
+
* // The `count` state is private to this component and cannot be set by a parent.
|
43
|
+
* const [count, setCount] = useConfig(0);
|
44
|
+
*
|
45
|
+
* return {
|
46
|
+
* tag: 'div',
|
47
|
+
* cn: [{
|
48
|
+
* tag: 'h1',
|
49
|
+
* // Access the public, named config via the `config` parameter.
|
50
|
+
* text: config.text
|
51
|
+
* }, {
|
52
|
+
* tag: 'p',
|
53
|
+
* // Access the private, internal state directly.
|
54
|
+
* text: `You clicked ${count} times`
|
55
|
+
* }, {
|
56
|
+
* tag: 'button',
|
57
|
+
* text: 'Click me',
|
58
|
+
* // The setter from `useConfig` updates the internal state.
|
59
|
+
* onclick: () => setCount(count + 1)
|
60
|
+
* }]
|
61
|
+
* };
|
62
|
+
* },
|
63
|
+
*
|
64
|
+
* // 3. Lifecycle hooks for named configs work automatically.
|
65
|
+
* afterSetText(newValue, oldValue) {
|
66
|
+
* console.log(`Text changed from '${oldValue}' to '${newValue}'`);
|
67
|
+
* }
|
68
|
+
* });
|
69
|
+
*
|
70
|
+
* // The returned MyComponent is a class constructor that can be used with Neo.create()
|
71
|
+
* // const instance = Neo.create(MyComponent, {
|
72
|
+
* // text: 'Welcome to Neo.mjs!' // Set the public config on creation.
|
73
|
+
* // });
|
74
|
+
*/
|
75
|
+
export function defineComponent(spec) {
|
76
|
+
const configSpec = spec.config;
|
77
|
+
delete spec.config;
|
78
|
+
|
79
|
+
if (!configSpec?.className) {
|
80
|
+
throw new Error('defineComponent requires a config object with a className.');
|
81
|
+
}
|
82
|
+
|
83
|
+
class FunctionalComponent extends FunctionalBase {
|
84
|
+
static config = {
|
85
|
+
...configSpec
|
86
|
+
// We can add our own configurations here
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
// Assign instance methods
|
91
|
+
Object.entries(spec).forEach(([key, value]) => {
|
92
|
+
FunctionalComponent.prototype[key] = value
|
93
|
+
});
|
94
|
+
|
95
|
+
// To support multiple envs (like `devmode`, `dist/esm`, `dist/production` in parallel,
|
96
|
+
// we must re-assign FunctionalComponent to the setupClass() output.
|
97
|
+
FunctionalComponent = Neo.setupClass(FunctionalComponent);
|
98
|
+
|
99
|
+
return FunctionalComponent
|
100
|
+
}
|
101
|
+
|
102
|
+
export default defineComponent;
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import Config from '../core/Config.mjs';
|
2
|
+
import EffectManager from '../core/EffectManager.mjs';
|
3
|
+
|
4
|
+
const
|
5
|
+
hookIndexSymbol = Symbol.for('hookIndex'),
|
6
|
+
hooksSymbol = Symbol.for('hooks');
|
7
|
+
|
8
|
+
/**
|
9
|
+
* A hook for managing reactive state within a functional component's `createVdom` method.
|
10
|
+
* It mirrors the behavior of React's `useState` but is powered by `Neo.core.Config` for reactivity.
|
11
|
+
* @param {*} initialValue The initial value for the state.
|
12
|
+
* @returns {Array<any>} A tuple containing the current value and a setter function.
|
13
|
+
*/
|
14
|
+
export function useConfig(initialValue) {
|
15
|
+
EffectManager.pause();
|
16
|
+
|
17
|
+
const
|
18
|
+
effect = EffectManager.getActiveEffect(),
|
19
|
+
component = effect && Neo.getComponent(effect.componentId);
|
20
|
+
|
21
|
+
if (!component) {
|
22
|
+
throw new Error('useConfig must be called from within a functional component\'s createVdom method.')
|
23
|
+
}
|
24
|
+
|
25
|
+
const currentIndex = component[hookIndexSymbol];
|
26
|
+
|
27
|
+
// Increment the index for the next hook call within the same component render cycle.
|
28
|
+
component[hookIndexSymbol]++;
|
29
|
+
|
30
|
+
// If this is the first time this hook is being called for this component, initialize its state.
|
31
|
+
if (!component[hooksSymbol][currentIndex]) {
|
32
|
+
const config = new Config(initialValue);
|
33
|
+
|
34
|
+
const customSetter = (newValue) => {
|
35
|
+
if (typeof newValue === 'function') {
|
36
|
+
newValue = newValue(config.get())
|
37
|
+
}
|
38
|
+
config.set(newValue)
|
39
|
+
};
|
40
|
+
|
41
|
+
component[hooksSymbol][currentIndex] = [config, customSetter]
|
42
|
+
}
|
43
|
+
|
44
|
+
const [config, setter] = component[hooksSymbol][currentIndex];
|
45
|
+
|
46
|
+
EffectManager.resume();
|
47
|
+
|
48
|
+
// Call config.get() to ensure this component's effect tracks this config as a dependency.
|
49
|
+
return [config.get(), setter]
|
50
|
+
}
|
51
|
+
|
52
|
+
export default useConfig;
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import EffectManager from '../core/EffectManager.mjs';
|
2
|
+
|
3
|
+
const pendingDomEventsSymbol = Symbol.for('pendingDomEvents');
|
4
|
+
|
5
|
+
/**
|
6
|
+
* A hook for registering DOM event listeners within a functional component.
|
7
|
+
* Event listeners registered via this hook will be managed by Neo.mjs's
|
8
|
+
* delegated DOM event system, ensuring efficient and proper lifecycle handling.
|
9
|
+
*
|
10
|
+
* @param {String} eventType - The type of DOM event to listen for (e.g., 'click', 'input').
|
11
|
+
* @param {Function} handler - The event handler function. It will receive the event data as its argument.
|
12
|
+
* @param {String} [delegate] - An optional CSS selector for event delegation. If provided,
|
13
|
+
* the handler will only fire if the event target matches this selector.
|
14
|
+
*/
|
15
|
+
export function useEvent(eventType, handler, delegate) {
|
16
|
+
const
|
17
|
+
activeEffect = EffectManager.getActiveEffect(),
|
18
|
+
componentId = activeEffect?.componentId;
|
19
|
+
|
20
|
+
if (!componentId) {
|
21
|
+
throw new Error('useEvent must be called from within a functional component\'s createVdom method.')
|
22
|
+
}
|
23
|
+
|
24
|
+
EffectManager.pause();
|
25
|
+
const component = Neo.getComponent(componentId);
|
26
|
+
EffectManager.resume();
|
27
|
+
|
28
|
+
if (!component) {
|
29
|
+
throw new Error(`Component with id ${componentId} not found for useEvent hook.`);
|
30
|
+
}
|
31
|
+
|
32
|
+
// Ensure pendingDomEventsSymbol exists on the component instance
|
33
|
+
component[pendingDomEventsSymbol] ??= [];
|
34
|
+
|
35
|
+
// Add the event listener configuration to the component's pending list
|
36
|
+
component[pendingDomEventsSymbol].push({
|
37
|
+
[eventType]: handler,
|
38
|
+
delegate,
|
39
|
+
scope : component // The component instance itself will be the scope
|
40
|
+
})
|
41
|
+
}
|
42
|
+
|
43
|
+
export default useEvent;
|