neo.mjs 10.0.0-beta.4 → 10.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.4.md +2 -2
  2. package/.github/RELEASE_NOTES/v10.0.0-beta.5.md +70 -0
  3. package/.github/RELEASE_NOTES/v10.0.0-beta.6.md +48 -0
  4. package/.github/epic-functional-components.md +498 -0
  5. package/.github/ticket-asymmetric-vdom-updates.md +122 -0
  6. package/README.md +0 -3
  7. package/ServiceWorker.mjs +2 -2
  8. package/apps/colors/store/Colors.mjs +1 -0
  9. package/apps/colors/view/GridContainer.mjs +3 -0
  10. package/apps/colors/view/HeaderToolbar.mjs +2 -0
  11. package/apps/colors/view/Viewport.mjs +3 -0
  12. package/apps/covid/view/FooterContainer.mjs +3 -0
  13. package/apps/covid/view/GalleryContainer.mjs +2 -0
  14. package/apps/covid/view/GalleryContainerController.mjs +1 -0
  15. package/apps/covid/view/HeaderContainer.mjs +2 -0
  16. package/apps/covid/view/HelixContainer.mjs +2 -0
  17. package/apps/covid/view/HelixContainerController.mjs +1 -0
  18. package/apps/covid/view/MainContainer.mjs +3 -0
  19. package/apps/covid/view/TableContainer.mjs +3 -0
  20. package/apps/covid/view/TableContainerController.mjs +1 -0
  21. package/apps/covid/view/WorldMapContainer.mjs +2 -0
  22. package/apps/covid/view/country/Gallery.mjs +3 -0
  23. package/apps/covid/view/country/Helix.mjs +8 -0
  24. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -0
  25. package/apps/covid/view/country/Table.mjs +2 -0
  26. package/apps/covid/view/mapboxGl/Component.mjs +1 -0
  27. package/apps/covid/view/mapboxGl/Container.mjs +2 -0
  28. package/apps/email/EPIC_PLAN.md +58 -0
  29. package/apps/email/neo-config.json +2 -2
  30. package/apps/email/store/Emails.mjs +11 -1
  31. package/apps/email/view/ComposeView.mjs +44 -0
  32. package/apps/email/view/MainView.mjs +89 -0
  33. package/apps/email/view/Viewport.mjs +4 -33
  34. package/apps/email/view/ViewportStateProvider.mjs +3 -3
  35. package/apps/form/store/SideNav.mjs +1 -0
  36. package/apps/form/view/FormContainer.mjs +1 -0
  37. package/apps/form/view/FormPageContainer.mjs +2 -0
  38. package/apps/form/view/SideNavList.mjs +1 -0
  39. package/apps/form/view/Viewport.mjs +3 -0
  40. package/apps/portal/childapps/preview/MainContainer.mjs +1 -0
  41. package/apps/portal/index.html +1 -1
  42. package/apps/portal/store/BlogPosts.mjs +2 -0
  43. package/apps/portal/store/Content.mjs +1 -0
  44. package/apps/portal/store/ContentSections.mjs +1 -0
  45. package/apps/portal/store/Examples.mjs +1 -0
  46. package/apps/portal/view/HeaderToolbar.mjs +1 -0
  47. package/apps/portal/view/Viewport.mjs +5 -0
  48. package/apps/portal/view/ViewportController.mjs +8 -2
  49. package/apps/portal/view/about/Container.mjs +2 -0
  50. package/apps/portal/view/about/MemberContainer.mjs +7 -0
  51. package/apps/portal/view/blog/Container.mjs +2 -0
  52. package/apps/portal/view/blog/List.mjs +2 -0
  53. package/apps/portal/view/examples/List.mjs +1 -0
  54. package/apps/portal/view/examples/TabContainer.mjs +4 -0
  55. package/apps/portal/view/home/ContentBox.mjs +3 -0
  56. package/apps/portal/view/home/FeatureSection.mjs +8 -0
  57. package/apps/portal/view/home/FooterContainer.mjs +4 -1
  58. package/apps/portal/view/home/MainContainer.mjs +2 -0
  59. package/apps/portal/view/home/parts/AfterMath.mjs +2 -0
  60. package/apps/portal/view/home/parts/BaseContainer.mjs +1 -0
  61. package/apps/portal/view/home/parts/Colors.mjs +4 -0
  62. package/apps/portal/view/home/parts/Features.mjs +2 -0
  63. package/apps/portal/view/home/parts/Helix.mjs +5 -0
  64. package/apps/portal/view/home/parts/How.mjs +4 -0
  65. package/apps/portal/view/home/parts/MainNeo.mjs +1 -0
  66. package/apps/portal/view/home/parts/References.mjs +2 -0
  67. package/apps/portal/view/learn/ContentComponent.mjs +11 -5
  68. package/apps/portal/view/learn/ContentTreeList.mjs +2 -0
  69. package/apps/portal/view/learn/CubeLayoutButton.mjs +1 -0
  70. package/apps/portal/view/learn/MainContainer.mjs +4 -0
  71. package/apps/portal/view/learn/PageContainer.mjs +2 -0
  72. package/apps/portal/view/learn/PageSectionsContainer.mjs +3 -0
  73. package/apps/portal/view/learn/PageSectionsList.mjs +1 -0
  74. package/apps/portal/view/services/Component.mjs +1 -0
  75. package/apps/realworld/api/Base.mjs +1 -0
  76. package/apps/realworld/view/HeaderComponent.mjs +4 -0
  77. package/apps/realworld/view/HomeComponent.mjs +7 -0
  78. package/apps/realworld/view/MainContainer.mjs +2 -0
  79. package/apps/realworld/view/MainContainerController.mjs +2 -0
  80. package/apps/realworld/view/article/CommentComponent.mjs +3 -0
  81. package/apps/realworld/view/article/Component.mjs +17 -10
  82. package/apps/realworld/view/article/CreateCommentComponent.mjs +2 -0
  83. package/apps/realworld/view/article/CreateComponent.mjs +5 -0
  84. package/apps/realworld/view/article/PreviewComponent.mjs +9 -0
  85. package/apps/realworld/view/article/TagListComponent.mjs +2 -0
  86. package/apps/realworld/view/user/ProfileComponent.mjs +7 -0
  87. package/apps/realworld/view/user/SettingsComponent.mjs +5 -0
  88. package/apps/realworld/view/user/SignUpComponent.mjs +3 -0
  89. package/apps/realworld2/api/Base.mjs +1 -0
  90. package/apps/realworld2/view/FooterComponent.mjs +1 -0
  91. package/apps/realworld2/view/HeaderToolbar.mjs +3 -0
  92. package/apps/realworld2/view/HomeContainer.mjs +1 -0
  93. package/apps/realworld2/view/MainContainer.mjs +2 -0
  94. package/apps/realworld2/view/MainContainerController.mjs +1 -0
  95. package/apps/realworld2/view/article/Helix.mjs +1 -0
  96. package/apps/realworld2/view/article/PreviewComponent.mjs +9 -0
  97. package/apps/realworld2/view/article/PreviewList.mjs +1 -0
  98. package/apps/realworld2/view/article/TagListComponent.mjs +2 -0
  99. package/apps/route/view/CenterContainer.mjs +1 -0
  100. package/apps/route/view/MainView.mjs +1 -0
  101. package/apps/sharedcovid/childapps/sharedcovidchart/MainContainer.mjs +1 -0
  102. package/apps/sharedcovid/childapps/sharedcovidgallery/MainContainer.mjs +1 -0
  103. package/apps/sharedcovid/childapps/sharedcovidhelix/MainContainer.mjs +1 -0
  104. package/apps/sharedcovid/childapps/sharedcovidmap/MainContainer.mjs +1 -0
  105. package/apps/sharedcovid/view/FooterContainer.mjs +3 -0
  106. package/apps/sharedcovid/view/GalleryContainer.mjs +2 -0
  107. package/apps/sharedcovid/view/GalleryContainerController.mjs +1 -0
  108. package/apps/sharedcovid/view/HeaderContainer.mjs +2 -0
  109. package/apps/sharedcovid/view/HelixContainer.mjs +2 -0
  110. package/apps/sharedcovid/view/HelixContainerController.mjs +1 -0
  111. package/apps/sharedcovid/view/MainContainer.mjs +3 -0
  112. package/apps/sharedcovid/view/TableContainer.mjs +3 -0
  113. package/apps/sharedcovid/view/TableContainerController.mjs +1 -0
  114. package/apps/sharedcovid/view/WorldMapContainer.mjs +2 -0
  115. package/apps/sharedcovid/view/country/Gallery.mjs +3 -0
  116. package/apps/sharedcovid/view/country/Helix.mjs +8 -0
  117. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -0
  118. package/apps/sharedcovid/view/country/Table.mjs +2 -0
  119. package/apps/sharedcovid/view/mapboxGl/Component.mjs +1 -0
  120. package/apps/sharedcovid/view/mapboxGl/Container.mjs +2 -0
  121. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +2 -0
  122. package/apps/shareddialog/view/DemoDialog.mjs +2 -0
  123. package/apps/shareddialog/view/MainContainer.mjs +2 -0
  124. package/apps/shareddialog/view/MainContainerController.mjs +1 -0
  125. package/buildScripts/addReactiveTags.mjs +191 -0
  126. package/buildScripts/checkReactiveTags.mjs +160 -0
  127. package/docs/app/store/Api.mjs +1 -0
  128. package/docs/app/store/Examples.mjs +1 -0
  129. package/docs/app/view/ApiTreeList.mjs +1 -0
  130. package/docs/app/view/ContentTabContainer.mjs +2 -0
  131. package/docs/app/view/ExamplesTreeList.mjs +2 -0
  132. package/docs/app/view/HeaderContainer.mjs +3 -0
  133. package/docs/app/view/MainContainer.mjs +5 -0
  134. package/docs/app/view/classdetails/HeaderComponent.mjs +1 -0
  135. package/docs/app/view/classdetails/MainContainer.mjs +3 -0
  136. package/docs/app/view/classdetails/MembersList.mjs +5 -0
  137. package/docs/app/view/classdetails/SourceViewComponent.mjs +2 -0
  138. package/examples/ConfigurationViewport.mjs +14 -8
  139. package/examples/button/effect/MainContainer.mjs +207 -0
  140. package/examples/button/effect/app.mjs +6 -0
  141. package/examples/button/effect/index.html +11 -0
  142. package/examples/button/effect/neo-config.json +6 -0
  143. package/examples/calendar/weekview/MainContainer.mjs +4 -0
  144. package/examples/component/coronaGallery/CountryGallery.mjs +2 -0
  145. package/examples/component/coronaGallery/CountryStore.mjs +1 -0
  146. package/examples/component/coronaGallery/Viewport.mjs +3 -0
  147. package/examples/component/coronaGallery/ViewportController.mjs +1 -0
  148. package/examples/component/coronaHelix/CountryHelix.mjs +7 -0
  149. package/examples/component/coronaHelix/MainContainer.mjs +1 -0
  150. package/examples/component/gallery/ImageStore.mjs +1 -0
  151. package/examples/component/helix/ImageStore.mjs +1 -0
  152. package/examples/component/helix/Viewport.mjs +3 -0
  153. package/examples/component/helix/ViewportController.mjs +1 -0
  154. package/examples/component/multiWindowCoronaGallery/childapp/Viewport.mjs +1 -0
  155. package/examples/component/multiWindowHelix/childapp/Viewport.mjs +1 -0
  156. package/examples/component/wrapper/googleMaps/MapComponent.mjs +2 -0
  157. package/examples/core/config/MainContainer.mjs +2 -0
  158. package/examples/dialog/DemoDialog.mjs +2 -0
  159. package/examples/dialog/MainContainer.mjs +1 -0
  160. package/examples/form/field/color/MainStore.mjs +1 -0
  161. package/examples/functional/defineComponent/Component.mjs +18 -0
  162. package/examples/functional/defineComponent/MainContainer.mjs +41 -0
  163. package/examples/functional/defineComponent/app.mjs +6 -0
  164. package/examples/functional/defineComponent/index.html +11 -0
  165. package/examples/functional/defineComponent/neo-config.json +6 -0
  166. package/examples/functional/hostComponent/Component.mjs +32 -0
  167. package/examples/functional/hostComponent/MainContainer.mjs +48 -0
  168. package/examples/functional/hostComponent/app.mjs +6 -0
  169. package/examples/functional/hostComponent/index.html +11 -0
  170. package/examples/functional/hostComponent/neo-config.json +6 -0
  171. package/examples/grid/animatedRowSorting/Viewport.mjs +1 -1
  172. package/examples/grid/bigData/ControlsContainer.mjs +3 -0
  173. package/examples/grid/bigData/GridContainer.mjs +4 -2
  174. package/examples/grid/bigData/MainContainer.mjs +2 -0
  175. package/examples/grid/bigData/MainModel.mjs +1 -0
  176. package/examples/grid/bigData/MainStore.mjs +3 -0
  177. package/examples/grid/cellEditing/MainContainer.mjs +1 -1
  178. package/examples/grid/container/MainContainer.mjs +1 -1
  179. package/examples/grid/covid/GridContainer.mjs +3 -0
  180. package/examples/grid/covid/MainContainer.mjs +2 -0
  181. package/examples/grid/covid/Store.mjs +1 -0
  182. package/examples/grid/nestedRecordFields/EditUserDialog.mjs +3 -0
  183. package/examples/grid/nestedRecordFields/Viewport.mjs +3 -1
  184. package/examples/list/animate/List.mjs +4 -0
  185. package/examples/list/animate/MainContainer.mjs +2 -0
  186. package/examples/list/circle/MainStore.mjs +1 -0
  187. package/examples/list/color/MainStore.mjs +1 -0
  188. package/examples/preloadingAssets/view/MainContainer.mjs +2 -0
  189. package/examples/stateProvider/advanced/MainContainer.mjs +1 -0
  190. package/examples/stateProvider/dialog/EditUserDialog.mjs +2 -0
  191. package/examples/stateProvider/dialog/MainContainer.mjs +1 -0
  192. package/examples/stateProvider/extendedClass/MainContainer.mjs +2 -0
  193. package/examples/stateProvider/inline/MainContainer.mjs +1 -0
  194. package/examples/stateProvider/inlineNoStateProvider/MainContainer.mjs +1 -0
  195. package/examples/stateProvider/inlineNoStateProvider/MainContainerController.mjs +2 -0
  196. package/examples/stateProvider/multiWindow/EditUserDialog.mjs +3 -0
  197. package/examples/stateProvider/multiWindow/MainContainer.mjs +1 -0
  198. package/examples/stateProvider/multiWindow/Viewport.mjs +1 -0
  199. package/examples/stateProvider/nestedData/MainContainer.mjs +1 -0
  200. package/examples/stateProvider/table/MainContainer.mjs +1 -0
  201. package/examples/table/covid/MainContainer.mjs +2 -0
  202. package/examples/table/covid/Store.mjs +1 -0
  203. package/examples/table/covid/TableContainer.mjs +3 -0
  204. package/examples/table/nestedRecordFields/EditUserDialog.mjs +3 -0
  205. package/examples/table/nestedRecordFields/Viewport.mjs +1 -0
  206. package/examples/todoList/version1/MainComponent.mjs +1 -1
  207. package/examples/toolbar/breadcrumb/view/MainContainer.mjs +2 -0
  208. package/examples/toolbar/paging/store/Users.mjs +1 -0
  209. package/examples/toolbar/paging/view/AddUserDialog.mjs +3 -0
  210. package/examples/toolbar/paging/view/MainContainer.mjs +3 -0
  211. package/examples/treeAccordion/MainContainer.mjs +2 -2
  212. package/examples/worker/task/MainContainer.mjs +1 -0
  213. package/learn/Glossary.md +1 -0
  214. package/learn/UsingTheseTopics.md +1 -0
  215. package/learn/benefits/ConfigSystem.md +2 -0
  216. package/learn/benefits/Effort.md +1 -0
  217. package/learn/benefits/Features.md +1 -0
  218. package/learn/benefits/FormsEngine.md +1 -0
  219. package/learn/benefits/FourEnvironments.md +2 -0
  220. package/learn/benefits/Introduction.md +2 -0
  221. package/learn/benefits/MultiWindow.md +3 -1
  222. package/learn/benefits/OffTheMainThread.md +2 -0
  223. package/learn/benefits/Quick.md +2 -0
  224. package/learn/benefits/RPCLayer.md +2 -0
  225. package/learn/benefits/Speed.md +2 -0
  226. package/learn/comparisons/NeoVsAngular.md +90 -0
  227. package/learn/comparisons/NeoVsExtJs.md +178 -0
  228. package/learn/comparisons/NeoVsNextJs.md +124 -0
  229. package/learn/comparisons/NeoVsReact.md +95 -0
  230. package/learn/comparisons/NeoVsSolid.md +78 -0
  231. package/learn/comparisons/NeoVsVue.md +92 -0
  232. package/learn/comparisons/Overview.md +46 -0
  233. package/learn/gettingstarted/ComponentModels.md +2 -0
  234. package/learn/gettingstarted/Config.md +2 -0
  235. package/learn/gettingstarted/DescribingTheUI.md +2 -0
  236. package/learn/gettingstarted/Events.md +2 -0
  237. package/learn/gettingstarted/Extending.md +2 -0
  238. package/learn/gettingstarted/References.md +2 -0
  239. package/learn/gettingstarted/Setup.md +3 -2
  240. package/learn/gettingstarted/Workspaces.md +2 -0
  241. package/learn/guides/datahandling/Collections.md +1 -0
  242. package/learn/guides/datahandling/Records.md +1 -0
  243. package/learn/guides/datahandling/StateProviders.md +131 -16
  244. package/learn/guides/datahandling/Tables.md +1 -1
  245. package/learn/guides/fundamentals/ConfigSystemDeepDive.md +1 -0
  246. package/learn/guides/fundamentals/DeclarativeComponentTreesVsImperativeVdom.md +2 -0
  247. package/learn/guides/fundamentals/DeclarativeVDOMWithEffects.md +168 -0
  248. package/learn/guides/fundamentals/ExtendingNeoClasses.md +1 -0
  249. package/learn/guides/fundamentals/InstanceLifecycle.md +3 -1
  250. package/learn/guides/fundamentals/MainThreadAddons.md +2 -0
  251. package/learn/guides/specificfeatures/Mixins.md +3 -1
  252. package/learn/guides/specificfeatures/MultiWindow.md +3 -1
  253. package/learn/guides/specificfeatures/PortalApp.md +2 -0
  254. package/learn/guides/uibuildingblocks/ComponentsAndContainers.md +2 -0
  255. package/learn/guides/uibuildingblocks/CustomComponents.md +2 -0
  256. package/learn/guides/uibuildingblocks/Layouts.md +2 -0
  257. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +2 -0
  258. package/learn/guides/userinteraction/Forms.md +2 -0
  259. package/learn/guides/userinteraction/events/CustomEvents.md +2 -1
  260. package/learn/guides/userinteraction/events/DomEvents.md +2 -0
  261. package/learn/javascript/ClassFeatures.md +4 -3
  262. package/learn/javascript/Classes.md +10 -13
  263. package/learn/javascript/Overrides.md +10 -6
  264. package/learn/javascript/Super.md +12 -8
  265. package/learn/tree.json +71 -63
  266. package/learn/tutorials/Earthquakes.md +2 -0
  267. package/learn/tutorials/RSP.md +3 -1
  268. package/learn/tutorials/TodoList.md +103 -7
  269. package/package.json +6 -4
  270. package/resources/scss/src/apps/email/ComposeView.scss +16 -0
  271. package/resources/scss/src/apps/email/MainView.scss +5 -0
  272. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +5 -4
  273. package/src/DefaultConfig.mjs +12 -2
  274. package/src/Main.mjs +1 -0
  275. package/src/Neo.mjs +377 -178
  276. package/src/Xhr.mjs +1 -0
  277. package/src/button/Base.mjs +13 -0
  278. package/src/button/Effect.mjs +449 -0
  279. package/src/button/Split.mjs +2 -0
  280. package/src/calendar/store/Calendars.mjs +1 -0
  281. package/src/calendar/store/Colors.mjs +1 -0
  282. package/src/calendar/store/Events.mjs +1 -0
  283. package/src/calendar/view/DayComponent.mjs +2 -0
  284. package/src/calendar/view/EditEventContainer.mjs +4 -1
  285. package/src/calendar/view/MainContainer.mjs +13 -0
  286. package/src/calendar/view/MainContainerStateProvider.mjs +14 -28
  287. package/src/calendar/view/SettingsContainer.mjs +1 -0
  288. package/src/calendar/view/YearComponent.mjs +16 -0
  289. package/src/calendar/view/calendars/ColorsList.mjs +2 -0
  290. package/src/calendar/view/calendars/Container.mjs +2 -0
  291. package/src/calendar/view/calendars/EditContainer.mjs +1 -0
  292. package/src/calendar/view/month/Component.mjs +11 -0
  293. package/src/calendar/view/settings/GeneralContainer.mjs +1 -0
  294. package/src/calendar/view/settings/MonthContainer.mjs +1 -0
  295. package/src/calendar/view/settings/WeekContainer.mjs +1 -0
  296. package/src/calendar/view/settings/YearContainer.mjs +1 -0
  297. package/src/calendar/view/week/Component.mjs +15 -1
  298. package/src/calendar/view/week/TimeAxisComponent.mjs +4 -0
  299. package/src/code/LivePreview.mjs +51 -23
  300. package/src/collection/Base.mjs +14 -12
  301. package/src/collection/Filter.mjs +6 -0
  302. package/src/collection/Sorter.mjs +3 -0
  303. package/src/component/Base.mjs +156 -802
  304. package/src/component/Canvas.mjs +1 -0
  305. package/src/component/Chip.mjs +4 -0
  306. package/src/component/Circle.mjs +14 -0
  307. package/src/component/Clock.mjs +4 -0
  308. package/src/component/DateSelector.mjs +12 -0
  309. package/src/component/Gallery.mjs +11 -0
  310. package/src/component/Helix.mjs +24 -0
  311. package/src/component/Label.mjs +1 -0
  312. package/src/component/Legend.mjs +3 -0
  313. package/src/component/MagicMoveText.mjs +4 -0
  314. package/src/component/Progress.mjs +3 -0
  315. package/src/component/Splitter.mjs +3 -0
  316. package/src/component/StatusBadge.mjs +6 -0
  317. package/src/component/Timer.mjs +4 -0
  318. package/src/component/Toast.mjs +6 -0
  319. package/src/component/Video.mjs +1 -0
  320. package/src/component/mwc/Button.mjs +7 -0
  321. package/src/component/mwc/TextField.mjs +9 -0
  322. package/src/component/wrapper/AmChart.mjs +2 -0
  323. package/src/component/wrapper/GoogleMaps.mjs +3 -0
  324. package/src/component/wrapper/MapboxGL.mjs +5 -0
  325. package/src/component/wrapper/MonacoEditor.mjs +12 -0
  326. package/src/container/Accordion.mjs +2 -0
  327. package/src/container/Base.mjs +34 -26
  328. package/src/container/Panel.mjs +1 -0
  329. package/src/container/Viewport.mjs +1 -0
  330. package/src/controller/Application.mjs +1 -0
  331. package/src/controller/Base.mjs +1 -0
  332. package/src/controller/Component.mjs +1 -0
  333. package/src/core/Base.mjs +193 -22
  334. package/src/core/Compare.mjs +4 -7
  335. package/src/core/Config.mjs +137 -33
  336. package/src/core/Effect.mjs +193 -0
  337. package/src/core/EffectBatchManager.mjs +67 -0
  338. package/src/core/EffectManager.mjs +60 -0
  339. package/src/core/IdGenerator.mjs +13 -44
  340. package/src/data/Model.mjs +2 -0
  341. package/src/data/Store.mjs +7 -0
  342. package/src/data/connection/WebSocket.mjs +2 -0
  343. package/src/date/DayViewComponent.mjs +2 -0
  344. package/src/date/SelectorContainer.mjs +14 -0
  345. package/src/dialog/Base.mjs +8 -0
  346. package/src/draggable/DragZone.mjs +5 -0
  347. package/src/draggable/tree/DragZone.mjs +1 -0
  348. package/src/filter/BooleanContainer.mjs +2 -0
  349. package/src/filter/NumberContainer.mjs +3 -0
  350. package/src/filter/ToggleOperatorsButton.mjs +2 -0
  351. package/src/form/Fieldset.mjs +6 -0
  352. package/src/form/field/Base.mjs +7 -0
  353. package/src/form/field/CheckBox.mjs +18 -0
  354. package/src/form/field/Chip.mjs +1 -0
  355. package/src/form/field/ComboBox.mjs +8 -0
  356. package/src/form/field/Country.mjs +1 -0
  357. package/src/form/field/Currency.mjs +2 -0
  358. package/src/form/field/Date.mjs +4 -0
  359. package/src/form/field/Display.mjs +1 -0
  360. package/src/form/field/Email.mjs +1 -0
  361. package/src/form/field/FileUpload.mjs +7 -0
  362. package/src/form/field/Hidden.mjs +1 -0
  363. package/src/form/field/Number.mjs +7 -0
  364. package/src/form/field/Password.mjs +1 -0
  365. package/src/form/field/Phone.mjs +3 -0
  366. package/src/form/field/Picker.mjs +2 -0
  367. package/src/form/field/Radio.mjs +1 -0
  368. package/src/form/field/Range.mjs +3 -0
  369. package/src/form/field/Search.mjs +2 -0
  370. package/src/form/field/Text.mjs +32 -0
  371. package/src/form/field/TextArea.mjs +7 -0
  372. package/src/form/field/Time.mjs +6 -0
  373. package/src/form/field/Url.mjs +3 -0
  374. package/src/form/field/ZipCode.mjs +2 -0
  375. package/src/form/field/trigger/Base.mjs +3 -0
  376. package/src/form/field/trigger/Clear.mjs +2 -0
  377. package/src/form/field/trigger/CopyToClipboard.mjs +2 -0
  378. package/src/form/field/trigger/Date.mjs +1 -0
  379. package/src/form/field/trigger/Picker.mjs +1 -0
  380. package/src/form/field/trigger/Search.mjs +1 -0
  381. package/src/form/field/trigger/SpinDown.mjs +2 -0
  382. package/src/form/field/trigger/SpinUp.mjs +1 -0
  383. package/src/form/field/trigger/Time.mjs +2 -0
  384. package/src/functional/_export.mjs +6 -0
  385. package/src/functional/component/Base.mjs +499 -0
  386. package/src/functional/defineComponent.mjs +102 -0
  387. package/src/functional/useConfig.mjs +52 -0
  388. package/src/functional/useEvent.mjs +43 -0
  389. package/src/grid/Body.mjs +20 -1
  390. package/src/grid/Container.mjs +57 -63
  391. package/src/grid/ScrollManager.mjs +2 -0
  392. package/src/grid/VerticalScrollbar.mjs +2 -0
  393. package/src/grid/column/Base.mjs +2 -0
  394. package/src/grid/column/Component.mjs +1 -1
  395. package/src/grid/header/Button.mjs +7 -0
  396. package/src/grid/header/Toolbar.mjs +6 -0
  397. package/src/grid/plugin/AnimateRows.mjs +2 -0
  398. package/src/layout/Base.mjs +3 -0
  399. package/src/layout/Card.mjs +1 -0
  400. package/src/layout/Cube.mjs +11 -1
  401. package/src/layout/Fit.mjs +1 -0
  402. package/src/layout/Flexbox.mjs +7 -0
  403. package/src/layout/Form.mjs +2 -0
  404. package/src/layout/Grid.mjs +1 -0
  405. package/src/layout/HBox.mjs +1 -0
  406. package/src/layout/VBox.mjs +1 -0
  407. package/src/list/Base.mjs +13 -0
  408. package/src/list/Chip.mjs +1 -0
  409. package/src/list/Circle.mjs +2 -0
  410. package/src/list/Color.mjs +1 -0
  411. package/src/list/plugin/Animate.mjs +2 -0
  412. package/src/main/DeltaUpdates.mjs +1 -0
  413. package/src/main/DomEvents.mjs +2 -0
  414. package/src/main/addon/CloneNode.mjs +1 -0
  415. package/src/main/addon/Cookie.mjs +1 -0
  416. package/src/main/addon/GoogleMaps.mjs +1 -0
  417. package/src/main/addon/LocalStorage.mjs +1 -0
  418. package/src/main/addon/MapboxGL.mjs +1 -0
  419. package/src/main/addon/Markdown.mjs +1 -0
  420. package/src/main/addon/Navigator.mjs +1 -0
  421. package/src/main/addon/Popover.mjs +1 -0
  422. package/src/main/addon/Stylesheet.mjs +1 -0
  423. package/src/main/addon/WindowPosition.mjs +1 -0
  424. package/src/manager/Component.mjs +0 -71
  425. package/src/manager/VDomUpdate.mjs +235 -0
  426. package/src/menu/List.mjs +6 -0
  427. package/src/menu/Model.mjs +1 -0
  428. package/src/menu/Panel.mjs +3 -0
  429. package/src/menu/Store.mjs +1 -0
  430. package/src/mixin/DomEvents.mjs +130 -0
  431. package/src/mixin/VdomLifecycle.mjs +667 -0
  432. package/src/plugin/Base.mjs +1 -0
  433. package/src/plugin/Resizable.mjs +2 -0
  434. package/src/selection/Model.mjs +15 -18
  435. package/src/selection/grid/BaseModel.mjs +1 -0
  436. package/src/sitemap/Component.mjs +1 -0
  437. package/src/state/Provider.mjs +376 -457
  438. package/src/state/createHierarchicalDataProxy.mjs +138 -0
  439. package/src/tab/Container.mjs +6 -0
  440. package/src/tab/Strip.mjs +1 -0
  441. package/src/tab/header/Button.mjs +2 -0
  442. package/src/tab/header/EffectButton.mjs +77 -0
  443. package/src/tab/header/Toolbar.mjs +1 -0
  444. package/src/table/Body.mjs +3 -0
  445. package/src/table/Container.mjs +10 -0
  446. package/src/table/header/Button.mjs +8 -0
  447. package/src/table/header/Toolbar.mjs +5 -0
  448. package/src/table/plugin/CellEditing.mjs +1 -0
  449. package/src/toolbar/Base.mjs +4 -0
  450. package/src/toolbar/Breadcrumb.mjs +3 -0
  451. package/src/toolbar/Paging.mjs +5 -0
  452. package/src/tooltip/Base.mjs +2 -0
  453. package/src/tree/List.mjs +3 -0
  454. package/src/util/HashHistory.mjs +1 -0
  455. package/src/util/KeyNavigation.mjs +2 -0
  456. package/src/util/Matrix.mjs +1 -0
  457. package/src/util/VDom.mjs +7 -1
  458. package/src/util/VNode.mjs +7 -1
  459. package/src/util/vdom/TreeBuilder.mjs +129 -0
  460. package/src/vdom/Helper.mjs +44 -33
  461. package/src/vdom/VNode.mjs +5 -7
  462. package/src/worker/App.mjs +1 -5
  463. package/src/worker/Base.mjs +2 -0
  464. package/src/worker/Manager.mjs +2 -0
  465. package/src/worker/ServiceBase.mjs +6 -1
  466. package/test/siesta/siesta.js +36 -1
  467. package/test/siesta/tests/CollectionBase.mjs +10 -10
  468. package/test/siesta/tests/VdomCalendar.mjs +13 -9
  469. package/test/siesta/tests/VdomHelper.mjs +22 -59
  470. package/test/siesta/tests/config/AfterSetConfig.mjs +100 -0
  471. package/test/siesta/tests/{ReactiveConfigs.mjs → config/Basic.mjs} +58 -21
  472. package/test/siesta/tests/config/CircularDependencies.mjs +166 -0
  473. package/test/siesta/tests/config/CustomFunctions.mjs +69 -0
  474. package/test/siesta/tests/config/Hierarchy.mjs +94 -0
  475. package/test/siesta/tests/config/MemoryLeak.mjs +92 -0
  476. package/test/siesta/tests/config/MultiLevelHierarchy.mjs +85 -0
  477. package/test/siesta/tests/core/Effect.mjs +127 -0
  478. package/test/siesta/tests/core/EffectBatching.mjs +310 -0
  479. package/test/siesta/tests/neo/MixinStaticConfig.mjs +138 -0
  480. package/test/siesta/tests/state/Provider.mjs +537 -0
  481. package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +255 -0
  482. package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +204 -0
  483. package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +366 -0
  484. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +249 -0
  485. package/learn/javascript/NewNode.md +0 -31
@@ -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 TreeBuilder from '../../../../src/util/vdom/TreeBuilder.mjs';
5
+ import VDomUpdate from '../../../../src/manager/VDomUpdate.mjs';
6
+ import VdomLifecycle from '../../../../src/mixin/VdomLifecycle.mjs';
7
+ import VdomHelper from '../../../../src/vdom/Helper.mjs';
8
+ import VDomUtil from '../../../../src/util/VDom.mjs';
9
+
10
+ // IMPORTANT: Test with the new standard renderer
11
+ Neo.config.useDomApiRenderer = true;
12
+ VdomHelper.onNeoConfigChange({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 VdomHelper from '../../../../src/vdom/Helper.mjs';
6
+
7
+ // IMPORTANT: This test file uses real components and expects them to render.
8
+ // We need to enable unitTestMode for isolation, but also allow VDOM updates.
9
+ Neo.config.unitTestMode = true;
10
+ Neo.config.allowVdomUpdatesInTests = true;
11
+ // This ensures that the VdomHelper uses the correct renderer for the assertions.
12
+ Neo.config.useDomApiRenderer = true;
13
+ VdomHelper.onNeoConfigChange({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,31 +0,0 @@
1
- ```javascript readonly
2
- import Base from '../../../node_modules/neo.mjs/src/core/Base.mjs';
3
-
4
- class Mammal extends Base {
5
- static config = {
6
- className: 'Simple.example.Mammal'
7
- }
8
- }
9
-
10
- const myMammal = Neo.create(Mammal);
11
-
12
- Mammal = Neo.setupClass(Mammal); // Where Neo.mjs initializes the class config.
13
- export default Mammal; // Makes the class available elsewhere.
14
- ```
15
-
16
-
17
-
18
- ```javascript live-preview
19
- import Base from '../../../node_modules/neo.mjs/src/core/Base.mjs';
20
-
21
- class Mammal extends Base {
22
- static config = {
23
- className: 'Simple.example.Mammal'
24
- }
25
- }
26
-
27
- const myMammal = Neo.create(Mammal);
28
-
29
- Mammal = Neo.setupClass(Mammal); // Where Neo.mjs initializes the class config.
30
- export default Mammal; // Makes the class available elsewhere.
31
- ```