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,366 @@
|
|
1
|
+
import Neo from '../../../../src/Neo.mjs';
|
2
|
+
import * as core from '../../../../src/core/_export.mjs';
|
3
|
+
import ComponentManager from '../../../../src/manager/Component.mjs';
|
4
|
+
import DomApiVnodeCreator from '../../../../src/vdom/util/DomApiVnodeCreator.mjs';
|
5
|
+
import TreeBuilder from '../../../../src/util/vdom/TreeBuilder.mjs';
|
6
|
+
import VDomUpdate from '../../../../src/manager/VDomUpdate.mjs';
|
7
|
+
import VdomLifecycle from '../../../../src/mixin/VdomLifecycle.mjs';
|
8
|
+
import VdomHelper from '../../../../src/vdom/Helper.mjs';
|
9
|
+
import VDomUtil from '../../../../src/util/VDom.mjs';
|
10
|
+
|
11
|
+
// IMPORTANT: Test with the new standard renderer
|
12
|
+
Neo.config.useDomApiRenderer = true;
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Creates a mock component object for testing.
|
16
|
+
* @param {string} id
|
17
|
+
* @param {string} parentId
|
18
|
+
* @param {Object} vdom
|
19
|
+
* @returns {Object} A mock component
|
20
|
+
*/
|
21
|
+
const createMockComponent = (id, parentId, vdom) => {
|
22
|
+
const component = {
|
23
|
+
id,
|
24
|
+
parentId,
|
25
|
+
vdom,
|
26
|
+
// Add properties from VdomLifecycle that we need to test
|
27
|
+
isVdomUpdating: false,
|
28
|
+
// By adding the prototype methods to our mock instances, we can test
|
29
|
+
// the lifecycle logic without needing full component instantiation.
|
30
|
+
hasUpdateCollision: VdomLifecycle.prototype.hasUpdateCollision,
|
31
|
+
isParentUpdating : VdomLifecycle.prototype.isParentUpdating,
|
32
|
+
};
|
33
|
+
// Create the initial vnode from the vdom definition.
|
34
|
+
const { vnode } = VdomHelper.create({ vdom });
|
35
|
+
component.vnode = vnode;
|
36
|
+
|
37
|
+
// Register the component BEFORE syncing IDs. This is critical so that
|
38
|
+
// a parent's syncVdomIds call can find this component if it's a child.
|
39
|
+
ComponentManager.register(component);
|
40
|
+
VDomUtil.syncVdomIds(component.vnode, component.vdom);
|
41
|
+
|
42
|
+
return component;
|
43
|
+
};
|
44
|
+
|
45
|
+
StartTest(t => {
|
46
|
+
|
47
|
+
t.beforeEach(() => {
|
48
|
+
// Reset managers to ensure test isolation
|
49
|
+
VDomUpdate.mergedCallbackMap.clear();
|
50
|
+
VDomUpdate.postUpdateQueueMap.clear();
|
51
|
+
ComponentManager.wrapperNodes.clear();
|
52
|
+
ComponentManager.clear();
|
53
|
+
});
|
54
|
+
|
55
|
+
t.it('Should handle asymmetric update with depth 2 using DomApiRenderer', t => {
|
56
|
+
// 1. SETUP
|
57
|
+
// Create a parent and a child. The parent's vdom references the child via componentId.
|
58
|
+
const childVdomInitial = { id: 'child-1', cn: [{ tag: 'span', text: 'Initial' }] };
|
59
|
+
const parentVdom = {
|
60
|
+
id: 'parent-1',
|
61
|
+
cn: [{ componentId: 'child-1' }]
|
62
|
+
};
|
63
|
+
|
64
|
+
// Create components dependency-first (child before parent) to ensure
|
65
|
+
// component references can be resolved during VDOM/VNode processing.
|
66
|
+
// The `createMockComponent` factory now handles registration.
|
67
|
+
let child = createMockComponent('child-1', 'parent-1', childVdomInitial);
|
68
|
+
let parent = createMockComponent('parent-1', 'root', parentVdom);
|
69
|
+
|
70
|
+
// 2. SIMULATE A CHILD-INITIATED UPDATE
|
71
|
+
// The child's internal state changes, and it requests to be part of the parent's next update.
|
72
|
+
VDomUpdate.registerMerged(
|
73
|
+
parent.id,
|
74
|
+
child.id,
|
75
|
+
1, // childUpdateDepth
|
76
|
+
1 // distance
|
77
|
+
);
|
78
|
+
|
79
|
+
// The child's vdom has now changed. We update our mock to reflect this.
|
80
|
+
// By mutating the existing vdom object, we ensure stable IDs are preserved for diffing.
|
81
|
+
child.vdom.cn[0].text = 'Updated';
|
82
|
+
|
83
|
+
// 3. SIMULATE THE PARENT'S UPDATE LIFECYCLE
|
84
|
+
// The parent calculates the required depth for the update.
|
85
|
+
const adjustedDepth = VDomUpdate.getAdjustedUpdateDepth(parent.id);
|
86
|
+
t.is(adjustedDepth, 2, 'Adjusted update depth should be 2 to include direct children');
|
87
|
+
|
88
|
+
// The parent builds an asymmetric VDOM tree. TreeBuilder will find the updated
|
89
|
+
// child.vdom via the ComponentManager.
|
90
|
+
const newAsymmetricVdom = TreeBuilder.getVdomTree(parent.vdom, adjustedDepth);
|
91
|
+
|
92
|
+
// Verify the created tree has the child's *new* vdom
|
93
|
+
t.is(newAsymmetricVdom.cn[0].id, 'child-1', 'The child component VDOM is expanded in the asymmetric tree');
|
94
|
+
t.is(newAsymmetricVdom.cn[0].cn[0].text, 'Updated', 'The expanded VDOM reflects the childs updated state');
|
95
|
+
|
96
|
+
// 4. GENERATE DELTAS
|
97
|
+
// VdomHelper diffs the new, expanded tree against the parent's OLD vnode.
|
98
|
+
// The old vnode must also be expanded to the same depth to ensure a correct diff.
|
99
|
+
const oldAsymmetricVnode = TreeBuilder.getVnodeTree(parent.vnode, adjustedDepth);
|
100
|
+
const { deltas } = VdomHelper.update({
|
101
|
+
vdom : newAsymmetricVdom,
|
102
|
+
vnode: oldAsymmetricVnode
|
103
|
+
});
|
104
|
+
|
105
|
+
// 5. ASSERTIONS
|
106
|
+
// For useDomApiRenderer=true, a text change results in a delta updating
|
107
|
+
// the `textContent` property of the parent element's vnode.
|
108
|
+
t.is(deltas.length, 1, 'Should generate exactly one delta for the text change');
|
109
|
+
const spanVnode = oldAsymmetricVnode.childNodes[0].childNodes[0];
|
110
|
+
const delta = deltas[0];
|
111
|
+
|
112
|
+
t.is(delta.id, spanVnode.id, 'Delta targets the correct span element');
|
113
|
+
t.is(delta.textContent, 'Updated', 'The new text content should be correct');
|
114
|
+
t.is(Object.keys(delta).length, 2, 'Delta has the correct shape (id, textContent)');
|
115
|
+
});
|
116
|
+
|
117
|
+
t.it('Should handle nested asymmetric update (grandchild update)', t => {
|
118
|
+
// 1. SETUP
|
119
|
+
const grandchildVdomInitial = { id: 'grandchild-1', cn: [{ tag: 'span', text: 'Initial' }] };
|
120
|
+
const childVdom = {
|
121
|
+
id: 'child-1',
|
122
|
+
cn: [{ componentId: 'grandchild-1' }]
|
123
|
+
};
|
124
|
+
const parentVdom = {
|
125
|
+
id: 'parent-1',
|
126
|
+
cn: [{ componentId: 'child-1' }]
|
127
|
+
};
|
128
|
+
|
129
|
+
// Create components dependency-first (grandchild -> child -> parent) to ensure
|
130
|
+
// component references can be resolved during VDOM/VNode processing.
|
131
|
+
const grandchild = createMockComponent('grandchild-1', 'child-1', grandchildVdomInitial);
|
132
|
+
createMockComponent('child-1', 'parent-1', childVdom);
|
133
|
+
let parent = createMockComponent('parent-1', 'root', parentVdom);
|
134
|
+
|
135
|
+
// 2. SIMULATE A GRANDCHILD-INITIATED UPDATE
|
136
|
+
// The grandchild's state changes. It is at a distance of 2 from the updating parent.
|
137
|
+
VDomUpdate.registerMerged(
|
138
|
+
parent.id,
|
139
|
+
grandchild.id,
|
140
|
+
1, // grandchild's own updateDepth
|
141
|
+
2 // distance from parent
|
142
|
+
);
|
143
|
+
|
144
|
+
// The grandchild's vdom has now changed.
|
145
|
+
// By mutating the existing vdom object, we ensure stable IDs are preserved for diffing.
|
146
|
+
grandchild.vdom.cn[0].text = 'Updated';
|
147
|
+
|
148
|
+
// 3. SIMULATE THE PARENT'S UPDATE LIFECYCLE
|
149
|
+
// The required depth for the parent should be 3 to expand down to the grandchild.
|
150
|
+
const adjustedDepth = VDomUpdate.getAdjustedUpdateDepth(parent.id);
|
151
|
+
t.is(adjustedDepth, 3, 'Adjusted update depth should be 3 to include grandchild');
|
152
|
+
|
153
|
+
// The parent builds an asymmetric VDOM tree.
|
154
|
+
const newAsymmetricVdom = TreeBuilder.getVdomTree(parent.vdom, adjustedDepth);
|
155
|
+
|
156
|
+
// Verify the created tree has the grandchild's *new* vdom
|
157
|
+
const expandedChild = newAsymmetricVdom.cn[0];
|
158
|
+
const expandedGrandchild = expandedChild.cn[0];
|
159
|
+
t.is(expandedGrandchild.id, 'grandchild-1', 'The grandchild component VDOM is expanded in the asymmetric tree');
|
160
|
+
t.is(expandedGrandchild.cn[0].text, 'Updated', 'The expanded VDOM reflects the grandchilds updated state');
|
161
|
+
|
162
|
+
// 4. GENERATE DELTAS
|
163
|
+
const oldAsymmetricVnode = TreeBuilder.getVnodeTree(parent.vnode, adjustedDepth);
|
164
|
+
const { deltas } = VdomHelper.update({
|
165
|
+
vdom : newAsymmetricVdom,
|
166
|
+
vnode: oldAsymmetricVnode
|
167
|
+
});
|
168
|
+
|
169
|
+
// 5. ASSERTIONS
|
170
|
+
// For useDomApiRenderer=true, a text change results in a delta updating
|
171
|
+
// the `textContent` property of the parent element's vnode.
|
172
|
+
t.is(deltas.length, 1, 'Should generate exactly one delta for the text change');
|
173
|
+
const spanVnode = oldAsymmetricVnode.childNodes[0].childNodes[0].childNodes[0];
|
174
|
+
const delta = deltas[0];
|
175
|
+
|
176
|
+
t.is(delta.id, spanVnode.id, 'Delta targets the correct span element');
|
177
|
+
t.is(delta.textContent, 'Updated', 'The new text content should be correct');
|
178
|
+
t.is(Object.keys(delta).length, 2, 'Delta has the correct shape (id, textContent)');
|
179
|
+
});
|
180
|
+
|
181
|
+
t.it('Should handle structural change in a deeply nested component', t => {
|
182
|
+
// 1. SETUP
|
183
|
+
const grandchildVdomInitial = { id: 'grandchild-1', cn: [{ tag: 'span', text: 'Initial' }] };
|
184
|
+
const childVdom = {
|
185
|
+
id: 'child-1',
|
186
|
+
cn: [{ componentId: 'grandchild-1' }]
|
187
|
+
};
|
188
|
+
const parentVdom = {
|
189
|
+
id: 'parent-1',
|
190
|
+
cn: [{ componentId: 'child-1' }]
|
191
|
+
};
|
192
|
+
|
193
|
+
// Create components dependency-first
|
194
|
+
let grandchild = createMockComponent('grandchild-1', 'child-1', grandchildVdomInitial);
|
195
|
+
createMockComponent('child-1', 'parent-1', childVdom);
|
196
|
+
let parent = createMockComponent('parent-1', 'root', parentVdom);
|
197
|
+
|
198
|
+
// 2. SIMULATE A GRANDCHILD-INITIATED UPDATE
|
199
|
+
VDomUpdate.registerMerged(
|
200
|
+
parent.id,
|
201
|
+
grandchild.id,
|
202
|
+
1, // grandchild's own updateDepth
|
203
|
+
2 // distance from parent
|
204
|
+
);
|
205
|
+
|
206
|
+
// The grandchild's vdom has a structural change.
|
207
|
+
grandchild.vdom.cn.push({ id: 'new-node', tag: 'div', text: 'New Node' });
|
208
|
+
|
209
|
+
// 3. SIMULATE THE PARENT'S UPDATE LIFECYCLE
|
210
|
+
const adjustedDepth = VDomUpdate.getAdjustedUpdateDepth(parent.id);
|
211
|
+
t.is(adjustedDepth, 3, 'Adjusted update depth should be 3 to include grandchild');
|
212
|
+
|
213
|
+
const newAsymmetricVdom = TreeBuilder.getVdomTree(parent.vdom, adjustedDepth);
|
214
|
+
const oldAsymmetricVnode = TreeBuilder.getVnodeTree(parent.vnode, adjustedDepth);
|
215
|
+
|
216
|
+
// Verify the new VDOM structure
|
217
|
+
const expandedGrandchildVdom = newAsymmetricVdom.cn[0].cn[0];
|
218
|
+
t.is(expandedGrandchildVdom.cn.length, 2, 'New VDOM for grandchild has 2 children');
|
219
|
+
t.is(expandedGrandchildVdom.cn[1].id, 'new-node', 'New node is present in the asymmetric VDOM');
|
220
|
+
|
221
|
+
// 4. GENERATE DELTAS
|
222
|
+
const { deltas } = VdomHelper.update({
|
223
|
+
vdom : newAsymmetricVdom,
|
224
|
+
vnode: oldAsymmetricVnode
|
225
|
+
});
|
226
|
+
|
227
|
+
// 5. ASSERTIONS
|
228
|
+
t.is(deltas.length, 1, 'Should generate one delta for the insertion');
|
229
|
+
const delta = deltas[0];
|
230
|
+
const newNodeVdom = expandedGrandchildVdom.cn[1];
|
231
|
+
|
232
|
+
t.is(delta.action, 'insertNode', 'Delta action should be insertNode');
|
233
|
+
t.is(delta.parentId, grandchild.vdom.id, 'Delta parentId should be the grandchild');
|
234
|
+
t.is(delta.index, 1, 'Delta index should be 1');
|
235
|
+
|
236
|
+
// For DomApiRenderer, the vnode is passed directly.
|
237
|
+
t.is(delta.vnode.id, newNodeVdom.id, 'Inserted vnode has the correct ID');
|
238
|
+
t.is(delta.vnode.textContent, 'New Node', 'Inserted vnode has the correct text');
|
239
|
+
});
|
240
|
+
|
241
|
+
t.it('Should handle update collision (isParentUpdating)', t => {
|
242
|
+
// 1. SETUP
|
243
|
+
const childVdom = { id: 'child-1', text: 'child' };
|
244
|
+
const parentVdom = { id: 'parent-1', cn: [{ componentId: 'child-1' }] };
|
245
|
+
|
246
|
+
let child = createMockComponent('child-1', 'parent-1', childVdom);
|
247
|
+
let parent = createMockComponent('parent-1', 'root', parentVdom);
|
248
|
+
|
249
|
+
// 2. SIMULATE A PARENT UPDATE IN PROGRESS
|
250
|
+
// This is the state during a real update, before post-processing.
|
251
|
+
parent.isVdomUpdating = true;
|
252
|
+
VDomUpdate.registerInFlightUpdate(parent.id, 2);
|
253
|
+
|
254
|
+
// 3. SIMULATE A CHILD-INITIATED UPDATE (during the parent's update)
|
255
|
+
let hasCollision = child.isParentUpdating(child.parentId, () => {});
|
256
|
+
|
257
|
+
// 4. ASSERTIONS
|
258
|
+
t.ok(hasCollision, 'isParentUpdating should return true, detecting a collision');
|
259
|
+
const postUpdateQueue = VDomUpdate.postUpdateQueueMap.get(parent.id);
|
260
|
+
t.ok(postUpdateQueue, 'Parent should have a post-update queue');
|
261
|
+
t.is(postUpdateQueue.children.length, 1, 'Post-update queue should have one entry');
|
262
|
+
t.is(postUpdateQueue.children[0].childId, child.id, 'The queued item should be the child component');
|
263
|
+
});
|
264
|
+
|
265
|
+
t.it('Should not detect a collision if updateDepth is insufficient', t => {
|
266
|
+
// 1. SETUP
|
267
|
+
const grandchildVdom = { id: 'grandchild-1', text: 'grandchild' };
|
268
|
+
const childVdom = { id: 'child-1', cn: [{ componentId: 'grandchild-1' }] };
|
269
|
+
const parentVdom = { id: 'parent-1', cn: [{ componentId: 'child-1' }] };
|
270
|
+
|
271
|
+
let grandchild = createMockComponent('grandchild-1', 'child-1', grandchildVdom);
|
272
|
+
createMockComponent('child-1', 'parent-1', childVdom);
|
273
|
+
let parent = createMockComponent('parent-1', 'root', parentVdom);
|
274
|
+
|
275
|
+
// 2. SIMULATE A PARENT UPDATE IN PROGRESS
|
276
|
+
parent.isVdomUpdating = true;
|
277
|
+
VDomUpdate.registerInFlightUpdate(parent.id, 2);
|
278
|
+
|
279
|
+
// 3. SIMULATE A GRANDCHILD-INITIATED UPDATE
|
280
|
+
// The grandchild is at distance 2 from the parent. hasUpdateCollision(2, 2) should be false.
|
281
|
+
let hasCollision = grandchild.isParentUpdating(grandchild.parentId, () => {});
|
282
|
+
|
283
|
+
// 4. ASSERTIONS
|
284
|
+
t.notOk(hasCollision, 'isParentUpdating should return false, no collision detected');
|
285
|
+
const postUpdateQueue = VDomUpdate.postUpdateQueueMap.get(parent.id);
|
286
|
+
t.notOk(postUpdateQueue, 'Parent should not have a post-update queue');
|
287
|
+
});
|
288
|
+
|
289
|
+
t.it('Should handle merged updates from multiple non-contiguous children', t => {
|
290
|
+
// 1. SETUP
|
291
|
+
// Parent -> Child1 -> Grandchild1
|
292
|
+
// Parent -> Child2
|
293
|
+
// Parent -> Child3
|
294
|
+
const grandchildVdom = { id: 'grandchild-1', cn: [{ tag: 'span', text: 'Initial GC' }] };
|
295
|
+
const child1Vdom = { id: 'child-1', cn: [{ componentId: 'grandchild-1' }] };
|
296
|
+
const child2Vdom = { id: 'child-2', text: 'Initial C2' };
|
297
|
+
const child3Vdom = { id: 'child-3', text: 'Initial C3' };
|
298
|
+
const parentVdom = {
|
299
|
+
id: 'parent-1',
|
300
|
+
cn: [
|
301
|
+
{ componentId: 'child-1' },
|
302
|
+
{ componentId: 'child-2' },
|
303
|
+
{ componentId: 'child-3' }
|
304
|
+
]
|
305
|
+
};
|
306
|
+
|
307
|
+
// Create components
|
308
|
+
let grandchild1 = createMockComponent('grandchild-1', 'child-1', grandchildVdom);
|
309
|
+
createMockComponent('child-1', 'parent-1', child1Vdom);
|
310
|
+
createMockComponent('child-2', 'parent-1', child2Vdom);
|
311
|
+
let child3 = createMockComponent('child-3', 'parent-1', child3Vdom);
|
312
|
+
let parent = createMockComponent('parent-1', 'root', parentVdom);
|
313
|
+
|
314
|
+
// 2. SIMULATE MULTIPLE MERGED UPDATES
|
315
|
+
// Grandchild1 (at distance 2) requests an update
|
316
|
+
VDomUpdate.registerMerged(parent.id, grandchild1.id, 1, 2);
|
317
|
+
// Child3 (at distance 1) requests an update
|
318
|
+
VDomUpdate.registerMerged(parent.id, child3.id, 1, 1);
|
319
|
+
|
320
|
+
// Make the changes to the source vdoms
|
321
|
+
grandchild1.vdom.cn[0].text = 'Updated GC';
|
322
|
+
child3.vdom.text = 'Updated C3';
|
323
|
+
|
324
|
+
// 3. SIMULATE THE PARENT'S UPDATE LIFECYCLE
|
325
|
+
const adjustedDepth = VDomUpdate.getAdjustedUpdateDepth(parent.id);
|
326
|
+
// Depth for GC1 is 2(dist) + 1(depth) = 3. Depth for C3 is 1(dist) + 1(depth) = 2.
|
327
|
+
// The max should be taken.
|
328
|
+
t.is(adjustedDepth, 3, 'Adjusted update depth should be 3, the max required by children');
|
329
|
+
|
330
|
+
const newAsymmetricVdom = TreeBuilder.getVdomTree(parent.vdom, adjustedDepth);
|
331
|
+
const oldAsymmetricVnode = TreeBuilder.getVnodeTree(parent.vnode, adjustedDepth);
|
332
|
+
|
333
|
+
// Verify the new VDOM structure is correctly expanded.
|
334
|
+
// TreeBuilder expands based on depth, so non-updating siblings within the depth are also expanded.
|
335
|
+
const [expChild1, expChild2, expChild3] = newAsymmetricVdom.cn;
|
336
|
+
// Child1 should be expanded to reveal Grandchild1
|
337
|
+
t.is(expChild1.cn[0].id, 'grandchild-1', 'Child1 is expanded');
|
338
|
+
t.is(expChild1.cn[0].cn[0].text, 'Updated GC', 'Grandchild1 vdom is updated');
|
339
|
+
// Child2 is also expanded because the adjustedDepth of 3 is enough to reach it
|
340
|
+
t.notOk(expChild2.componentId, 'Child2 IS expanded due to update depth');
|
341
|
+
t.is(expChild2.text, 'Initial C2', 'Child2 is fully expanded');
|
342
|
+
// Child3 should be expanded as it requested the update
|
343
|
+
t.is(expChild3.text, 'Updated C3', 'Child3 vdom is updated');
|
344
|
+
|
345
|
+
// 4. GENERATE DELTAS
|
346
|
+
const { deltas } = VdomHelper.update({
|
347
|
+
vdom: newAsymmetricVdom,
|
348
|
+
vnode: oldAsymmetricVnode
|
349
|
+
});
|
350
|
+
|
351
|
+
// 5. ASSERTIONS
|
352
|
+
t.is(deltas.length, 2, 'Should generate two deltas for the two changes');
|
353
|
+
|
354
|
+
const gcSpanVnode = oldAsymmetricVnode.childNodes[0].childNodes[0].childNodes[0];
|
355
|
+
const c3Vnode = oldAsymmetricVnode.childNodes[2];
|
356
|
+
|
357
|
+
const delta1 = deltas.find(d => d.id === gcSpanVnode.id);
|
358
|
+
const delta2 = deltas.find(d => d.id === c3Vnode.id);
|
359
|
+
|
360
|
+
t.ok(delta1, 'A delta for the grandchild change should exist');
|
361
|
+
t.is(delta1.textContent, 'Updated GC', 'Grandchild text delta is correct');
|
362
|
+
|
363
|
+
t.ok(delta2, 'A delta for the child3 change should exist');
|
364
|
+
t.is(delta2.textContent, 'Updated C3', 'Child3 text delta is correct');
|
365
|
+
});
|
366
|
+
});
|
@@ -0,0 +1,249 @@
|
|
1
|
+
import Neo from '../../../../src/Neo.mjs';
|
2
|
+
import * as core from '../../../../src/core/_export.mjs';
|
3
|
+
import Component from '../../../../src/component/Base.mjs';
|
4
|
+
import Container from '../../../../src/container/Base.mjs';
|
5
|
+
import DomApiVnodeCreator from '../../../../src/vdom/util/DomApiVnodeCreator.mjs';
|
6
|
+
import VdomHelper from '../../../../src/vdom/Helper.mjs';
|
7
|
+
|
8
|
+
// IMPORTANT: This test file uses real components and expects them to render.
|
9
|
+
// We need to enable unitTestMode for isolation, but also allow VDOM updates.
|
10
|
+
Neo.config.unitTestMode = true;
|
11
|
+
Neo.config.allowVdomUpdatesInTests = true;
|
12
|
+
// This ensures that the VdomHelper uses the correct renderer for the assertions.
|
13
|
+
Neo.config.useDomApiRenderer = true;
|
14
|
+
|
15
|
+
// Create a mock application context, as the component lifecycle requires it for updates.
|
16
|
+
const appName = 'VdomRealWorldTestApp';
|
17
|
+
Neo.apps = Neo.apps || {};
|
18
|
+
Neo.apps[appName] = {
|
19
|
+
name : appName,
|
20
|
+
fire : Neo.emptyFn,
|
21
|
+
isMounted: () => true,
|
22
|
+
rendering: false
|
23
|
+
};
|
24
|
+
|
25
|
+
class TestGrandchild extends Component {
|
26
|
+
static config = {
|
27
|
+
className: 'Test.Grandchild',
|
28
|
+
ntype : 'test-grandchild',
|
29
|
+
tag : 'span',
|
30
|
+
text : 'initial grandchild'
|
31
|
+
}
|
32
|
+
}
|
33
|
+
Neo.setupClass(TestGrandchild);
|
34
|
+
|
35
|
+
class TestChild extends Container {
|
36
|
+
static config = {
|
37
|
+
className: 'Test.Child',
|
38
|
+
ntype : 'test-child',
|
39
|
+
layout : {ntype: 'vbox'},
|
40
|
+
title_ : 'initial child'
|
41
|
+
}
|
42
|
+
|
43
|
+
afterSetTitle(value, oldValue) {
|
44
|
+
if (oldValue !== undefined) {
|
45
|
+
this.items[0].text = value;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
Neo.setupClass(TestChild);
|
50
|
+
|
51
|
+
class TestParent extends Container {
|
52
|
+
static config = {
|
53
|
+
className: 'Test.Parent',
|
54
|
+
ntype : 'test-parent',
|
55
|
+
layout : {ntype: 'vbox'},
|
56
|
+
heading_ : 'initial parent'
|
57
|
+
}
|
58
|
+
|
59
|
+
construct(config) {
|
60
|
+
super.construct(config);
|
61
|
+
let me = this;
|
62
|
+
me.headingComponent = me.insert(0, {
|
63
|
+
module: Component,
|
64
|
+
vdom : {
|
65
|
+
tag : 'h1',
|
66
|
+
id : me.id + '__heading',
|
67
|
+
text: me.heading
|
68
|
+
}
|
69
|
+
}, true);
|
70
|
+
}
|
71
|
+
|
72
|
+
afterSetHeading(value, oldValue) {
|
73
|
+
if (oldValue !== undefined) {
|
74
|
+
this.headingComponent.text = value;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
Neo.setupClass(TestParent);
|
79
|
+
|
80
|
+
// A container with its own nested item to test deep structural changes
|
81
|
+
class TestChildContainer extends Container {
|
82
|
+
static config = {
|
83
|
+
className: 'Test.ChildContainer',
|
84
|
+
ntype : 'test-child-container',
|
85
|
+
items : [
|
86
|
+
{ntype: 'test-grandchild', text: 'I am nested'}
|
87
|
+
]
|
88
|
+
}
|
89
|
+
}
|
90
|
+
Neo.setupClass(TestChildContainer);
|
91
|
+
|
92
|
+
|
93
|
+
StartTest(t => {
|
94
|
+
let parent, child, grandchild, testRun = 0;
|
95
|
+
|
96
|
+
t.beforeEach(async t => {
|
97
|
+
testRun++;
|
98
|
+
parent = Neo.create(TestParent, {
|
99
|
+
appName,
|
100
|
+
id : 'test-parent-' + testRun,
|
101
|
+
items: [
|
102
|
+
{
|
103
|
+
ntype: 'test-child',
|
104
|
+
id : 'test-child-' + testRun,
|
105
|
+
items: [{ntype: 'test-grandchild', id: 'test-grandchild-' + testRun}]
|
106
|
+
}
|
107
|
+
]
|
108
|
+
});
|
109
|
+
|
110
|
+
await parent.render();
|
111
|
+
child = parent.items[1]; // TestParent inserts a component at index 0
|
112
|
+
grandchild = child.items[0];
|
113
|
+
parent.mounted = true;
|
114
|
+
});
|
115
|
+
|
116
|
+
t.afterEach(t => {
|
117
|
+
parent.destroy();
|
118
|
+
parent = null;
|
119
|
+
child = null;
|
120
|
+
grandchild = null;
|
121
|
+
});
|
122
|
+
|
123
|
+
t.it('Should handle a simple parent-only update', async t => {
|
124
|
+
parent.setSilent({heading: 'Updated Parent'});
|
125
|
+
const {deltas} = await parent.promiseUpdate();
|
126
|
+
|
127
|
+
t.is(deltas.length, 1, 'Should generate exactly one delta');
|
128
|
+
const parentUpdate = deltas[0];
|
129
|
+
t.is(parentUpdate.id, parent.headingComponent.id, 'Delta should target the heading component');
|
130
|
+
t.is(parentUpdate.textContent, 'Updated Parent', 'Parent delta text is correct');
|
131
|
+
});
|
132
|
+
|
133
|
+
t.it('Should handle a simple child-only update', async t => {
|
134
|
+
// A small delay to ensure any in-flight updates from the initial
|
135
|
+
// render() and mount cycle have fully completed before this test runs.
|
136
|
+
await Promise.resolve();
|
137
|
+
|
138
|
+
grandchild.setSilent({text: 'Updated Grandchild'});
|
139
|
+
const {deltas} = await grandchild.promiseUpdate();
|
140
|
+
|
141
|
+
t.is(deltas.length, 1, 'Should generate exactly one delta');
|
142
|
+
const grandchildUpdate = deltas[0];
|
143
|
+
t.is(grandchildUpdate.id, grandchild.id, 'Delta should target the grandchild component');
|
144
|
+
t.is(grandchildUpdate.textContent, 'Updated Grandchild', 'Grandchild delta text is correct');
|
145
|
+
});
|
146
|
+
|
147
|
+
t.it('Should merge a parent update and a reactively-triggered child update', async t => {
|
148
|
+
parent.setSilent({heading: 'Updated Parent'});
|
149
|
+
child.setSilent({title: 'Updated Child Title'}); // This reactively updates the grandchild's text
|
150
|
+
const {deltas} = await parent.promiseUpdate();
|
151
|
+
|
152
|
+
t.is(deltas.length, 2, 'Should generate exactly two deltas');
|
153
|
+
|
154
|
+
const parentUpdate = deltas.find(d => d.id === parent.headingComponent.id);
|
155
|
+
const grandchildUpdate = deltas.find(d => d.id === grandchild.id);
|
156
|
+
|
157
|
+
t.ok(parentUpdate, 'Should have a delta for the parent heading component');
|
158
|
+
t.is(parentUpdate.textContent, 'Updated Parent', 'Parent delta text is correct');
|
159
|
+
t.ok(grandchildUpdate, 'Should have a delta for the grandchild component');
|
160
|
+
t.is(grandchildUpdate.textContent, 'Updated Child Title', 'Grandchild delta text is correct');
|
161
|
+
});
|
162
|
+
|
163
|
+
t.it('Should handle silent child update merged with parent update', async t => {
|
164
|
+
parent.setSilent({heading: 'Updated Parent'});
|
165
|
+
grandchild.setSilent({text: 'Silently Updated Grandchild'});
|
166
|
+
const {deltas} = await parent.promiseUpdate();
|
167
|
+
|
168
|
+
t.is(deltas.length, 2, 'Should generate exactly two deltas');
|
169
|
+
const parentUpdate = deltas.find(d => d.id === parent.headingComponent.id);
|
170
|
+
const grandchildUpdate = deltas.find(d => d.id === grandchild.id);
|
171
|
+
|
172
|
+
t.ok(parentUpdate, 'Should have a delta for the parent heading component');
|
173
|
+
t.is(parentUpdate.textContent, 'Updated Parent', 'Parent delta text is correct');
|
174
|
+
t.ok(grandchildUpdate, 'Should have a delta for the grandchild component');
|
175
|
+
t.is(grandchildUpdate.textContent, 'Silently Updated Grandchild', 'Grandchild delta text is correct');
|
176
|
+
});
|
177
|
+
|
178
|
+
t.it('Should handle structural change (add) in a silent child update', async t => {
|
179
|
+
parent.setSilent({heading: 'Updated Parent'});
|
180
|
+
const newGrandchild = child.insert(1, {ntype: 'test-grandchild', id: 'new-grandchild-' + testRun, text: 'New Grandchild'}, true);
|
181
|
+
const {deltas} = await parent.promiseUpdate();
|
182
|
+
|
183
|
+
t.is(deltas.length, 2, 'Should generate exactly two deltas');
|
184
|
+
const parentUpdate = deltas.find(d => d.id === parent.headingComponent.id);
|
185
|
+
const insertionDelta = deltas.find(d => d.action === 'insertNode');
|
186
|
+
|
187
|
+
t.ok(parentUpdate, 'Should have a delta for the parent heading component');
|
188
|
+
t.ok(insertionDelta, 'Should have a delta for the node insertion');
|
189
|
+
t.is(insertionDelta.parentId, child.getVdomItemsRoot().id, 'Insertion delta has correct parentId');
|
190
|
+
t.is(insertionDelta.vnode.id, newGrandchild.vdom.id, 'Inserted vnode has the correct ID');
|
191
|
+
t.is(insertionDelta.vnode.textContent, 'New Grandchild', 'Inserted vnode has correct text');
|
192
|
+
|
193
|
+
newGrandchild.destroy();
|
194
|
+
});
|
195
|
+
|
196
|
+
t.it('Should handle structural change (remove) in a silent child update', async t => {
|
197
|
+
parent.setSilent({heading: 'Updated Parent'});
|
198
|
+
child.removeAt(0, false, true);
|
199
|
+
const {deltas} = await parent.promiseUpdate();
|
200
|
+
|
201
|
+
t.is(deltas.length, 2, 'Should generate exactly two deltas');
|
202
|
+
const parentUpdate = deltas.find(d => d.id === parent.headingComponent.id);
|
203
|
+
const removalDelta = deltas.find(d => d.action === 'removeNode');
|
204
|
+
|
205
|
+
t.ok(parentUpdate, 'Should have a delta for the parent heading component');
|
206
|
+
t.ok(removalDelta, 'Should have a delta for the node removal');
|
207
|
+
t.is(removalDelta.id, grandchild.vdom.id, 'Removal delta has the correct ID');
|
208
|
+
});
|
209
|
+
|
210
|
+
t.it('Should handle silent insertion of a container with nested items', async t => {
|
211
|
+
parent.setSilent({heading: 'Updated Parent'});
|
212
|
+
const newContainer = child.insert(1, {ntype: 'test-child-container', id: 'new-container-' + testRun}, true);
|
213
|
+
const {deltas} = await parent.promiseUpdate();
|
214
|
+
|
215
|
+
t.is(deltas.length, 2, 'Should generate exactly two deltas');
|
216
|
+
const insertionDelta = deltas.find(d => d.action === 'insertNode');
|
217
|
+
|
218
|
+
t.ok(insertionDelta, 'Should have a delta for the node insertion');
|
219
|
+
t.is(insertionDelta.vnode.id, newContainer.vdom.id, 'Inserted vnode has the correct ID');
|
220
|
+
t.is(insertionDelta.vnode.childNodes.length, 1, 'Inserted vnode should have one child node');
|
221
|
+
|
222
|
+
const nestedChild = insertionDelta.vnode.childNodes[0];
|
223
|
+
t.is(nestedChild.id, newContainer.items[0].vdom.id, 'Nested child vnode has the correct ID');
|
224
|
+
t.is(nestedChild.textContent, 'I am nested', 'Nested child vnode has the correct text');
|
225
|
+
|
226
|
+
newContainer.destroy();
|
227
|
+
});
|
228
|
+
|
229
|
+
t.it('Should merge multiple property updates on a child into a single parent update cycle', async t => {
|
230
|
+
parent.setSilent({heading: 'Updated Parent'});
|
231
|
+
grandchild.setSilent({
|
232
|
+
cls : ['new-class'],
|
233
|
+
text: 'Updated Grandchild'
|
234
|
+
});
|
235
|
+
const {deltas} = await parent.promiseUpdate();
|
236
|
+
|
237
|
+
t.is(deltas.length, 2, 'Should generate exactly two deltas');
|
238
|
+
|
239
|
+
const parentUpdate = deltas.find(d => d.id === parent.headingComponent.id);
|
240
|
+
const grandchildUpdate = deltas.find(d => d.id === grandchild.id);
|
241
|
+
|
242
|
+
t.ok(parentUpdate, 'Should have a delta for the parent heading component');
|
243
|
+
t.is(parentUpdate.textContent, 'Updated Parent', 'Parent delta text is correct');
|
244
|
+
|
245
|
+
t.ok(grandchildUpdate, 'Should have a delta for the grandchild changes');
|
246
|
+
t.is(grandchildUpdate.textContent, 'Updated Grandchild', 'Grandchild text delta is correct');
|
247
|
+
t.isDeeplyStrict(grandchildUpdate.cls.add, ['new-class'], 'Grandchild class delta is correct');
|
248
|
+
});
|
249
|
+
});
|
@@ -1,6 +1,10 @@
|
|
1
|
-
import Neo
|
2
|
-
import * as core
|
3
|
-
import
|
1
|
+
import Neo from '../../../../../src/Neo.mjs';
|
2
|
+
import * as core from '../../../../../src/core/_export.mjs';
|
3
|
+
import StringFromVnode from '../../../../../src/vdom/util/StringFromVnode.mjs';
|
4
|
+
import VdomHelper from '../../../../../src/vdom/Helper.mjs';
|
5
|
+
|
6
|
+
// tests are designed for this rendering mode
|
7
|
+
Neo.config.useDomApiRenderer = false;
|
4
8
|
|
5
9
|
let oldVdom, oldVnode, vdom;
|
6
10
|
|
@@ -16,7 +20,7 @@ StartTest(t => {
|
|
16
20
|
{id: 'neo-component-6'}
|
17
21
|
]};
|
18
22
|
|
19
|
-
let oldVnode = VdomHelper.create(oldVdom);
|
23
|
+
let oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
|
20
24
|
|
21
25
|
vdom =
|
22
26
|
{id: 'neo-container-1', cn: [
|
@@ -67,7 +71,7 @@ StartTest(t => {
|
|
67
71
|
]}
|
68
72
|
]};
|
69
73
|
|
70
|
-
oldVnode = VdomHelper.create(oldVdom);
|
74
|
+
oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
|
71
75
|
|
72
76
|
vdom =
|
73
77
|
{id: 'neo-container-1', cn: [
|
@@ -105,7 +109,7 @@ StartTest(t => {
|
|
105
109
|
{id: 'neo-component-6'}
|
106
110
|
]};
|
107
111
|
|
108
|
-
oldVnode = VdomHelper.create(oldVdom);
|
112
|
+
oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
|
109
113
|
|
110
114
|
vdom =
|
111
115
|
{id: 'neo-container-1', cn: [
|
@@ -158,7 +162,7 @@ StartTest(t => {
|
|
158
162
|
]}
|
159
163
|
]};
|
160
164
|
|
161
|
-
oldVnode = VdomHelper.create(oldVdom);
|
165
|
+
oldVnode = VdomHelper.create({vdom: oldVdom}).vnode;
|
162
166
|
|
163
167
|
vdom =
|
164
168
|
{id: 'neo-container-1', cn: [
|