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,166 @@
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+
4
+ class TestComponent extends core.Base {
5
+ static config = {
6
+ className : 'Neo.Test.CircularDependenciesComponent',
7
+ configA_ : 'initialA',
8
+ configB_ : 'initialB',
9
+ configC_ : 'initialC',
10
+ nonReactive: 'initialNonReactive'
11
+ }
12
+
13
+ classField = 'initialClassField';
14
+
15
+ _customProp = 'initialCustomProp'; // Private backing field for customProp
16
+
17
+ // Custom getter/setter for a property not defined in static config
18
+ get customProp() {
19
+ return this._customProp;
20
+ }
21
+ set customProp(value) {
22
+ this._customProp = value;
23
+
24
+ // Simulate interaction with other properties
25
+ this.customPropSetLog.push({
26
+ value,
27
+ currentConfigA : this.configA,
28
+ currentNonReactive: this.nonReactive,
29
+ currentClassField : this.classField
30
+ });
31
+ }
32
+
33
+ // To record the state inside afterSet hooks and customProp setter
34
+ afterSetLogs = {
35
+ configA: [],
36
+ configB: [],
37
+ configC: []
38
+ };
39
+ customPropSetLog = [];
40
+
41
+ afterSetConfigA(newValue, oldValue) {
42
+ this.afterSetLogs.configA.push({
43
+ newValue,
44
+ oldValue,
45
+ currentConfigB : this.configB,
46
+ currentConfigC : this.configC,
47
+ currentClassField : this.classField,
48
+ currentNonReactive: this.nonReactive,
49
+ currentCustomProp : this.customProp
50
+ });
51
+ }
52
+
53
+ afterSetConfigB(newValue, oldValue) {
54
+ this.afterSetLogs.configB.push({
55
+ newValue,
56
+ oldValue,
57
+ currentConfigA : this.configA,
58
+ currentConfigC : this.configC,
59
+ currentClassField : this.classField,
60
+ currentNonReactive: this.nonReactive,
61
+ currentCustomProp : this.customProp
62
+ });
63
+ }
64
+
65
+ afterSetConfigC(newValue, oldValue) {
66
+ this.afterSetLogs.configC.push({
67
+ newValue,
68
+ oldValue,
69
+ currentConfigA : this.configA,
70
+ currentConfigB : this.configB,
71
+ currentClassField : this.classField,
72
+ currentNonReactive: this.nonReactive,
73
+ currentCustomProp : this.customProp
74
+ });
75
+ }
76
+ }
77
+ Neo.setupClass(TestComponent);
78
+
79
+ StartTest(t => {
80
+ t.it('afterSet hooks should see latest values of other configs and fields during simultaneous update', t => {
81
+ const instance = Neo.create(TestComponent);
82
+ instance.afterSetLogs = { configA: [], configB: [], configC: [] }; // Clear logs from initial config processing
83
+ instance.customPropSetLog = []; // Clear customProp logs
84
+
85
+ instance.set({
86
+ configA : 'newA',
87
+ configB : 'newB',
88
+ configC : 'newC',
89
+ nonReactive: 'newNonReactive',
90
+ classField : 'newClassField',
91
+ customProp : 'newCustomProp'
92
+ });
93
+
94
+ // Verify afterSetConfigA log
95
+ t.is(instance.afterSetLogs.configA.length, 1, 'afterSetConfigA should be called once');
96
+ const logA = instance.afterSetLogs.configA[0];
97
+ t.is(logA.newValue, 'newA', 'logA: newValue is correct');
98
+ t.is(logA.oldValue, 'initialA', 'logA: oldValue is correct');
99
+ t.is(logA.currentConfigB, 'newB', 'logA: currentConfigB should be newB');
100
+ t.is(logA.currentConfigC, 'newC', 'logA: currentConfigC should be newC');
101
+ t.is(logA.currentClassField, 'newClassField', 'logA: currentClassField should be newClassField');
102
+ t.is(logA.currentNonReactive, 'newNonReactive', 'logA: currentNonReactive should be newNonReactive');
103
+ t.is(logA.currentCustomProp, 'newCustomProp', 'logA: currentCustomProp should be newCustomProp');
104
+
105
+ // Verify afterSetConfigB log
106
+ t.is(instance.afterSetLogs.configB.length, 1, 'afterSetConfigB should be called once');
107
+ const logB = instance.afterSetLogs.configB[0];
108
+ t.is(logB.newValue, 'newB', 'logB: newValue is correct');
109
+ t.is(logB.oldValue, 'initialB', 'logB: oldValue is correct');
110
+ t.is(logB.currentConfigA, 'newA', 'logB: currentConfigA should be newA');
111
+ t.is(logB.currentConfigC, 'newC', 'logB: currentConfigC should be newC');
112
+ t.is(logB.currentClassField, 'newClassField', 'logB: currentClassField should be newClassField');
113
+ t.is(logB.currentNonReactive, 'newNonReactive', 'logB: currentNonReactive should be newNonReactive');
114
+ t.is(logB.currentCustomProp, 'newCustomProp', 'logB: currentCustomProp should be newCustomProp');
115
+
116
+ // Verify afterSetConfigC log
117
+ t.is(instance.afterSetLogs.configC.length, 1, 'afterSetConfigC should be called once');
118
+ const logC = instance.afterSetLogs.configC[0];
119
+ t.is(logC.newValue, 'newC', 'logC: newValue is correct');
120
+ t.is(logC.oldValue, 'initialC', 'logC: oldValue is correct');
121
+ t.is(logC.currentConfigA, 'newA', 'logC: currentConfigA should be newA');
122
+ t.is(logC.currentConfigB, 'newB', 'logC: currentConfigB should be newB');
123
+ t.is(logC.currentClassField, 'newClassField', 'logC: currentClassField should be newClassField');
124
+ t.is(logC.currentNonReactive, 'newNonReactive', 'logC: currentNonReactive should be newNonReactive');
125
+ t.is(logC.currentCustomProp, 'newCustomProp', 'logC: currentCustomProp should be newCustomProp');
126
+
127
+ // Verify customProp setter log
128
+ t.is(instance.customPropSetLog.length, 1, 'customProp setter should be called once');
129
+ const customPropLog = instance.customPropSetLog[0];
130
+ t.is(customPropLog.value, 'newCustomProp', 'customProp setter: value is correct');
131
+ t.is(customPropLog.currentConfigA, 'newA', 'customProp setter: currentConfigA should be newA');
132
+ t.is(customPropLog.currentNonReactive, 'newNonReactive', 'customProp setter: currentNonReactive should be newNonReactive');
133
+ t.is(customPropLog.currentClassField, 'newClassField', 'customProp setter: currentClassField should be newClassField');
134
+ });
135
+
136
+ t.it('afterSet hooks should see existing values if not part of current set() operation', t => {
137
+ const instance = Neo.create(TestComponent, {
138
+ configA : 'preSetA',
139
+ configB : 'preSetB',
140
+ configC : 'preSetC',
141
+ nonReactive: 'preSetNonReactive',
142
+ classField : 'preSetClassField',
143
+ customProp : 'preSetCustomProp'
144
+ });
145
+ instance.afterSetLogs = { configA: [], configB: [], configC: [] }; // Reset logs
146
+ instance.customPropSetLog = []; // Reset customProp logs
147
+
148
+ instance.set({
149
+ configA: 'newAOnly'
150
+ });
151
+
152
+ t.is(instance.afterSetLogs.configA.length, 1, 'afterSetConfigA should be called once');
153
+ const logA = instance.afterSetLogs.configA[0];
154
+ t.is(logA.newValue, 'newAOnly', 'logA: newValue is correct');
155
+ t.is(logA.oldValue, 'preSetA', 'logA: oldValue is correct');
156
+ t.is(logA.currentConfigB, 'preSetB', 'logA: currentConfigB should be preSetB');
157
+ t.is(logA.currentConfigC, 'preSetC', 'logA: currentConfigC should be preSetC');
158
+ t.is(logA.currentClassField, 'preSetClassField', 'logA: currentClassField should be preSetClassField');
159
+ t.is(logA.currentNonReactive, 'preSetNonReactive', 'logA: currentNonReactive should be preSetNonReactive');
160
+ t.is(logA.currentCustomProp, 'preSetCustomProp', 'logA: currentCustomProp should be preSetCustomProp');
161
+
162
+ t.is(instance.afterSetLogs.configB.length, 0, 'afterSetConfigB should not be called');
163
+ t.is(instance.afterSetLogs.configC.length, 0, 'afterSetConfigC should not be called');
164
+ t.is(instance.customPropSetLog.length, 0, 'customProp setter should not be called');
165
+ });
166
+ });
@@ -0,0 +1,69 @@
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import {isDescriptor} from '../../../../src/core/ConfigSymbols.mjs';
4
+
5
+ class TestComponent extends core.Base {
6
+ static config = {
7
+ className : 'Neo.Test.CustomFunctionsComponent',
8
+ arrayConfig_ : {
9
+ [isDescriptor]: true,
10
+ value : [1, 2],
11
+ // NOTE: The custom merge function is called during two distinct phases:
12
+ // 1. During Neo.setupClass(): To merge the static config down the inheritance chain.
13
+ // At this stage, `defaultValue` will be `undefined` if the config is defined for the
14
+ // first time in this class. The function must handle this case.
15
+ // 2. During Neo.create(): To merge an instance-specific value into the class's final
16
+ // static default value.
17
+ merge : (defaultValue, instanceValue) => {
18
+ // Custom merge: concatenate arrays. Handle the initial undefined case.
19
+ return (defaultValue || []).concat(instanceValue);
20
+ }
21
+ },
22
+ objectConfig_: {
23
+ [isDescriptor]: true,
24
+ value : {id: 1, data: 'initial'},
25
+ isEqual : (a, b) => {
26
+ // Custom isEqual: compare by id property
27
+ return a?.id === b?.id;
28
+ }
29
+ }
30
+ }
31
+ }
32
+ Neo.setupClass(TestComponent);
33
+
34
+ StartTest(t => {
35
+ t.it('Custom "merge" function for array concatenation', t => {
36
+ const instance = Neo.create(TestComponent, {
37
+ arrayConfig: [3, 4]
38
+ });
39
+
40
+ // The custom merge function should concatenate the static default value [1, 2]
41
+ // with the instance value [3, 4]
42
+ t.isDeeplyStrict(instance.arrayConfig, [1, 2, 3, 4], 'Custom merge function should concatenate arrays');
43
+ });
44
+
45
+ t.it('Custom "isEqual" function for object comparison by id', t => {
46
+ const instance = Neo.create(TestComponent);
47
+
48
+ let subscriberCalled = 0;
49
+ instance.observeConfig(instance, 'objectConfig', () => {
50
+ subscriberCalled++;
51
+ });
52
+
53
+ t.is(instance.objectConfig.data, 'initial', 'Initial object data is correct');
54
+
55
+ // Set a new object with the SAME id. The custom isEqual should return true.
56
+ instance.objectConfig = {id: 1, data: 'updated'};
57
+
58
+ t.is(subscriberCalled, 0, 'Subscriber should NOT be called when id is the same');
59
+ // The value should NOT have been updated because isEqual returned true
60
+ t.is(instance.objectConfig.data, 'initial', 'Object data should not change when id is the same');
61
+
62
+
63
+ // Set a new object with a DIFFERENT id. The custom isEqual should return false.
64
+ instance.objectConfig = {id: 2, data: 'new object'};
65
+
66
+ t.is(subscriberCalled, 1, 'Subscriber should be called when id is different');
67
+ t.is(instance.objectConfig.data, 'new object', 'Object data should update when id is different');
68
+ });
69
+ });
@@ -0,0 +1,94 @@
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import {isDescriptor} from '../../../../src/core/ConfigSymbols.mjs';
4
+
5
+ // Base Class
6
+ class BaseComponent extends core.Base {
7
+ static config = {
8
+ className : 'Neo.Test.BaseComponent',
9
+ myConfig_ : 'baseValue',
10
+ arrayConfig_ : {
11
+ [isDescriptor]: true,
12
+ value : [1, 2],
13
+ merge : 'replace'
14
+ },
15
+ objectConfig_: {
16
+ [isDescriptor]: true,
17
+ value : {a: 1, b: {c: 2}},
18
+ merge : 'deep'
19
+ }
20
+ }
21
+ }
22
+ Neo.setupClass(BaseComponent);
23
+
24
+ // Derived Class
25
+ class DerivedComponent extends BaseComponent {
26
+ static config = {
27
+ className : 'Neo.Test.DerivedComponent',
28
+ myConfig : 'derivedValue',
29
+ arrayConfig : [3, 4], // Override with a new array
30
+ objectConfig: {b: {c: 3, d: 4}, e: 5} // Override with a partial object for deep merge
31
+ }
32
+ }
33
+ Neo.setupClass(DerivedComponent);
34
+
35
+
36
+ StartTest(t => {
37
+ t.it('Initial values for DerivedComponent instance', t => {
38
+ const instance = Neo.create(DerivedComponent);
39
+
40
+ t.is(instance.myConfig, 'derivedValue', 'myConfig should be the value from the derived class');
41
+
42
+ // Test 'replace' merge strategy for arrayConfig
43
+ t.isDeeplyStrict(instance.arrayConfig, [3, 4], 'arrayConfig should be replaced by the derived class value');
44
+
45
+ // Test 'deep' merge strategy for objectConfig
46
+ const expectedObject = {
47
+ a: 1, // from BaseComponent
48
+ b: {c: 3, d: 4}, // from DerivedComponent (b.c is overwritten, b.d is added)
49
+ e: 5 // from DerivedComponent
50
+ };
51
+ t.isDeeplyStrict(instance.objectConfig, expectedObject, 'objectConfig should be a deep merge of base and derived values');
52
+ });
53
+
54
+ t.it('Reactivity on DerivedComponent instance', t => {
55
+ const instance = Neo.create(DerivedComponent);
56
+
57
+ let subscriberCalled = false;
58
+ let receivedNewValue, receivedOldValue;
59
+
60
+ const cleanup = instance.observeConfig(instance, 'myConfig', (newValue, oldValue) => { // Discouraged: Self-observation
61
+ subscriberCalled = true;
62
+ receivedNewValue = newValue;
63
+ receivedOldValue = oldValue;
64
+ });
65
+
66
+ instance.myConfig = 'newValue';
67
+
68
+ t.ok(subscriberCalled, 'Subscriber callback should be called');
69
+ t.is(receivedNewValue, 'newValue', 'New value should be passed to subscriber');
70
+ t.is(receivedOldValue, 'derivedValue', 'Old value should be the initial value from the derived class');
71
+
72
+ cleanup();
73
+ });
74
+
75
+ t.it('Instance-level config overrides derived defaults', t => {
76
+ const instance = Neo.create(DerivedComponent, {
77
+ myConfig : 'instanceValue',
78
+ arrayConfig : [5, 6],
79
+ objectConfig: {b: {c: 99}, g: 7}
80
+ });
81
+
82
+ t.is(instance.myConfig, 'instanceValue', 'myConfig should be the value from the instance config');
83
+ t.isDeeplyStrict(instance.arrayConfig, [5, 6], 'arrayConfig should be replaced by the instance config value');
84
+
85
+ const expectedObject = {
86
+ a: 1, // from BaseComponent
87
+ b: {c: 99, d: 4}, // b.c from instance, b.d from DerivedComponent
88
+ e: 5, // from DerivedComponent
89
+ g: 7 // from instance
90
+ };
91
+
92
+ t.isDeeplyStrict(instance.objectConfig, expectedObject, 'objectConfig from instance should be deep-merged into the class default');
93
+ });
94
+ });
@@ -0,0 +1,92 @@
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+
4
+ class PublisherComponent extends core.Base {
5
+ static config = {
6
+ className: 'PublisherComponent',
7
+ myConfig_: 'initialValue'
8
+ }
9
+ }
10
+
11
+ class LeakySubscriberComponent extends core.Base {
12
+ static config = {
13
+ className: 'LeakySubscriberComponent'
14
+ }
15
+
16
+ // A flag to check if the subscriber was called
17
+ subscriberCalled = false; // This will be updated via sharedState
18
+
19
+ // A method to subscribe to a publisher's config
20
+ subscribeToPublisher(publisherInstance, sharedStateRef) {
21
+ this.cleanupFn = this.observeConfig(publisherInstance, 'myConfig', (newValue, oldValue) => {
22
+ sharedStateRef.subscriberCalled = true;
23
+ });
24
+ }
25
+ }
26
+
27
+ PublisherComponent = Neo.setupClass(PublisherComponent);
28
+ LeakySubscriberComponent = Neo.setupClass(LeakySubscriberComponent);
29
+
30
+ StartTest(t => {
31
+ t.it('Memory leak scenario: destroyed subscriber without cleanup', t => {
32
+ const sharedState = { subscriberCalled: false };
33
+ const publisher = Neo.create(PublisherComponent);
34
+ let subscriber = Neo.create(LeakySubscriberComponent);
35
+
36
+ subscriber.subscribeToPublisher(publisher, sharedState);
37
+
38
+ // Destroy the subscriber instance without calling cleanupFn
39
+ subscriber.destroy();
40
+ subscriber = null; // Nullify reference to help GC, though subscription still exists
41
+
42
+ // Change the publisher's config value
43
+ // This should trigger the subscriber's callback if the leak exists
44
+ publisher.myConfig = 'newValueAfterSubscriberDestroyed';
45
+
46
+ // Assert that the subscriber's callback was NOT called due to automatic cleanup
47
+ t.notOk(sharedState.subscriberCalled, 'Leaked subscriber callback should NOT be called due to automatic cleanup');
48
+
49
+ // The observeConfig method now handles automatic cleanup, so no manual cleanup is needed here.
50
+ publisher.destroy();
51
+ });
52
+
53
+ t.it('No memory leak scenario: destroyed subscriber with cleanup', t => {
54
+ const sharedStateClean = { subscriberCalled: false };
55
+ const publisherClean = Neo.create(PublisherComponent);
56
+ let subscriberClean = Neo.create(LeakySubscriberComponent);
57
+
58
+ subscriberClean.subscribeToPublisher(publisherClean, sharedStateClean);
59
+
60
+ // Explicitly call cleanup before destroying
61
+ subscriberClean.destroy();
62
+ subscriberClean = null;
63
+
64
+ publisherClean.myConfig = 'noLeakTrigger';
65
+
66
+ t.notOk(sharedStateClean.subscriberCalled, 'Subscriber callback should NOT be called after cleanup');
67
+ publisherClean.destroy(); // Clean up publisher
68
+ });
69
+
70
+ t.it('Manual unsubscribe before destruction', t => {
71
+ const sharedStateManual = { subscriberCalled: false };
72
+ const publisherManual = Neo.create(PublisherComponent);
73
+ let subscriberManual = Neo.create(LeakySubscriberComponent);
74
+
75
+ const cleanupManual = subscriberManual.observeConfig(publisherManual, 'myConfig', (newValue, oldValue) => {
76
+ sharedStateManual.subscriberCalled = true;
77
+ });
78
+
79
+ // Manually unsubscribe
80
+ cleanupManual();
81
+
82
+ // Change the publisher's config value
83
+ publisherManual.myConfig = 'valueAfterManualUnsubscribe';
84
+
85
+ // Assert that the subscriber's callback was NOT called
86
+ t.notOk(sharedStateManual.subscriberCalled, 'Subscriber callback should NOT be called after manual unsubscribe');
87
+
88
+ // Destroy instances to ensure full cleanup
89
+ subscriberManual.destroy();
90
+ publisherManual.destroy();
91
+ });
92
+ });
@@ -0,0 +1,85 @@
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import {isDescriptor} from '../../../../src/core/ConfigSymbols.mjs';
4
+
5
+ // Level 1
6
+ class BaseComponent extends core.Base {
7
+ static config = {
8
+ className : 'Neo.Test.MultiLevel.BaseComponent',
9
+ myConfig_ : 'baseValue',
10
+ arrayConfig_ : {
11
+ [isDescriptor]: true,
12
+ value : [1, 2],
13
+ merge : 'replace'
14
+ },
15
+ objectConfig_: {
16
+ [isDescriptor]: true,
17
+ value : {a: 1, b: {c: 2}},
18
+ merge : 'deep'
19
+ }
20
+ }
21
+ }
22
+ Neo.setupClass(BaseComponent);
23
+
24
+ // Level 2
25
+ class MidLevelComponent extends BaseComponent {
26
+ static config = {
27
+ className : 'Neo.Test.MultiLevel.MidLevelComponent',
28
+ arrayConfig : [3, 4],
29
+ objectConfig: {b: {c: 3, d: 4}, e: 5}
30
+ }
31
+ }
32
+ Neo.setupClass(MidLevelComponent);
33
+
34
+ // Level 3
35
+ class TopLevelComponent extends MidLevelComponent {
36
+ static config = {
37
+ className : 'Neo.Test.MultiLevel.TopLevelComponent',
38
+ myConfig : 'topValue',
39
+ objectConfig: {b: {d: 99}, f: 6} // Override again
40
+ }
41
+ }
42
+ Neo.setupClass(TopLevelComponent);
43
+
44
+
45
+ StartTest(t => {
46
+ t.it('Initial values for a three-level hierarchy instance', t => {
47
+ const instance = Neo.create(TopLevelComponent);
48
+
49
+ // myConfig is defined in Base, overridden in Top (Mid doesn't touch it)
50
+ t.is(instance.myConfig, 'topValue', 'myConfig should be the value from the top-level class');
51
+
52
+ // arrayConfig is defined in Base, overridden in Mid (Top doesn't touch it)
53
+ t.isDeeplyStrict(instance.arrayConfig, [3, 4], 'arrayConfig should be replaced by the mid-level class value');
54
+
55
+ // objectConfig is defined in Base, merged in Mid, and merged again in Top
56
+ const expectedObject = {
57
+ a: 1, // from Base
58
+ b: {c: 3, d: 99}, // c from Mid, d from Top
59
+ e: 5, // from Mid
60
+ f: 6 // from Top
61
+ };
62
+ t.isDeeplyStrict(instance.objectConfig, expectedObject, 'objectConfig should be a deep merge of all three levels');
63
+ });
64
+
65
+ t.it('Instance-level config overrides on a three-level hierarchy', t => {
66
+ const instance = Neo.create(TopLevelComponent, {
67
+ myConfig : 'instanceValue',
68
+ arrayConfig : [10, 11],
69
+ objectConfig: {a: 100, b: {c: 101}, g: 102}
70
+ });
71
+
72
+ t.is(instance.myConfig, 'instanceValue', 'myConfig should be the value from the instance config');
73
+ t.isDeeplyStrict(instance.arrayConfig, [10, 11], 'arrayConfig should be replaced by the instance config value');
74
+
75
+ const expectedObject = {
76
+ a: 100, // from instance
77
+ b: {c: 101, d: 99}, // c from instance, d from Top
78
+ e: 5, // from Mid
79
+ f: 6, // from Top
80
+ g: 102 // from instance
81
+ };
82
+
83
+ t.isDeeplyStrict(instance.objectConfig, expectedObject, 'objectConfig from instance should be deep-merged into the class hierarchy defaults');
84
+ });
85
+ });
@@ -0,0 +1,127 @@
1
+ import Neo from '../../../../src/Neo.mjs';
2
+ import * as core from '../../../../src/core/_export.mjs';
3
+ import Effect from '../../../../src/core/Effect.mjs';
4
+ import EffectManager from '../../../../src/core/EffectManager.mjs';
5
+ import Config from '../../../../src/core/Config.mjs';
6
+
7
+ StartTest(t => {
8
+ t.it('EffectManager should manage active effects', t => {
9
+ const effect1 = new Effect(() => {});
10
+ const effect2 = new Effect(() => {});
11
+
12
+ t.is(EffectManager.getActiveEffect(), null, 'No active effect initially');
13
+
14
+ EffectManager.push(effect1);
15
+ t.is(EffectManager.getActiveEffect(), effect1, 'Effect1 is active');
16
+
17
+ EffectManager.push(effect2);
18
+ t.is(EffectManager.getActiveEffect(), effect2, 'Effect2 is active');
19
+
20
+ EffectManager.pop();
21
+ t.is(EffectManager.getActiveEffect(), effect1, 'Effect1 is active after pop');
22
+
23
+ EffectManager.pop();
24
+ t.is(EffectManager.getActiveEffect(), null, 'No active effect after all pops');
25
+
26
+ effect1.destroy();
27
+ effect2.destroy();
28
+ });
29
+
30
+ t.it('Effect should run its function and track dependencies', t => {
31
+ let runCount = 0;
32
+ let sum = 0;
33
+ const configA = new Config(1);
34
+ const configB = new Config(10);
35
+
36
+ // Identity check
37
+ t.is(configA, configA, 'configA is strictly equal to itself');
38
+ t.is(configB, configB, 'configB is strictly equal to itself');
39
+ t.isNot(configA, configB, 'configA is not strictly equal to configB');
40
+
41
+ const effect = new Effect(() => {
42
+ runCount++;
43
+ // Access configs to register them as dependencies
44
+ sum = configA.get() + configB.get();
45
+ t.pass(`Effect ran. Sum: ${sum}`);
46
+ });
47
+
48
+ t.is(runCount, 1, 'Effect function ran once on creation');
49
+ t.is(effect.dependencies.size, 2, 'Effect tracked 2 dependencies');
50
+ t.is(sum, 11, 'Effect function ran with correct sum: 1 + 10 = 11');
51
+
52
+ // Change a dependency, effect should re-run
53
+ configA.set(2);
54
+ t.is(runCount, 2, 'Effect function ran again after configA change');
55
+ t.is(sum, 12, 'Effect function ran with correct sum: 2 + 10 = 12');
56
+
57
+ // Change another dependency, effect should re-run
58
+ configB.set(20);
59
+ t.is(runCount, 3, 'Effect function ran again after configB change');
60
+ t.is(sum, 22, 'Effect function ran with correct sum: 2 + 20 = 22');
61
+
62
+ // Change a dependency to the same value, effect should not re-run (Config handles this)
63
+ configA.set(2);
64
+ t.is(runCount, 3, 'Effect function did not run after no-change configA update');
65
+ t.is(sum, 22, 'Effect function ran with correct sum: 2 + 20 = 22');
66
+
67
+ effect.destroy();
68
+ t.is(effect.isDestroyed, true, 'Effect is destroyed');
69
+ t.is(effect.dependencies.size, 0, 'Effect dependencies cleared after destroy');
70
+
71
+ // Changing config after effect is destroyed should not re-run effect
72
+ configA.set(3);
73
+ t.is(runCount, 3, 'Effect function did not run after configA change when destroyed');
74
+ t.is(sum, 22, 'Effect function ran with correct sum: 2 + 20 = 22');
75
+ });
76
+
77
+ t.it('Effect should clean up old dependencies on re-run', t => {
78
+ let runCount = 0;
79
+ const configX = new Config('X');
80
+ const configY = new Config('Y');
81
+
82
+ // Initial effect: depends on configX
83
+ const effect = new Effect(() => {
84
+ runCount++;
85
+ t.is(configX.get(), 'X', 'Effect ran (1st): configX value');
86
+ });
87
+
88
+ t.is(runCount, 1, 'Effect ran once initially');
89
+ t.is(effect.dependencies.size, 1, 'Effect has 1 dependency (configX)');
90
+
91
+ // --- Transition to configY dependency ---
92
+ // Reassign the effect's function to depend on configY
93
+ effect.fn = () => {
94
+ runCount++;
95
+
96
+ if (runCount === 2) {
97
+ t.is(configY.get(), 'Y', 'Effect ran (2nd): configY value');
98
+ }
99
+
100
+ if (runCount === 3) {
101
+ t.is(configY.get(), 'Y_new', 'Effect ran (3rd): configY value');
102
+ }
103
+
104
+ else if (runCount === 4) {
105
+ t.is(configY.get(), 'Y_final', 'Effect ran (4th): configY value');
106
+ }
107
+ };
108
+
109
+ t.is(runCount, 2, 'Effect ran a second time after fn reassignment');
110
+
111
+ // Changing the config value will trigger a re-run.
112
+ configY.set('Y_new');
113
+
114
+ t.is(runCount, 3, 'Effect ran a second time after fn reassignment');
115
+ t.is(effect.dependencies.size, 1, 'Effect now has 1 dependency (configY)');
116
+
117
+ // Change configX: should NOT trigger the effect (old dependency cleaned up)
118
+ configX.set('X_new');
119
+ t.is(runCount, 3, 'Effect did not re-run after old dependency (configX) changed');
120
+
121
+ // Change configY: should trigger the effect (new dependency)
122
+ configY.set('Y_final'); // This will trigger runCount to become 3
123
+ t.is(runCount, 4, 'Effect re-ran after new dependency (configY) changed');
124
+
125
+ effect.destroy();
126
+ });
127
+ });