neo.mjs 10.0.0-beta.5 → 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.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/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 +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 +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 -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 +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 +217 -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/Base.mjs +104 -771
- 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 +55 -3
- package/src/core/Compare.mjs +4 -7
- package/src/core/Config.mjs +65 -52
- package/src/core/Effect.mjs +79 -13
- package/src/core/EffectBatchManager.mjs +18 -19
- package/src/core/EffectManager.mjs +25 -3
- 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 +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 +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 +98 -70
- 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 +7 -1
- package/src/util/VNode.mjs +7 -1
- package/src/util/vdom/TreeBuilder.mjs +129 -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/test/siesta/siesta.js +5 -2
- package/test/siesta/tests/VdomCalendar.mjs +13 -9
- package/test/siesta/tests/core/Effect.mjs +10 -14
- package/test/siesta/tests/core/EffectBatching.mjs +25 -37
- package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +255 -0
- package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +42 -55
- 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
@@ -0,0 +1,499 @@
|
|
1
|
+
import Base from '../../core/Base.mjs';
|
2
|
+
import ComponentManager from '../../manager/Component.mjs';
|
3
|
+
import DomEvents from '../../mixin/DomEvents.mjs';
|
4
|
+
import Effect from '../../core/Effect.mjs';
|
5
|
+
import NeoArray from '../../util/Array.mjs';
|
6
|
+
import Observable from '../../core/Observable.mjs';
|
7
|
+
import VdomLifecycle from '../../mixin/VdomLifecycle.mjs';
|
8
|
+
|
9
|
+
const
|
10
|
+
activeDomListenersSymbol = Symbol.for('activeDomListeners'),
|
11
|
+
hookIndexSymbol = Symbol.for('hookIndex'),
|
12
|
+
hooksSymbol = Symbol.for('hooks'),
|
13
|
+
pendingDomEventsSymbol = Symbol.for('pendingDomEvents'),
|
14
|
+
vdomToApplySymbol = Symbol('vdomToApply');
|
15
|
+
|
16
|
+
/**
|
17
|
+
* @class Neo.functional.component.Base
|
18
|
+
* @extends Neo.core.Base
|
19
|
+
* @mixes Neo.component.mixin.DomEvents
|
20
|
+
* @mixes Neo.core.Observable
|
21
|
+
* @mixes Neo.component.mixin.VdomLifecycle
|
22
|
+
*/
|
23
|
+
class FunctionalBase extends Base {
|
24
|
+
static config = {
|
25
|
+
/**
|
26
|
+
* @member {String} className='Neo.functional.component.Base'
|
27
|
+
* @protected
|
28
|
+
*/
|
29
|
+
className: 'Neo.functional.component.Base',
|
30
|
+
/**
|
31
|
+
* @member {String} ntype='functional-component'
|
32
|
+
* @protected
|
33
|
+
*/
|
34
|
+
ntype: 'functional-component',
|
35
|
+
/**
|
36
|
+
* Custom CSS selectors to apply to the root level node of this component
|
37
|
+
* @member {String[]} cls=null
|
38
|
+
* @reactive
|
39
|
+
*/
|
40
|
+
cls: null,
|
41
|
+
/**
|
42
|
+
* @member {Neo.core.Base[]} mixins=[DomEvents, Observable, VdomLifecycle]
|
43
|
+
*/
|
44
|
+
mixins: [DomEvents, Observable, VdomLifecycle],
|
45
|
+
/**
|
46
|
+
* True after the component render() method was called. Also fires the rendered event.
|
47
|
+
* @member {Boolean} mounted_=false
|
48
|
+
* @protected
|
49
|
+
* @reactive
|
50
|
+
*/
|
51
|
+
mounted_: false,
|
52
|
+
/**
|
53
|
+
* @member {String|null} parentId_=null
|
54
|
+
* @protected
|
55
|
+
* @reactive
|
56
|
+
*/
|
57
|
+
parentId_: null,
|
58
|
+
/**
|
59
|
+
* The vdom markup for this component.
|
60
|
+
* @member {Object} vdom={}
|
61
|
+
*/
|
62
|
+
vdom: {},
|
63
|
+
/**
|
64
|
+
* The custom windowIs (timestamp) this component belongs to
|
65
|
+
* @member {Number|null} windowId_=null
|
66
|
+
* @reactive
|
67
|
+
*/
|
68
|
+
windowId_: null
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* Neo component instances, which got defined inside createVdom()
|
73
|
+
* @member {Map|null} childComponents=null
|
74
|
+
*/
|
75
|
+
childComponents = null
|
76
|
+
/**
|
77
|
+
* Internal flag which will get set to true while a component is waiting for its mountedPromise
|
78
|
+
* @member {Boolean} isAwaitingMount=false
|
79
|
+
* @protected
|
80
|
+
*/
|
81
|
+
isAwaitingMount = false
|
82
|
+
/**
|
83
|
+
* Internal Map to store the next set of components after the createVdom() Effect has run.
|
84
|
+
* @member {Map|null} nextChildComponents=null
|
85
|
+
* @private
|
86
|
+
*/
|
87
|
+
#nextChildComponents = null
|
88
|
+
|
89
|
+
/**
|
90
|
+
* A Promise that resolves when the component is mounted to the DOM.
|
91
|
+
* This provides a convenient way to wait for the component to be fully
|
92
|
+
* available and interactive before executing subsequent logic.
|
93
|
+
*
|
94
|
+
* It also handles unmounting by resetting the promise, so it can be safely
|
95
|
+
* awaited again if the component is remounted.
|
96
|
+
* @returns {Promise<Neo.component.Base>}
|
97
|
+
*/
|
98
|
+
get mountedPromise() {
|
99
|
+
let me = this;
|
100
|
+
|
101
|
+
if (!me._mountedPromise) {
|
102
|
+
me._mountedPromise = new Promise(resolve => {
|
103
|
+
if (me.mounted) {
|
104
|
+
resolve(me);
|
105
|
+
} else {
|
106
|
+
me.mountedPromiseResolve = resolve
|
107
|
+
}
|
108
|
+
})
|
109
|
+
}
|
110
|
+
|
111
|
+
return me._mountedPromise
|
112
|
+
}
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Convenience method to access the parent component
|
116
|
+
* @returns {Neo.component.Base|null}
|
117
|
+
*/
|
118
|
+
get parent() {
|
119
|
+
let me = this;
|
120
|
+
|
121
|
+
return me.parentComponent || (me.parentId === 'document.body' ? null : Neo.getComponent(me.parentId))
|
122
|
+
}
|
123
|
+
|
124
|
+
/**
|
125
|
+
* @param {Object} config
|
126
|
+
*/
|
127
|
+
construct(config) {
|
128
|
+
super.construct(config);
|
129
|
+
|
130
|
+
let me = this,
|
131
|
+
opts = {configurable: true, enumerable: false, writable: true};
|
132
|
+
|
133
|
+
Object.defineProperties(me, {
|
134
|
+
[activeDomListenersSymbol]: {...opts, value: []},
|
135
|
+
[hookIndexSymbol] : {...opts, value: 0},
|
136
|
+
[hooksSymbol] : {...opts, value: []},
|
137
|
+
[pendingDomEventsSymbol] : {...opts, value: []},
|
138
|
+
[vdomToApplySymbol] : {...opts, value: null}
|
139
|
+
});
|
140
|
+
|
141
|
+
// Creates a reactive effect that re-executes createVdom() when dependencies change.
|
142
|
+
me.vdomEffect = new Effect({
|
143
|
+
fn: () => {
|
144
|
+
me[hookIndexSymbol] = 0;
|
145
|
+
me[pendingDomEventsSymbol] = []; // Clear pending events for new render
|
146
|
+
me[vdomToApplySymbol] = me.createVdom(me, me.data)
|
147
|
+
},
|
148
|
+
componentId: me.id,
|
149
|
+
subscriber : {
|
150
|
+
id : me.id,
|
151
|
+
fn : me.onEffectRunStateChange,
|
152
|
+
scope: me
|
153
|
+
}
|
154
|
+
})
|
155
|
+
}
|
156
|
+
|
157
|
+
/**
|
158
|
+
* Triggered after the id config got changed
|
159
|
+
* @param {String|null} value
|
160
|
+
* @param {String|null} oldValue
|
161
|
+
* @protected
|
162
|
+
*/
|
163
|
+
afterSetId(value, oldValue) {
|
164
|
+
super.afterSetId(value, oldValue);
|
165
|
+
|
166
|
+
oldValue && ComponentManager.unregister(oldValue);
|
167
|
+
value && ComponentManager.register(this)
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* Triggered after the mounted config got changed
|
172
|
+
* @param {Boolean} value
|
173
|
+
* @param {Boolean} oldValue
|
174
|
+
* @protected
|
175
|
+
*/
|
176
|
+
afterSetMounted(value, oldValue) {
|
177
|
+
if (oldValue !== undefined) {
|
178
|
+
const me = this;
|
179
|
+
|
180
|
+
if (value) { // mount
|
181
|
+
me.initDomEvents();
|
182
|
+
|
183
|
+
// Initial registration of DOM event listeners when component mounts
|
184
|
+
me.applyPendingDomListeners();
|
185
|
+
|
186
|
+
me.mountedPromiseResolve?.(this);
|
187
|
+
delete me.mountedPromiseResolve
|
188
|
+
} else { // unmount
|
189
|
+
delete me._mountedPromise
|
190
|
+
}
|
191
|
+
}
|
192
|
+
}
|
193
|
+
|
194
|
+
/**
|
195
|
+
* Triggered after the windowId config got changed
|
196
|
+
* @param {Number|null} value
|
197
|
+
* @param {Number|null} oldValue
|
198
|
+
* @protected
|
199
|
+
*/
|
200
|
+
afterSetWindowId(value, oldValue) {
|
201
|
+
const me = this;
|
202
|
+
|
203
|
+
if (value) {
|
204
|
+
Neo.currentWorker.insertThemeFiles(value, me.__proto__)
|
205
|
+
}
|
206
|
+
|
207
|
+
me.childComponents?.forEach(childData => {
|
208
|
+
childData.instance.windowId = value
|
209
|
+
})
|
210
|
+
|
211
|
+
// If a component gets moved into a different window, an update cycle might still be running.
|
212
|
+
// Since the update might no longer get mapped, we want to re-enable this instance for future updates.
|
213
|
+
if (oldValue) {
|
214
|
+
me.isVdomUpdating = false
|
215
|
+
}
|
216
|
+
}
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Applies the pending DOM event listeners and updates the active list.
|
220
|
+
* @private
|
221
|
+
*/
|
222
|
+
applyPendingDomListeners() {
|
223
|
+
const
|
224
|
+
me = this,
|
225
|
+
activeEvents = me[activeDomListenersSymbol],
|
226
|
+
pendingEvents = me[pendingDomEventsSymbol];
|
227
|
+
|
228
|
+
if (pendingEvents.length > 0) {
|
229
|
+
if (!Neo.isEqual(activeEvents, pendingEvents)) {
|
230
|
+
if (activeEvents?.length > 0) {
|
231
|
+
// Remove old dynamic listeners
|
232
|
+
me.removeDomListeners(me[activeDomListenersSymbol])
|
233
|
+
}
|
234
|
+
|
235
|
+
me.addDomListeners([...pendingEvents]);
|
236
|
+
|
237
|
+
me[activeDomListenersSymbol] = [...pendingEvents]
|
238
|
+
}
|
239
|
+
|
240
|
+
// Clear pending events for next `createVdom()` Effect run
|
241
|
+
me[pendingDomEventsSymbol] = []
|
242
|
+
}
|
243
|
+
}
|
244
|
+
|
245
|
+
/**
|
246
|
+
* Override this method in your functional component to return its VDOM structure.
|
247
|
+
* This method will be automatically re-executed when any of the component's configs change.
|
248
|
+
* @param {Neo.functional.component.Base} config - Mental model: while it contains the instance, it makes it clear to access configs
|
249
|
+
* @param {Object} data - Convenience shortcut for accessing `state.Provider` data
|
250
|
+
* @returns {Object} The VDOM structure for the component.
|
251
|
+
*/
|
252
|
+
createVdom(config, data) {
|
253
|
+
// This method should be overridden by subclasses
|
254
|
+
return {}
|
255
|
+
}
|
256
|
+
|
257
|
+
/**
|
258
|
+
*
|
259
|
+
*/
|
260
|
+
destroy() {
|
261
|
+
const me = this;
|
262
|
+
|
263
|
+
me.vdomEffect?.destroy();
|
264
|
+
|
265
|
+
// Destroy all classic components instantiated by this functional component
|
266
|
+
me.childComponents?.forEach(childData => {
|
267
|
+
childData.instance.destroy()
|
268
|
+
});
|
269
|
+
me.childComponents?.clear();
|
270
|
+
|
271
|
+
me.removeDomEvents();
|
272
|
+
|
273
|
+
// Remove any pending DOM event listeners that might not have been mounted
|
274
|
+
me[pendingDomEventsSymbol] = null;
|
275
|
+
|
276
|
+
ComponentManager.unregister(me);
|
277
|
+
|
278
|
+
super.destroy()
|
279
|
+
}
|
280
|
+
|
281
|
+
/**
|
282
|
+
* This method recursively compares the new VDOM config with the last applied config
|
283
|
+
* for a given component instance and its sub-instances.
|
284
|
+
* @param {Neo.core.Base} instance The component instance to update.
|
285
|
+
* @param {Object} newConfig The new configuration object from the VDOM.
|
286
|
+
* @param {Object} lastConfig The last applied configuration object.
|
287
|
+
* @private
|
288
|
+
*/
|
289
|
+
diffAndSet(instance, newConfig, lastConfig) {
|
290
|
+
const deltaConfig = {};
|
291
|
+
|
292
|
+
for (const key in newConfig) {
|
293
|
+
const newValue = newConfig[key],
|
294
|
+
oldValue = lastConfig[key];
|
295
|
+
|
296
|
+
if (!Neo.isEqual(newValue, oldValue)) {
|
297
|
+
// If the config property is an object and it maps to a sub-component instance, recurse.
|
298
|
+
if (Neo.typeOf(newValue) === 'Object' && Neo.typeOf(instance[key]) === 'NeoInstance') {
|
299
|
+
this.diffAndSet(instance[key], newValue, oldValue || {})
|
300
|
+
} else {
|
301
|
+
// Otherwise, add it to the delta to be set on the current instance.
|
302
|
+
deltaConfig[key] = newValue
|
303
|
+
}
|
304
|
+
}
|
305
|
+
}
|
306
|
+
|
307
|
+
// Only call set() if there are actual changes for the current instance.
|
308
|
+
if (Object.keys(deltaConfig).length > 0) {
|
309
|
+
instance.set(deltaConfig)
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
/**
|
314
|
+
* This handler runs when the effect's `isRunning` state changes.
|
315
|
+
* It runs outside the effect's tracking scope, preventing feedback loops.
|
316
|
+
* @param {Boolean} value
|
317
|
+
* @param {Boolean} oldValue
|
318
|
+
*/
|
319
|
+
onEffectRunStateChange(value, oldValue) {
|
320
|
+
// When the effect has just finished running...
|
321
|
+
if (value === false) {
|
322
|
+
const me = this,
|
323
|
+
newVdom = me[vdomToApplySymbol];
|
324
|
+
|
325
|
+
if (newVdom) {
|
326
|
+
// Create a new map for components instantiated in this render cycle
|
327
|
+
me.#nextChildComponents = new Map();
|
328
|
+
|
329
|
+
// Process the newVdom to instantiate components
|
330
|
+
// The parentId for these components will be the functional component's id
|
331
|
+
const processedVdom = me.processVdomForComponents(newVdom, me.id);
|
332
|
+
|
333
|
+
// Destroy components that are no longer present in the new VDOM
|
334
|
+
if (me.childComponents?.size > 0) {
|
335
|
+
[...me.childComponents].forEach(([key, childData]) => {
|
336
|
+
if (!me.#nextChildComponents.has(key)) {
|
337
|
+
me.childComponents.delete(key);
|
338
|
+
childData.instance.destroy()
|
339
|
+
}
|
340
|
+
})
|
341
|
+
}
|
342
|
+
|
343
|
+
// If this component created other classic or functional components,
|
344
|
+
// include their full vdom into the next update cycle.
|
345
|
+
const oldKeys = me.childComponents ? new Set(me.childComponents.keys()) : new Set();
|
346
|
+
let hasNewChildren = false;
|
347
|
+
|
348
|
+
for (const newKey of me.#nextChildComponents.keys()) {
|
349
|
+
if (!oldKeys.has(newKey)) {
|
350
|
+
hasNewChildren = true;
|
351
|
+
break
|
352
|
+
}
|
353
|
+
}
|
354
|
+
|
355
|
+
if (hasNewChildren) {
|
356
|
+
// When new child components are created, we need to send their full VDOM
|
357
|
+
// to the vdom-worker, so they can get rendered.
|
358
|
+
// Subsequent updates will be granular via diffAndSet() => set() on the child.
|
359
|
+
me.updateDepth = -1;
|
360
|
+
}
|
361
|
+
|
362
|
+
// Update the main map of instantiated components
|
363
|
+
me.childComponents = me.#nextChildComponents;
|
364
|
+
|
365
|
+
// Clear the old vdom properties
|
366
|
+
for (const key in me.vdom) {
|
367
|
+
delete me.vdom[key]
|
368
|
+
}
|
369
|
+
|
370
|
+
// Assign the new properties
|
371
|
+
Object.assign(me.vdom, processedVdom); // Use processedVdom here
|
372
|
+
|
373
|
+
me[vdomToApplySymbol] = null;
|
374
|
+
|
375
|
+
const root = me.getVdomRoot();
|
376
|
+
|
377
|
+
if (me.cls) {
|
378
|
+
root.cls = NeoArray.union(me.cls, root.cls)
|
379
|
+
}
|
380
|
+
|
381
|
+
if (me.id) {
|
382
|
+
root.id = me.id
|
383
|
+
}
|
384
|
+
|
385
|
+
me.updateVdom();
|
386
|
+
|
387
|
+
// Update DOM event listeners based on the new render
|
388
|
+
if (me.mounted) {
|
389
|
+
me.applyPendingDomListeners()
|
390
|
+
}
|
391
|
+
}
|
392
|
+
}
|
393
|
+
}
|
394
|
+
|
395
|
+
/**
|
396
|
+
* Recursively processes a VDOM node to instantiate components defined within it.
|
397
|
+
* @param {Object} vdomTree The VDOM node to process.
|
398
|
+
* @param {String} parentId The ID of the parent component (the functional component hosting it).
|
399
|
+
* @param {Number} [parentIndex] The index of the vdomNode within its parent's children.
|
400
|
+
* @returns {Object} The processed VDOM node, potentially replaced with a component reference.
|
401
|
+
* @private
|
402
|
+
*/
|
403
|
+
processVdomForComponents(vdomTree, parentId, parentIndex) {
|
404
|
+
if (!vdomTree) {
|
405
|
+
return vdomTree
|
406
|
+
}
|
407
|
+
|
408
|
+
// If it's already a component reference, no need to process further
|
409
|
+
if (vdomTree.componentId) {
|
410
|
+
return vdomTree
|
411
|
+
}
|
412
|
+
|
413
|
+
const me = this;
|
414
|
+
|
415
|
+
// Check if it's a component definition (functional or classic)
|
416
|
+
if (vdomTree.className || vdomTree.module || vdomTree.ntype) {
|
417
|
+
// Components are reconciled based on their `id` property in the VDOM definition.
|
418
|
+
// If no `id` is provided, a new instance will be created on every render.
|
419
|
+
const componentKey = vdomTree.id;
|
420
|
+
|
421
|
+
if (!componentKey) {
|
422
|
+
console.error([
|
423
|
+
'Component definition in functional component VDOM is missing an "id". For stable reconciliation, ',
|
424
|
+
'especially in dynamic lists, provide a unique "id" property.'
|
425
|
+
].join(''),
|
426
|
+
vdomTree
|
427
|
+
)
|
428
|
+
}
|
429
|
+
|
430
|
+
let childData = me.childComponents?.get(componentKey),
|
431
|
+
newConfig = {...vdomTree}, // Shallow copy
|
432
|
+
instance;
|
433
|
+
|
434
|
+
delete newConfig.className;
|
435
|
+
delete newConfig.id;
|
436
|
+
delete newConfig.module;
|
437
|
+
delete newConfig.ntype;
|
438
|
+
|
439
|
+
if (!childData) {
|
440
|
+
me.childComponents ??= new Map();
|
441
|
+
|
442
|
+
// Instantiate the component
|
443
|
+
instance = Neo[(vdomTree.className || vdomTree.module) ? 'create' : 'ntype']({
|
444
|
+
...vdomTree,
|
445
|
+
parentId,
|
446
|
+
parentIndex,
|
447
|
+
windowId: me.windowId
|
448
|
+
});
|
449
|
+
} else {
|
450
|
+
instance = childData.instance;
|
451
|
+
|
452
|
+
// Recursively diff and set configs
|
453
|
+
this.diffAndSet(instance, newConfig, childData.lastConfig);
|
454
|
+
}
|
455
|
+
|
456
|
+
// Add to the new map for tracking in this render cycle
|
457
|
+
me.#nextChildComponents.set(componentKey, {
|
458
|
+
instance,
|
459
|
+
lastConfig: newConfig
|
460
|
+
});
|
461
|
+
|
462
|
+
// Replace the definition with a reference using the component's own method
|
463
|
+
return instance.createVdomReference();
|
464
|
+
}
|
465
|
+
|
466
|
+
// Recursively process children
|
467
|
+
if (vdomTree.cn && Array.isArray(vdomTree.cn)) {
|
468
|
+
vdomTree.cn = vdomTree.cn.map((child, index) =>
|
469
|
+
me.processVdomForComponents(child, parentId, index)
|
470
|
+
)
|
471
|
+
}
|
472
|
+
|
473
|
+
return vdomTree
|
474
|
+
}
|
475
|
+
|
476
|
+
/**
|
477
|
+
* Change multiple configs at once, ensuring that all afterSet methods get all new assigned values
|
478
|
+
* @param {Object} values={}
|
479
|
+
* @param {Boolean} silent=false
|
480
|
+
* @returns {Promise<*>}
|
481
|
+
*/
|
482
|
+
set(values={}, silent=false) {
|
483
|
+
let me = this;
|
484
|
+
|
485
|
+
me.silentVdomUpdate = true;
|
486
|
+
|
487
|
+
super.set(values);
|
488
|
+
|
489
|
+
me.silentVdomUpdate = false;
|
490
|
+
|
491
|
+
if (silent || !me.needsVdomUpdate) {
|
492
|
+
return Promise.resolve()
|
493
|
+
}
|
494
|
+
|
495
|
+
return me.promiseUpdate()
|
496
|
+
}
|
497
|
+
}
|
498
|
+
|
499
|
+
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;
|