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
@@ -1,16 +1,19 @@
1
- import Base from '../core/Base.mjs';
2
- import ClassSystemUtil from '../util/ClassSystem.mjs';
3
- import NeoArray from '../util/Array.mjs';
4
- import Observable from '../core/Observable.mjs';
1
+ import Base from '../core/Base.mjs';
2
+ import ClassSystemUtil from '../util/ClassSystem.mjs';
3
+ import Config from '../core/Config.mjs';
4
+ import Effect from '../core/Effect.mjs';
5
+ import EffectBatchManager from '../core/EffectBatchManager.mjs';
6
+ import Observable from '../core/Observable.mjs';
7
+ import {createHierarchicalDataProxy} from './createHierarchicalDataProxy.mjs';
8
+ import {isDescriptor} from '../core/ConfigSymbols.mjs';
5
9
 
6
- const dataVariableRegex = /data((?!(\.[a-z_]\w*\(\)))\.[a-z_]\w*)+/gi,
7
- twoWayBindingSymbol = Symbol.for('twoWayBinding'),
8
- variableNameRegex = /^\w*/;
10
+ const twoWayBindingSymbol = Symbol.for('twoWayBinding');
9
11
 
10
12
  /**
11
13
  * An optional component state provider for adding bindings to configs
12
14
  * @class Neo.state.Provider
13
15
  * @extends Neo.core.Base
16
+ * @mixes Neo.core.Observable
14
17
  */
15
18
  class Provider extends Base {
16
19
  /**
@@ -31,100 +34,154 @@ class Provider extends Base {
31
34
  * @protected
32
35
  */
33
36
  ntype: 'state-provider',
34
- /**
35
- * @member {Object|null} bindings_=null
36
- * @protected
37
- */
38
- bindings_: null,
39
37
  /**
40
38
  * @member {Neo.component.Base|null} component=null
41
39
  * @protected
42
40
  */
43
41
  component: null,
44
42
  /**
43
+ /**
44
+ * The core data object managed by this StateProvider.
45
+ * This object holds the reactive state that can be accessed and modified
46
+ * by components and formulas within the provider's hierarchy.
47
+ * Changes to properties within this data object will trigger reactivity.
48
+ * When new data is assigned, it will be deeply merged with existing data.
45
49
  * @member {Object|null} data_=null
50
+ * @example
51
+ * data: {
52
+ * user: {
53
+ * firstName: 'John',
54
+ * lastName : 'Doe'
55
+ * },
56
+ * settings: {
57
+ * theme: 'dark'
58
+ * }
59
+ * }
60
+ * @reactive
46
61
  */
47
- data_: null,
62
+ data_: {
63
+ [isDescriptor]: true,
64
+ merge : 'deep',
65
+ value : {}
66
+ },
48
67
  /**
68
+ * Defines computed properties based on other data properties within the StateProvider hierarchy.
69
+ * Each formula is a function that receives a `data` argument, which is a hierarchical proxy
70
+ * allowing access to data from the current provider and all its parent providers.
71
+ * Changes to dependencies (accessed via `data.propertyName`) will automatically re-run the formula.
49
72
  * @member {Object|null} formulas_=null
50
- *
51
73
  * @example
52
74
  * data: {
53
- * a: 1,
54
- * b: 2
75
+ * a : 1,
76
+ * b : 2,
77
+ * total: 50
55
78
  * }
56
79
  * formulas: {
57
- * aPlusB: {
58
- * bind: {
59
- * foo: 'a',
60
- * bar: 'b'
61
- * },
62
- * get(data) {
63
- * return data.foo + data.bar
64
- * }
65
- * }
80
+ * aPlusB : (data) => data.a + data.b,
81
+ * aTimesB: (data) => data.a * data.b,
82
+ * // Accessing parent data (assuming a parent provider has a 'taxRate' property)
83
+ * totalWithTax: (data) => data.total * (1 + data.taxRate)
66
84
  * }
85
+ * @reactive
67
86
  */
68
87
  formulas_: null,
69
88
  /**
70
89
  * @member {Neo.state.Provider|null} parent_=null
90
+ * @reactive
71
91
  */
72
92
  parent_: null,
73
93
  /**
94
+ /**
95
+ * A collection of Neo.data.Store instances managed by this StateProvider.
96
+ * Stores are defined as config objects with a `module` property pointing
97
+ * to the store class, which will then be instantiated by the framework.
74
98
  * @member {Object|null} stores_=null
99
+ * @example
100
+ * stores: {
101
+ * myUsers: {
102
+ * module: Neo.data.Store,
103
+ * model : 'MyApp.model.User',
104
+ * data : [{id: 1, name: 'John'}, {id: 2, name: 'Doe'}]
105
+ * },
106
+ * myCustomStore1: MyCustomStoreClass,
107
+ * myCustomStore2: {
108
+ * module : MyCustomStoreClass,
109
+ * autoLoad: true
110
+ * }
111
+ * }
112
+ * @reactive
75
113
  */
76
114
  stores_: null
77
115
  }
78
116
 
79
117
  /**
80
- * @param {Object} config
118
+ * @member {Map} #bindingEffects=new Map()
119
+ * @private
81
120
  */
82
- construct(config) {
83
- Neo.currentWorker.isUsingStateProviders = true;
84
- super.construct(config);
85
- this.bindings = {}
86
- }
87
-
121
+ #bindingEffects = new Map()
88
122
  /**
89
- * Adds a given key/value combination on this stateProvider level.
90
- * The method is used by setData() & setDataAtSameLevel()
91
- * in case the data property does not exist yet.
92
- * @param {String} key
93
- * @param {*} value
123
+ * @member {Object} #dataConfigs={}
94
124
  * @private
95
125
  */
96
- addDataProperty(key, value) {
97
- let me = this,
98
- data, scope;
99
-
100
- Neo.ns(key, true, me.data);
101
-
102
- data = me.getDataScope(key);
103
- scope = data.scope;
104
-
105
- scope[data.key] = value;
126
+ #dataConfigs = {}
127
+ /**
128
+ * @member {Map} #formulaEffects=new Map()
129
+ * @private
130
+ */
131
+ #formulaEffects = new Map()
106
132
 
107
- me.createDataProperties(me.data, 'data')
133
+ /**
134
+ * @param {Object} config
135
+ */
136
+ construct(config) {
137
+ Neo.isUsingStateProviders = true;
138
+ super.construct(config)
108
139
  }
109
140
 
110
141
  /**
111
- * Triggered after the data config got changed
142
+ * Triggered after the data config got changed.
143
+ * This method initializes the internal #dataConfigs map, converting each
144
+ * plain data property into a reactive Neo.core.Config instance.
112
145
  * @param {Object|null} value
113
146
  * @param {Object|null} oldValue
114
147
  * @protected
115
148
  */
116
149
  afterSetData(value, oldValue) {
117
- value && this.createDataProperties(value, 'data')
150
+ value && this.processDataObject(value)
118
151
  }
119
152
 
120
153
  /**
121
- * Triggered after the formulas config got changed
122
- * @param {Object|null} value
123
- * @param {Object|null} oldValue
154
+ * Triggered after the formulas config got changed.
155
+ * This method sets up reactive effects for each defined formula.
156
+ * Each formula function receives the hierarchical data proxy, allowing implicit dependency tracking.
157
+ * @param {Object|null} value The new formulas configuration.
158
+ * @param {Object|null} oldValue The old formulas configuration.
124
159
  * @protected
125
160
  */
126
161
  afterSetFormulas(value, oldValue) {
127
- value && this.resolveFormulas(null)
162
+ const me = this;
163
+
164
+ // Destroy old formula effects to prevent memory leaks and stale calculations.
165
+ me.#formulaEffects.forEach(effect => effect.destroy());
166
+ me.#formulaEffects.clear();
167
+
168
+ if (value) {
169
+ Object.entries(value).forEach(([formulaKey, formulaFn]) => {
170
+ // Create a new lazy Effect. It will not run until explicitly told to.
171
+ const effect = new Effect({
172
+ fn: () => {
173
+ const
174
+ hierarchicalData = me.getHierarchyData(),
175
+ result = formulaFn(hierarchicalData);
176
+
177
+ me.setData(formulaKey, result);
178
+ },
179
+ lazy: true
180
+ });
181
+
182
+ me.#formulaEffects.set(formulaKey, effect)
183
+ })
184
+ }
128
185
  }
129
186
 
130
187
  /**
@@ -133,17 +190,7 @@ class Provider extends Base {
133
190
  * @protected
134
191
  */
135
192
  beforeGetData(value) {
136
- return value || {}
137
- }
138
-
139
- /**
140
- * Triggered before the parent config gets changed
141
- * @param {Neo.state.Provider|null} value
142
- * @param {Neo.state.Provider|null} oldValue
143
- * @protected
144
- */
145
- beforeSetParent(value, oldValue) {
146
- return value ? value : this.getParent()
193
+ return this.getHierarchyData()
147
194
  }
148
195
 
149
196
  /**
@@ -171,159 +218,92 @@ class Provider extends Base {
171
218
  }
172
219
 
173
220
  /**
174
- * @param {Function} formatter
175
- * @param {Object} data=null optionally pass this.getHierarchyData() for performance reasons
176
- * @returns {String}
177
- */
178
- callFormatter(formatter, data=null) {
179
- if (!data) {
180
- data = this.getHierarchyData()
181
- }
182
-
183
- return formatter.call(this, data)
184
- }
185
-
186
- /**
187
- * Registers a new binding in case a matching data property does exist.
188
- * Otherwise, it will use the closest stateProvider with a match.
221
+ * Creates a new binding for a component's config to a data property.
222
+ * This now uses the Effect-based reactivity system.
189
223
  * @param {String} componentId
190
- * @param {String} key
191
- * @param {String} value
192
- * @param {String} formatter
193
- */
194
- createBinding(componentId, key, value, formatter) {
195
- let me = this,
196
- data = me.getDataScope(key),
197
- scope = data.scope,
198
- keyLeaf = data.key,
199
- bindingScope, parentStateProvider;
200
-
201
- if (scope?.hasOwnProperty(keyLeaf)) {
202
- bindingScope = Neo.ns(`${key}.${componentId}`, true, me.bindings);
203
- bindingScope[value] = formatter
204
- } else {
205
- parentStateProvider = me.getParent();
206
-
207
- if (parentStateProvider) {
208
- parentStateProvider.createBinding(componentId, key, value, formatter)
209
- } else {
210
- console.error('No state.Provider found with the specified data property', componentId, keyLeaf, value)
211
- }
212
- }
213
- }
224
+ * @param {String} configKey The component config to bind (e.g., 'text').
225
+ * @param {String|Function} formatter The function that computes the value.
226
+ */
227
+ createBinding(componentId, configKey, formatter) {
228
+ const
229
+ me = this,
230
+ effect = new Effect(() => {
231
+ const component = Neo.get(componentId);
232
+
233
+ if (component && !component.isDestroyed) {
234
+ const
235
+ hierarchicalData = me.getHierarchyData(),
236
+ newValue = Neo.isFunction(formatter) ? formatter.call(me, hierarchicalData) : hierarchicalData[formatter];
237
+
238
+ component._skipTwoWayPush = configKey;
239
+ component[configKey] = newValue;
240
+ delete component._skipTwoWayPush
241
+ }
242
+ });
214
243
 
215
- /**
216
- * Registers a new binding in case a matching data property does exist.
217
- * Otherwise, it will use the closest stateProvider with a match.
218
- * @param {String} componentId
219
- * @param {String} formatter
220
- * @param {String} value
221
- * @returns {String[]}
222
- */
223
- createBindingByFormatter(componentId, formatter, value) {
224
- let me = this,
225
- formatterVars = me.getFormatterVariables(formatter);
244
+ me.#bindingEffects.set(componentId, effect);
226
245
 
227
- formatterVars.forEach(key => {
228
- me.createBinding(componentId, key, value, formatter)
246
+ // The effect observes the component's destruction to clean itself up.
247
+ me.observeConfig(componentId, 'isDestroying', (value) => {
248
+ if (value) {
249
+ effect.destroy();
250
+ me.#bindingEffects.delete(componentId)
251
+ }
229
252
  });
230
253
 
231
- return formatterVars
254
+ // The effect is returned to be managed by the component.
255
+ return effect
232
256
  }
233
257
 
234
258
  /**
235
- * @param {Neo.component.Base} component
259
+ * Processes a component's `bind` configuration to create reactive bindings.
260
+ * It differentiates between store bindings and data bindings, and sets up two-way binding if specified.
261
+ * @param {Neo.component.Base} component The component instance whose bindings are to be created.
236
262
  */
237
263
  createBindings(component) {
238
- Object.entries(component.bind).forEach(([key, value]) => {
239
- let twoWayBinding = false,
240
- formatterVars;
241
-
242
- if (Neo.isObject(value)) {
243
- twoWayBinding = true;
244
- value = value.value
245
- }
264
+ let me = this,
265
+ hasTwoWayBinding = false;
246
266
 
247
- if (!this.isStoreValue(value)) {
248
- formatterVars = this.createBindingByFormatter(component.id, value, key);
267
+ Object.entries(component.bind || {}).forEach(([configKey, value]) => {
268
+ let key = value;
249
269
 
250
- if (twoWayBinding) {
251
- component.bind[key].key = formatterVars[0];
252
- component[twoWayBindingSymbol] = true;
270
+ // If the binding value is an object, it might contain `twoWay` or a specific `key`.
271
+ if (Neo.isObject(value)) {
272
+ if (value.twoWay) {
273
+ hasTwoWayBinding = true
253
274
  }
275
+ key = value.key
254
276
  }
255
- })
256
- }
257
277
 
258
- /**
259
- * @param {Object} config
260
- * @param {String} path
261
- */
262
- createDataProperties(config, path) {
263
- let me = this,
264
- root = Neo.ns(path, false, me),
265
- descriptor, keyValue, newPath;
266
-
267
- Object.entries(config).forEach(([key, value]) => {
268
- if (!key.startsWith('_')) {
269
- descriptor = Object.getOwnPropertyDescriptor(root, key);
270
- newPath = `${path}.${key}`
271
-
272
- if (!(typeof descriptor === 'object' && typeof descriptor.set === 'function')) {
273
- keyValue = config[key];
274
- me.createDataProperty(key, newPath, root);
275
- root[key] = keyValue
276
- }
277
-
278
- if (Neo.isObject(value)) {
279
- me.createDataProperties(config[key], newPath)
280
- }
278
+ // Determine if it's a store binding or a data binding.
279
+ if (me.isStoreValue(key)) {
280
+ // For store bindings, resolve the store and assign it to the component config.
281
+ me.resolveStore(component, configKey, key.substring(7)) // remove the "stores." prefix
282
+ } else {
283
+ // For data bindings, create an Effect to keep the component config in sync with the data.
284
+ me.createBinding(component.id, configKey, key, value.twoWay)
281
285
  }
282
- })
286
+ });
287
+
288
+ // Mark the component if it has any two-way bindings, for internal tracking.
289
+ if (hasTwoWayBinding) {
290
+ component[twoWayBindingSymbol] = true
291
+ }
283
292
  }
284
293
 
285
294
  /**
286
- * @param {String} key
287
- * @param {String} path
288
- * @param {Object} root=this.data
295
+ * Destroys the state provider and cleans up all associated effects.
289
296
  */
290
- createDataProperty(key, path, root=this.data) {
291
- let me = this;
297
+ destroy() {
298
+ const me = this;
292
299
 
293
- if (path?.startsWith('data.')) {
294
- path = path.substring(5)
295
- }
296
-
297
- Object.defineProperty(root, key, {
298
- get() {
299
- let value = root['_' + key];
300
+ me.#formulaEffects.forEach(effect => effect.destroy());
301
+ me.#formulaEffects.clear();
300
302
 
301
- if (Neo.typeOf(value) === 'Date') {
302
- value = new Date(value.valueOf())
303
- }
303
+ me.#bindingEffects.forEach(effect => effect.destroy());
304
+ me.#bindingEffects.clear();
304
305
 
305
- return value
306
- },
307
-
308
- set(value) {
309
- let _key = `_${key}`,
310
- oldValue = root[_key];
311
-
312
- if (!root[_key]) {
313
- Object.defineProperty(root, _key, {
314
- enumerable: false,
315
- value,
316
- writable : true
317
- })
318
- } else {
319
- root[_key] = value
320
- }
321
-
322
- if (!Neo.isEqual(value, oldValue)) {
323
- me.onDataPropertyChange(path ? path : key, value, oldValue)
324
- }
325
- }
326
- })
306
+ super.destroy()
327
307
  }
328
308
 
329
309
  /**
@@ -338,133 +318,52 @@ class Provider extends Base {
338
318
  /**
339
319
  * Access the closest data property inside the parent chain.
340
320
  * @param {String} key
341
- * @param {Neo.state.Provider} originStateProvider=this for internal usage only
342
321
  * @returns {*} value
343
322
  */
344
- getData(key, originStateProvider=this) {
345
- let me = this,
346
- data = me.getDataScope(key),
347
- {scope} = data,
348
- keyLeaf = data.key,
349
- parentStateProvider;
350
-
351
- if (scope?.hasOwnProperty(keyLeaf)) {
352
- return scope[keyLeaf]
353
- }
323
+ getData(key) {
324
+ const ownerDetails = this.getOwnerOfDataProperty(key);
354
325
 
355
- parentStateProvider = me.getParent();
356
-
357
- if (!parentStateProvider) {
358
- console.error(`data property '${key}' does not exist.`, originStateProvider)
326
+ if (ownerDetails) {
327
+ return ownerDetails.owner.getDataConfig(ownerDetails.propertyName).get()
359
328
  }
360
-
361
- return parentStateProvider.getData(key, originStateProvider)
362
329
  }
363
330
 
364
331
  /**
365
- * Helper method to get the scope for a nested data property via Neo.ns() if needed.
366
- *
367
- * Example: passing the value 'foo.bar.baz' will return the bar object as the scope
368
- * and 'baz' as the key.
369
- * @param key
370
- * @returns {Object}
332
+ * Retrieves the underlying core.Config instance for a given data property path.
333
+ * @param {String} path The full path of the data property (e.g., 'user.firstname').
334
+ * @returns {Neo.core.Config|null}
371
335
  */
372
- getDataScope(key) {
373
- let me = this,
374
- keyLeaf = key,
375
- {data} = me;
376
-
377
- if (key.includes('.')) {
378
- key = key.split('.');
379
- keyLeaf = key.pop();
380
- data = Neo.ns(key.join('.'), false, data)
381
- }
382
-
383
- return {
384
- key : keyLeaf,
385
- scope: data
386
- }
336
+ getDataConfig(path) {
337
+ return this.#dataConfigs[path] || null
387
338
  }
388
339
 
389
340
  /**
390
- * Extracts data variables from a given formatter string
391
- * @param {String} value
341
+ * Returns the merged, hierarchical data object as a reactive Proxy.
342
+ * @returns {Proxy}
392
343
  */
393
- getFormatterVariables(value) {
394
- let {environment} = Neo.config;
395
-
396
- if (Neo.isFunction(value)) {
397
- value = value.toString()
398
- }
399
-
400
- if (environment === 'dist/esm' || environment === 'dist/production') {
401
- // See: https://github.com/neomjs/neo/issues/2371
402
- // Inside dist/esm & dist/prod the formatter:
403
- // data => DateUtil.convertToyyyymmdd(data.currentDate)
404
- // will get minified to:
405
- // e=>s.Z.convertToyyyymmdd(e.currentDate)
406
- // The new strategy: find the first variable name => "e"
407
- // Replace it with "data":
408
- // data=>s.Z.convertToyyyymmdd(data.currentDate)
409
- // From there we can use the dev mode regex again.
410
-
411
- let dataName = value.match(variableNameRegex)[0],
412
- variableRegExp = new RegExp(`(^|[^\\w.])(${dataName})(?!\\w)`, 'g');
413
-
414
- value = value.replace(variableRegExp, '$1data')
415
- }
416
-
417
- let dataVars = value.match(dataVariableRegex) || [],
418
- result = [];
419
-
420
- dataVars.forEach(variable => {
421
- // remove the "data." at the start
422
- variable = variable.substr(5);
423
- NeoArray.add(result, variable)
424
- });
425
-
426
- result.sort();
427
-
428
- return result
344
+ getHierarchyData() {
345
+ return createHierarchicalDataProxy(this)
429
346
  }
430
347
 
431
348
  /**
432
- * Returns the merged data
433
- * @param {Object} data=this.getPlainData()
434
- * @returns {Object} data
349
+ * Finds the state.Provider instance that owns a specific data property.
350
+ * @param {String} path The full path of the data property.
351
+ * @returns {{owner: Neo.state.Provider, propertyName: String}|null}
435
352
  */
436
- getHierarchyData(data=this.getPlainData()) {
437
- let me = this,
438
- parent = me.getParent();
353
+ getOwnerOfDataProperty(path) {
354
+ let me = this;
439
355
 
440
- if (parent) {
441
- return {
442
- ...parent.getHierarchyData(data),
443
- ...me.getPlainData()
444
- }
356
+ if (me.#dataConfigs[path]) {
357
+ return {owner: me, propertyName: path}
445
358
  }
446
359
 
447
- return me.getPlainData()
448
- }
449
-
450
- /**
451
- * Returns a plain version of this.data.
452
- * This excludes the property getters & setters.
453
- * @param {Object} data=this.data
454
- * @returns {Object}
455
- */
456
- getPlainData(data=this.data) {
457
- let plainData = {};
458
-
459
- Object.entries(data).forEach(([key, value]) => {
460
- if (Neo.typeOf(value) === 'Object') {
461
- plainData[key] = this.getPlainData(value)
462
- } else {
463
- plainData[key] = value
464
- }
465
- });
360
+ // Check for parent ownership
361
+ const parent = me.getParent();
362
+ if (parent) {
363
+ return parent.getOwnerOfDataProperty(path)
364
+ }
466
365
 
467
- return plainData
366
+ return null
468
367
  }
469
368
 
470
369
  /**
@@ -472,13 +371,22 @@ class Provider extends Base {
472
371
  * @returns {Neo.state.Provider|null}
473
372
  */
474
373
  getParent() {
475
- let {parent} = this;
374
+ let me = this;
476
375
 
477
- if (parent) {
478
- return parent
376
+ // Access the internal value of the parent_ config directly.
377
+ // This avoids recursive calls to the getter.
378
+ if (me._parent) {
379
+ return me._parent
380
+ }
381
+
382
+ // If no explicit parent is set, try to find it dynamically via the component.
383
+ // Ensure this.component exists before trying to access its parent.
384
+ if (me.component) {
385
+ return me.component.parent?.getStateProvider() || null
479
386
  }
480
387
 
481
- return this.component.parent?.getStateProvider() || null
388
+ // No explicit parent and no component to derive it from.
389
+ return null
482
390
  }
483
391
 
484
392
  /**
@@ -506,48 +414,111 @@ class Provider extends Base {
506
414
  }
507
415
 
508
416
  /**
509
- * Internal method to avoid code redundancy.
510
- * Use setData() or setDataAtSameLevel() instead.
417
+ * Checks if any data property in the hierarchy starts with the given path.
418
+ * This is used by the HierarchicalDataProxy to determine if it should return a nested proxy.
419
+ * @param {String} path The path to check (e.g., 'user').
420
+ * @returns {Boolean}
421
+ */
422
+ hasNestedDataStartingWith(path) {
423
+ const pathWithDot = `${path}.`;
424
+
425
+ if (Object.keys(this.#dataConfigs).some(key => key.startsWith(pathWithDot))) {
426
+ return true
427
+ }
428
+
429
+ return this.getParent()?.hasNestedDataStartingWith(path) || false
430
+ }
431
+
432
+ /**
433
+ * Returns the top-level data keys for a given path within this provider's data.
434
+ * @param {String} path The path to get keys for (e.g., 'user.address').
435
+ * @returns {String[]}
436
+ */
437
+ getTopLevelDataKeys(path) {
438
+ const
439
+ keys = new Set(),
440
+ pathPrefix = path ? `${path}.` : '';
441
+
442
+ for (const fullPath in this.#dataConfigs) {
443
+ if (fullPath.startsWith(pathPrefix)) {
444
+ const
445
+ relativePath = fullPath.substring(pathPrefix.length),
446
+ topLevelKey = relativePath.split('.')[0];
447
+
448
+ if (topLevelKey) {
449
+ keys.add(topLevelKey)
450
+ }
451
+ }
452
+ }
453
+
454
+ return Array.from(keys)
455
+ }
456
+
457
+ /**
458
+ * This is the core method for setting data, providing a single entry point for all data modifications.
459
+ * It handles multiple scenarios:
460
+ * 1. **Object-based updates:** If `key` is an object, it recursively calls itself for each key-value pair.
461
+ * 2. **Data Records:** If `value` is a `Neo.data.Record`, it is treated as an atomic value and set directly.
462
+ * 3. **Bubbling Reactivity:** For a given key (e.g., 'user.name'), it sets the leaf value and then "bubbles up"
463
+ * the change, creating new parent objects (e.g., 'user') to ensure that effects depending on any part
464
+ * of the path are triggered.
511
465
  *
512
- * Passing an originStateProvider param will try to set each key on the closest property match
513
- * inside the parent stateProvider chain => setData()
514
- * Not passing it will set all values on the stateProvider where the method gets called => setDataAtSameLevel()
515
- * @param {Object|String} key
516
- * @param {*} value
517
- * @param {Neo.state.Provider} [originStateProvider]
466
+ * All updates are batched by the public `setData` methods to ensure effects run only once.
467
+ * Use `setData()` or `setDataAtSameLevel()` instead of calling this method directly.
468
+ *
469
+ * @param {Object|String} key The property to set, or an object of key-value pairs.
470
+ * @param {*} value The new value.
471
+ * @param {Neo.state.Provider} [originStateProvider] The provider to start the search from for hierarchical updates.
518
472
  * @protected
519
473
  */
520
474
  internalSetData(key, value, originStateProvider) {
521
- let me = this,
522
- data, keyLeaf, parentStateProvider, scope;
475
+ const me = this;
523
476
 
524
- if (Neo.isObject(value) && !value.isRecord) {
525
- Object.entries(value).forEach(([dataKey, dataValue]) => {
526
- me.internalSetData(`${key}.${dataKey}`, dataValue, originStateProvider)
527
- })
528
- } else if (Neo.isObject(key)) {
477
+ // If the value is a Neo.data.Record, treat it as an atomic value
478
+ // and set it directly without further recursive processing of its properties.
479
+ if (Neo.isRecord(value)) {
480
+ const
481
+ ownerDetails = me.getOwnerOfDataProperty(key),
482
+ targetProvider = ownerDetails ? ownerDetails.owner : (originStateProvider || me);
483
+
484
+ me.#setConfigValue(targetProvider, key, value, null);
485
+ return
486
+ }
487
+
488
+ if (Neo.isObject(key)) {
529
489
  Object.entries(key).forEach(([dataKey, dataValue]) => {
530
490
  me.internalSetData(dataKey, dataValue, originStateProvider)
531
- })
532
- } else {
533
- data = me.getDataScope(key);
534
- keyLeaf = data.key;
535
- scope = data.scope;
491
+ });
492
+ return
493
+ }
536
494
 
537
- if (scope?.hasOwnProperty(keyLeaf)) {
538
- scope[keyLeaf] = value
539
- } else {
540
- if (originStateProvider) {
541
- parentStateProvider = me.getParent();
542
-
543
- if (parentStateProvider) {
544
- parentStateProvider.internalSetData(key, value, originStateProvider)
545
- } else {
546
- originStateProvider.addDataProperty(key, value)
547
- }
495
+ const
496
+ ownerDetails = me.getOwnerOfDataProperty(key),
497
+ targetProvider = ownerDetails ? ownerDetails.owner : (originStateProvider || me);
498
+
499
+ me.#setConfigValue(targetProvider, key, value, null);
500
+
501
+ // Bubble up the change to parent configs to trigger their effects
502
+ let path = key,
503
+ latestValue = value;
504
+
505
+ while (path.includes('.')) {
506
+ const leafKey = path.split('.').pop();
507
+ path = path.substring(0, path.lastIndexOf('.'));
508
+
509
+ const parentConfig = targetProvider.getDataConfig(path);
510
+
511
+ if (parentConfig) {
512
+ const oldParentValue = parentConfig.get();
513
+ if (Neo.isObject(oldParentValue)) {
514
+ const newParentValue = { ...oldParentValue, [leafKey]: latestValue };
515
+ parentConfig.set(newParentValue);
516
+ latestValue = newParentValue;
548
517
  } else {
549
- me.addDataProperty(key, value)
518
+ break // Stop if parent is not an object
550
519
  }
520
+ } else {
521
+ break // Stop if parent config does not exist
551
522
  }
552
523
  }
553
524
  }
@@ -562,17 +533,15 @@ class Provider extends Base {
562
533
  }
563
534
 
564
535
  /**
565
- * Override this method to change the order configs are applied to this instance.
566
- * @param {Object} config
567
- * @param {Boolean} [preventOriginalConfig] True prevents the instance from getting an originalConfig property
568
- * @returns {Object} config
536
+ * Gets called after all constructors & configs are applied.
537
+ * @protected
569
538
  */
570
- mergeConfig(config, preventOriginalConfig) {
571
- if (config.data) {
572
- config.data = Neo.merge(Neo.clone(this.constructor.config.data, true) || {}, config.data)
573
- }
539
+ onConstructed() {
540
+ super.onConstructed();
574
541
 
575
- return super.mergeConfig(config, preventOriginalConfig)
542
+ // After the provider is fully constructed and initial data is set,
543
+ // run the formula effects for the first time to compute their initial values.
544
+ this.#formulaEffects.forEach(effect => effect.run())
576
545
  }
577
546
 
578
547
  /**
@@ -581,124 +550,34 @@ class Provider extends Base {
581
550
  * @param {*} oldValue
582
551
  */
583
552
  onDataPropertyChange(key, value, oldValue) {
584
- let me = this,
585
- binding = me.bindings && Neo.ns(key, false, me.bindings),
586
- component, config, hierarchyData, stateProvider;
587
-
588
- if (binding) {
589
- hierarchyData = {};
590
-
591
- Object.entries(binding).forEach(([componentId, configObject]) => {
592
- component = Neo.getComponent(componentId) || Neo.get(componentId); // timing issue: the cmp might not be registered inside manager.Component yet
593
- config = {};
594
- stateProvider = component.getStateProvider() || me;
595
-
596
- if (!hierarchyData[stateProvider.id]) {
597
- hierarchyData[stateProvider.id] = stateProvider.getHierarchyData()
598
- }
599
-
600
- Object.entries(configObject).forEach(([configField, formatter]) => {
601
- // we can not call me.callFormatter(), since a data property inside a parent stateProvider
602
- // could have changed which is relying on data properties inside a closer stateProvider
603
- config[configField] = stateProvider.callFormatter(formatter, hierarchyData[stateProvider.id])
604
- });
605
-
606
- component?.set(config)
607
- })
608
- }
609
-
610
- me.formulas && me.resolveFormulas({key, id: me.id, oldValue, value});
611
-
612
- me.fire('dataPropertyChange', {key, id: me.id, oldValue, value})
553
+ // Can be overridden by subclasses
613
554
  }
614
555
 
615
556
  /**
616
- * This method will assign binding values at the earliest possible point inside the component lifecycle.
617
- * It can not store bindings though, since child component ids most likely do not exist yet.
618
- * @param {Neo.component.Base} component=this.component
619
- */
620
- parseConfig(component=this.component) {
621
- let me = this,
622
- config = {};
623
-
624
- if (component.bind) {
625
- me.createBindings(component);
626
-
627
- Object.entries(component.bind).forEach(([key, value]) => {
628
- if (Neo.isObject(value)) {
629
- value.key = me.getFormatterVariables(value.value)[0];
630
- value = value.value
631
- }
632
-
633
- if (me.isStoreValue(value)) {
634
- me.resolveStore(component, key, value.substring(7)) // remove the "stores." at the start
635
- } else {
636
- config[key] = me.callFormatter(value)
637
- }
638
- });
639
-
640
- component.set(config)
641
- }
642
- }
643
-
644
- /**
645
- * Removes all bindings for a given component id inside this stateProvider as well as inside all parent stateProviders.
646
- * @param {String} componentId
557
+ * Recursively processes a data object, creating or updating Neo.core.Config instances
558
+ * for each property and storing them in the #dataConfigs map.
559
+ * @param {Object} obj The data object to process.
560
+ * @param {String} [path=''] The current path prefix for nested objects.
561
+ * @protected
647
562
  */
648
- removeBindings(componentId) {
563
+ processDataObject(obj, path = '') {
649
564
  let me = this;
650
565
 
651
- Object.entries(me.bindings).forEach(([dataProperty, binding]) => {
652
- delete binding[componentId]
653
- });
566
+ Object.entries(obj).forEach(([key, value]) => {
567
+ const fullPath = path ? `${path}.${key}` : key;
654
568
 
655
- me.getParent()?.removeBindings(componentId)
656
- }
657
-
658
- /**
659
- * Resolve the formulas initially and update, when data change
660
- * @param {Object} data data from event or null on initial call
661
- */
662
- resolveFormulas(data) {
663
- let me = this,
664
- {formulas} = me,
665
- initialRun = !data,
666
- affectFormula, bindObject, fn, key, result, value;
667
-
668
- if (formulas) {
669
- if (!initialRun && (!data.key || !data.value)) {
670
- console.warn('[StateProvider:formulas] missing key or value', data.key, data.value)
569
+ // Ensure a Config instance exists for the current fullPath
570
+ if (me.#dataConfigs[fullPath]) {
571
+ me.#dataConfigs[fullPath].set(value)
572
+ } else {
573
+ me.#dataConfigs[fullPath] = new Config(value)
671
574
  }
672
575
 
673
- for ([key, value] of Object.entries(formulas)) {
674
- affectFormula = true;
675
-
676
- // Check if the change affects a formula
677
- if (!initialRun) {
678
- affectFormula = Object.values(value.bind).includes(data.key)
679
- }
680
-
681
- if (affectFormula) {
682
- // Create Bind-Object and fill with new values
683
- bindObject = Neo.clone(value.bind);
684
- fn = value.get;
685
-
686
- Object.keys(bindObject).forEach((key, index) => {
687
- bindObject[key] = me.getData(bindObject[key])
688
- });
689
-
690
- // Calc the formula
691
- result = fn(bindObject);
692
-
693
- // Assign if no error or null
694
- if (isNaN(result)) {
695
- me.setData(key, null)
696
- } else {
697
- me.setData(key, result)
698
- }
699
- }
576
+ // If the value is a plain object, recursively process its properties
577
+ if (Neo.typeOf(value) === 'Object') {
578
+ me.processDataObject(value, fullPath)
700
579
  }
701
- }
580
+ });
702
581
  }
703
582
 
704
583
  /**
@@ -714,24 +593,64 @@ class Provider extends Base {
714
593
  }
715
594
  }
716
595
 
596
+ /**
597
+ * Helper function to set a config value and trigger reactivity.
598
+ * This method creates a new Config instance if one doesn't exist for the given path,
599
+ * or updates an existing one. It also triggers binding effects and calls onDataPropertyChange.
600
+ * @param {Neo.state.Provider} provider The StateProvider instance owning the config.
601
+ * @param {String} path The full path of the data property (e.g., 'user.firstname').
602
+ * @param {*} newValue The new value to set.
603
+ * @param {*} oldVal The old value (optional, used for initial setup).
604
+ * @private
605
+ */
606
+ #setConfigValue(provider, path, newValue, oldVal) {
607
+ let currentConfig = provider.getDataConfig(path),
608
+ oldValue = oldVal;
609
+
610
+ if (currentConfig) {
611
+ oldValue = currentConfig.get();
612
+ currentConfig.set(newValue);
613
+ } else {
614
+ currentConfig = new Config(newValue);
615
+ provider.#dataConfigs[path] = currentConfig;
616
+ // Trigger all binding effects to re-evaluate their dependencies
617
+ provider.#bindingEffects.forEach(effect => effect.run())
618
+ }
619
+
620
+ // Notify subscribers of the data property change.
621
+ provider.onDataPropertyChange(path, newValue, oldValue)
622
+ }
623
+
717
624
  /**
718
625
  * The method will assign all values to the closest stateProvider where it finds an existing key.
719
626
  * In case no match is found inside the parent chain, a new data property will get generated.
627
+ *
628
+ * All updates within a single call are batched to ensure that reactive effects (bindings and formulas)
629
+ * are run only once.
630
+ *
720
631
  * @param {Object|String} key
721
632
  * @param {*} value
722
633
  */
723
634
  setData(key, value) {
724
- this.internalSetData(key, value, this)
635
+ EffectBatchManager.startBatch();
636
+ this.internalSetData(key, value, this);
637
+ EffectBatchManager.endBatch()
725
638
  }
726
639
 
727
640
  /**
728
641
  * Use this method instead of setData() in case you want to enforce
729
642
  * setting all keys on this instance instead of looking for matches inside parent stateProviders.
643
+ *
644
+ * All updates within a single call are batched to ensure that reactive effects (bindings and formulas)
645
+ * are run only once.
646
+ *
730
647
  * @param {Object|String} key
731
648
  * @param {*} value
732
649
  */
733
650
  setDataAtSameLevel(key, value) {
734
- this.internalSetData(key, value)
651
+ EffectBatchManager.startBatch();
652
+ this.internalSetData(key, value);
653
+ EffectBatchManager.endBatch()
735
654
  }
736
655
  }
737
656