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
package/src/core/Base.mjs CHANGED
@@ -3,7 +3,8 @@ import Compare from '../core/Co
3
3
  import Util from '../core/Util.mjs';
4
4
  import Config from './Config.mjs';
5
5
  import {isDescriptor} from './ConfigSymbols.mjs';
6
- import IdGenerator from './IdGenerator.mjs'
6
+ import IdGenerator from './IdGenerator.mjs';
7
+ import EffectBatchManager from './EffectBatchManager.mjs';
7
8
 
8
9
  const configSymbol = Symbol.for('configSymbol'),
9
10
  forceAssignConfigs = Symbol('forceAssignConfigs'),
@@ -57,7 +58,26 @@ class Base {
57
58
  */
58
59
  static overwrittenMethods = {}
59
60
  /**
60
- * Configs will get merged throughout the class hierarchy
61
+ * Defines the default configuration properties for the class. These configurations are
62
+ * merged throughout the class hierarchy and can be overridden at the instance level.
63
+ *
64
+ * There are two main types of configs:
65
+ *
66
+ * 1. **Reactive Configs:** Property names ending with a trailing underscore (e.g., `myConfig_`).
67
+ * The framework automatically generates a public getter and setter, removing the underscore
68
+ * from the property name (e.g., `this.myConfig`). This system enables powerful, optional
69
+ * lifecycle hooks that are called automatically if they are implemented on the class:
70
+ * - `beforeGetMyConfig(value)`: Executed before the getter returns. Can be used to dynamically modify the returned value.
71
+ * - `beforeSetMyConfig(newValue, oldValue)`: Executed before a new value is set. Can be used for validation or transformation. Returning `undefined` from this hook will cancel the update.
72
+ * - `afterSetMyConfig(newValue, oldValue)`: Executed after a new value has been successfully set. Ideal for triggering side effects.
73
+ *
74
+ * 2. **Non-Reactive (Prototype-based) Configs:** Property names without a trailing underscore.
75
+ * These are applied directly to the class's **prototype** during the `Neo.setupClass`
76
+ * process. This is highly memory-efficient as the value is shared across all instances.
77
+ * It also allows for powerful, application-wide modifications of default behaviors
78
+ * by using the `Neo.overwrites` mechanism, which modifies these prototype values at
79
+ * load time.
80
+ *
61
81
  * @returns {Object} config
62
82
  */
63
83
  static config = {
@@ -82,6 +102,7 @@ class Base {
82
102
  /**
83
103
  * The unique component id
84
104
  * @member {String|null} id_=null
105
+ * @reactive
85
106
  */
86
107
  id_: null,
87
108
  /**
@@ -98,12 +119,21 @@ class Base {
98
119
  * @protected
99
120
  */
100
121
  isConstructed: false,
122
+ /**
123
+ * This config will be set to `true` as the very first action within the `destroy()` method.
124
+ * Effects can observe this config to clean themselves up.
125
+ * @member {Boolean} isDestroying_=false
126
+ * @protected
127
+ * @reactive
128
+ */
129
+ isDestroying_: false,
101
130
  /**
102
131
  * The config will get set to `true` once the Promise of `async initAsync()` is resolved.
103
132
  * You can use `afterSetIsReady()` to get notified once the ready state is reached.
104
133
  * Since not all classes use the Observable mixin, Neo will not fire an event.
105
134
  * method body.
106
- * @member {Boolean} isReady=false
135
+ * @member {Boolean} isReady_=false
136
+ * @reactive
107
137
  */
108
138
  isReady_: false,
109
139
  /**
@@ -125,6 +155,7 @@ class Base {
125
155
  *
126
156
  * @member {Object|null} remote_=null
127
157
  * @protected
158
+ * @reactive
128
159
  */
129
160
  remote_: null
130
161
  }
@@ -135,6 +166,12 @@ class Base {
135
166
  * @private
136
167
  */
137
168
  #configs = {};
169
+ /**
170
+ * Internal cache for all config subscription cleanup functions.
171
+ * @member {Function[]} #configSubscriptionCleanups=[]
172
+ * @private
173
+ */
174
+ #configSubscriptionCleanups = []
138
175
  /**
139
176
  * Internal cache for all timeout ids when using this.timeout()
140
177
  * @member {Number[]} timeoutIds=[]
@@ -239,7 +276,7 @@ class Base {
239
276
  if (oldValue) {
240
277
  if (hasManager) {
241
278
  Neo.manager.Instance.unregister(oldValue)
242
- } else {
279
+ } else if (Neo.idMap) {
243
280
  delete Neo.idMap[oldValue]
244
281
  }
245
282
  }
@@ -249,7 +286,7 @@ class Base {
249
286
  Neo.manager.Instance.register(me);
250
287
  } else {
251
288
  Neo.idMap ??= {};
252
- Neo.idMap[me.id] = me
289
+ Neo.idMap[value] = me
253
290
  }
254
291
  }
255
292
  }
@@ -276,9 +313,38 @@ class Base {
276
313
  }
277
314
 
278
315
  /**
279
- * Applying overwrites and adding overwrittenMethods to the class constructors
280
- * @param {Object} cfg
316
+ * This static method is called by `Neo.setupClass()` during the class creation process.
317
+ * It allows for modifying a class's default prototype-based configs from outside the
318
+ * class hierarchy, which is a powerful way to avoid boilerplate code.
319
+ *
320
+ * It looks for a matching entry in the global `Neo.overwrites` object based on the
321
+ * class's `className`. If found, it merges the properties from the overwrite object
322
+ * into the class's static `config`. This provides a powerful mechanism for theming
323
+ * or applying application-wide customizations to framework or library classes without
324
+ * needing to extend them.
325
+ *
326
+ * @example
327
+ * // Imagine you have hundreds of buttons in your app, and you want all of them
328
+ * // to have `labelPosition: 'top'` instead of the default `'left'`.
329
+ * // Instead of configuring each instance, you can define an overwrite.
330
+ *
331
+ * // inside an Overwrites.mjs file loaded by your app:
332
+ * Neo.overwrites = {
333
+ * Neo: {
334
+ * button: {
335
+ * Base: {
336
+ * labelPosition: 'top'
337
+ * }
338
+ * }
339
+ * }
340
+ * };
341
+ *
342
+ * // Now, every `Neo.button.Base` (and any class that extends it) will have this
343
+ * // new default value on its prototype.
344
+ *
345
+ * @param {Object} cfg The static `config` object of the class being processed.
281
346
  * @protected
347
+ * @static
282
348
  */
283
349
  static applyOverwrites(cfg) {
284
350
  let overwrites = Neo.ns(cfg.className, false, Neo.overwrites),
@@ -386,10 +452,16 @@ class Base {
386
452
  destroy() {
387
453
  let me = this;
388
454
 
455
+ me.isDestroying = true;
456
+
389
457
  me.#timeoutIds.forEach(id => {
390
458
  clearTimeout(id)
391
459
  });
392
460
 
461
+ me.#configSubscriptionCleanups.forEach(cleanup => {
462
+ cleanup()
463
+ });
464
+
393
465
  if (Base.instanceManagerAvailable === true) {
394
466
  Neo.manager.Instance.unregister(me)
395
467
  } else if (Neo.idMap) {
@@ -423,7 +495,7 @@ class Base {
423
495
  let me = this;
424
496
 
425
497
  if (!me.#configs[key] && me.isConfig(key)) {
426
- me.#configs[key] = new Config()
498
+ me.#configs[key] = new Config(me.constructor.configDescriptors?.[key])
427
499
  }
428
500
 
429
501
  return me.#configs[key]
@@ -488,7 +560,7 @@ class Base {
488
560
  Object.assign(me[configSymbol], me.mergeConfig(config, preventOriginalConfig));
489
561
  delete me[configSymbol].id;
490
562
  me.processConfigs();
491
- me.isConfiguring = false;
563
+ me.isConfiguring = false
492
564
  }
493
565
 
494
566
  /**
@@ -524,10 +596,12 @@ class Base {
524
596
  * @returns {Boolean}
525
597
  */
526
598
  isConfig(key) {
527
- // A config is considered "reactive" if it has a generated property setter
599
+ let me = this;
600
+ // If a `core.Config` controller is already created, return true (fastest possible check).
601
+ // If not, a config is considered "reactive" if it has a generated property setter
528
602
  // AND it is present as a defined config in the merged static config hierarchy.
529
603
  // Neo.setupClass() removes the underscore from the static config keys.
530
- return Neo.hasPropertySetter(this, key) && (key in this.constructor.config);
604
+ return me.#configs[key] || (Neo.hasPropertySetter(me, key) && (key in me.constructor.config))
531
605
  }
532
606
 
533
607
  /**
@@ -539,7 +613,8 @@ class Base {
539
613
  */
540
614
  mergeConfig(config, preventOriginalConfig) {
541
615
  let me = this,
542
- ctor = me.constructor;
616
+ ctor = me.constructor,
617
+ configDescriptors, staticConfig;
543
618
 
544
619
  if (!ctor.config) {
545
620
  throw new Error('Neo.applyClassConfig has not been run on ' + me.className)
@@ -549,7 +624,70 @@ class Base {
549
624
  me.originalConfig = Neo.clone(config, true, true)
550
625
  }
551
626
 
552
- return {...ctor.config, ...config}
627
+ configDescriptors = ctor.configDescriptors;
628
+ staticConfig = ctor.config;
629
+
630
+ if (configDescriptors) {
631
+ Object.entries(config).forEach(([key, instanceValue]) => {
632
+ const descriptor = configDescriptors[key];
633
+
634
+ if (descriptor?.merge) {
635
+ config[key] = Neo.mergeConfig(staticConfig[key], instanceValue, descriptor.merge)
636
+ }
637
+ })
638
+ }
639
+
640
+ return {...staticConfig, ...config}
641
+ }
642
+
643
+ /**
644
+ * Subscribes *this* instance (the subscriber) to changes of a specific config property on another instance (the publisher).
645
+ * Ensures automatic cleanup when *this* instance (the subscriber) is destroyed.
646
+ *
647
+ * @param {String|Neo.core.Base} publisher - The ID of the publisher instance or the instance reference itself.
648
+ * @param {String} configName - The name of the config property on the publisher to subscribe to (e.g., 'myConfig').
649
+ * @param {Function} fn - The callback function to execute when the config changes.
650
+ * @returns {Function} A cleanup function to manually unsubscribe if needed before this instance's destruction.
651
+ *
652
+ * @example
653
+ * // Subscribing to a config on another instance
654
+ * this.observeConfig(someOtherInstance, 'myConfig', (newValue, oldValue) => {
655
+ * console.log('myConfig changed:', newValue);
656
+ * });
657
+ *
658
+ * // Discouraged: Self-observation. Use afterSet<ConfigName>() hooks instead.
659
+ * this.observeConfig(this, 'myOwnConfig', (newValue, oldValue) => {
660
+ * console.log('myOwnConfig changed:', newValue);
661
+ * });
662
+ */
663
+ observeConfig(publisher, configName, fn) {
664
+ let publisherInstance = publisher;
665
+
666
+ if (Neo.isString(publisher)) {
667
+ publisherInstance = Neo.get(publisher);
668
+ if (!publisherInstance) {
669
+ console.warn(`Publisher instance with ID '${publisher}' not found. Cannot subscribe.`);
670
+ return Neo.emptyFn
671
+ }
672
+ }
673
+
674
+ if (!(publisherInstance instanceof Neo.core.Base)) {
675
+ console.warn(`Invalid publisher provided. Must be a Neo.core.Base instance or its ID.`);
676
+ return Neo.emptyFn
677
+ }
678
+
679
+ const configController = publisherInstance.getConfig(configName);
680
+
681
+ if (!configController) {
682
+ console.warn(`Config '${configName}' not found on publisher instance ${publisherInstance.id}. Cannot subscribe.`);
683
+ return Neo.emptyFn
684
+ }
685
+
686
+ const cleanup = configController.subscribe({id: this.id, fn});
687
+
688
+ this.#configSubscriptionCleanups.push(cleanup);
689
+
690
+ return cleanup
553
691
  }
554
692
 
555
693
  /**
@@ -670,24 +808,58 @@ class Base {
670
808
  }
671
809
 
672
810
  /**
673
- * Change multiple configs at once, ensuring that all afterSet methods get all new assigned values
811
+ * set() accepts the following input as keys:
812
+ * 1. Non-reactive configs
813
+ * 2. Reactive configs
814
+ * 3. Class fields defined via value
815
+ * 4. Class fields defined via get() & set()
816
+ * 5. "Anything else" will get directly get assigned to the instance
817
+ *
818
+ * The logic resolves circular dependencies as good as possible and ensures that config related hooks:
819
+ * - beforeGet<Config>
820
+ * - beforeSet<Config>
821
+ * - afterSet<Config>
822
+ * can access all new values from the batch operation.
674
823
  * @param {Object} values={}
675
824
  */
676
825
  set(values={}) {
677
- let me = this;
826
+ let me = this,
827
+ classFieldsViaSet = {};
828
+
829
+ // Start batching for effects
830
+ EffectBatchManager.startBatch();
678
831
 
679
832
  values = me.setFields(values);
680
833
 
681
834
  // If the initial config processing is still running,
682
835
  // finish this one first before dropping new values into the configSymbol.
683
- // see: https://github.com/neomjs/neo/issues/2201
836
+ // See: https://github.com/neomjs/neo/issues/2201
684
837
  if (me[forceAssignConfigs] !== true && Object.keys(me[configSymbol]).length > 0) {
685
838
  me.processConfigs()
686
839
  }
687
840
 
841
+ // Store class fields which are defined via get() & set() and ensure they won't get added to the config symbol.
842
+ Object.entries(values).forEach(([key, value]) => {
843
+ if (!me.isConfig(key)) {
844
+ classFieldsViaSet[key] = value;
845
+ delete values[key]
846
+ }
847
+ })
848
+
849
+ // Add reactive configs to the configSymbol
688
850
  Object.assign(me[configSymbol], values);
689
851
 
690
- me.processConfigs(true)
852
+ // Process class fields which are defined via get() & set() => now they can access the latest values
853
+ // for reactive and non-reactive configs, as well as class fields defined with values.
854
+ Object.entries(classFieldsViaSet).forEach(([key, value]) => {
855
+ me[key] = value
856
+ })
857
+
858
+ // Process reactive configs
859
+ me.processConfigs(true);
860
+
861
+ // End batching for effects
862
+ EffectBatchManager.endBatch();
691
863
  }
692
864
 
693
865
  /**
@@ -698,15 +870,14 @@ class Base {
698
870
  * @protected
699
871
  */
700
872
  setFields(config) {
701
- let me = this,
702
- configNames = me.constructor.config;
873
+ let me = this;
703
874
 
704
875
  Object.entries(config).forEach(([key, value]) => {
705
- if (!configNames.hasOwnProperty(key) && !Neo.hasPropertySetter(me, key)) {
876
+ if (!me.isConfig(key) && !Neo.hasPropertySetter(me, key)) {
706
877
  me[key] = value;
707
878
  delete config[key]
708
879
  }
709
- })
880
+ });
710
881
 
711
882
  return config
712
883
  }
@@ -748,7 +919,7 @@ class Base {
748
919
  * @returns {String}
749
920
  */
750
921
  get [Symbol.toStringTag]() {
751
- return `${this.className} (id: ${this.id})`
922
+ return this.className
752
923
  }
753
924
 
754
925
  /**
@@ -163,10 +163,7 @@ class Compare {
163
163
  }
164
164
  }
165
165
 
166
- const ns = Neo.ns('Neo.core', true);
167
- ns.Compare = Compare;
168
-
169
- // alias
170
- Neo.isEqual = Compare.isEqual;
171
-
172
- export default Compare;
166
+ export default Neo.gatekeep(Compare, 'Neo.core.Compare', () => {
167
+ // alias
168
+ Neo.isEqual = Compare.isEqual
169
+ });
@@ -1,45 +1,65 @@
1
+ import EffectManager from './EffectManager.mjs';
1
2
  import {isDescriptor} from './ConfigSymbols.mjs';
2
3
 
3
4
  /**
4
- * @src/util/ClassSystem.mjs Neo.core.Config
5
- * @private
6
- * @internal
7
- *
8
5
  * Represents an observable container for a config property.
9
6
  * This class manages the value of a config, its subscribers, and custom behaviors
10
7
  * like merge strategies and equality checks defined via a descriptor object.
11
8
  *
12
9
  * The primary purpose of this class is to enable fine-grained reactivity and
13
10
  * decoupled cross-instance state sharing within the Neo.mjs framework.
11
+ * @class Neo.core.Config
12
+ * @private
13
+ * @internal
14
14
  */
15
15
  class Config {
16
16
  /**
17
- * The internal value of the config property.
17
+ * Stores all subscriptions for this Config instance.
18
+ * The data structure is a Map where:
19
+ * - The key is the ID of the subscription owner (e.g., a component's `id`).
20
+ * - The value is another Map (the subscriberMap).
21
+ *
22
+ * The nested subscriberMap is structured as:
23
+ * - The key is the callback function (`fn`).
24
+ * - The value is a Set of scopes (`scopeSet`).
25
+ *
26
+ * This nested structure `Map<string, Map<function, Set<scope>>>` is intentionally chosen
27
+ * to robustly handle the edge case where the same function is subscribed multiple times
28
+ * with different scopes, all under the same owner ID. It ensures that each
29
+ * `fn`-`scope` combination is unique and that cleanup is precise.
30
+ * @member {Object} #subscribers={}
18
31
  * @private
19
- * @apps/portal/view/about/MemberContainer.mjs {any} #value
20
32
  */
21
- #value;
22
-
33
+ #subscribers = {}
23
34
  /**
24
- * A Set to store callback functions that subscribe to changes in this config's value.
35
+ * The internal value of the config property.
36
+ * @member {*} #value
25
37
  * @private
26
- * @apps/portal/view/about/MemberContainer.mjs {Set<Function>} #subscribers
27
38
  */
28
- #subscribers = new Set();
39
+ #value
40
+ /**
41
+ * The cloning strategy to use when setting a new value.
42
+ * Supported values: 'deep', 'shallow', 'none'.
43
+ * @member {String} clone='deep'
44
+ */
29
45
 
30
46
  /**
31
- * The strategy to use when merging new values into this config.
32
- * Defaults to 'deep'. Can be overridden via a descriptor.
33
- * @apps/portal/view/about/MemberContainer.mjs {string} mergeStrategy
47
+ * The cloning strategy to use when getting a value.
48
+ * Supported values: 'deep', 'shallow', 'none'.
49
+ * @member {String} cloneOnGet=null
34
50
  */
35
- mergeStrategy = 'deep';
36
51
 
37
52
  /**
38
53
  * The function used to compare new and old values for equality.
39
54
  * Defaults to `Neo.isEqual`. Can be overridden via a descriptor.
40
- * @apps/portal/view/about/MemberContainer.mjs {Function} isEqual
55
+ * @member {Function} isEqual=Neo.isEqual
56
+ */
57
+
58
+ /**
59
+ * The strategy to use when merging new values into this config.
60
+ * Defaults to 'replace'. Can be overridden via a descriptor merge property.
61
+ * @member {Function|String} mergeStrategy='replace'
41
62
  */
42
- isEqual = Neo.isEqual;
43
63
 
44
64
  /**
45
65
  * Creates an instance of Config.
@@ -58,34 +78,68 @@ class Config {
58
78
  * @returns {any} The current value.
59
79
  */
60
80
  get() {
61
- return this.#value;
81
+ // Registers this Config instance as a dependency with the currently active Effect,
82
+ // enabling automatic re-execution when this Config's value changes.
83
+ EffectManager.addDependency(this);
84
+ return this.#value
62
85
  }
63
86
 
64
87
  /**
65
88
  * Initializes the `Config` instance using a descriptor object.
66
- * Extracts `mergeStrategy` and `isEqual` from the descriptor.
89
+ * Extracts `clone`, `mergeStrategy` and `isEqual` from the descriptor.
67
90
  * The internal `#value` is NOT set by this method.
68
- * @param {Object} descriptor - The descriptor object for the config.
69
- * @param {any} descriptor.value - The default value for the config (not set by this method).
70
- * @param {string} [descriptor.merge='deep'] - The merge strategy.
91
+ * @param {Object} descriptor - The descriptor object for the config.
92
+ * @param {string} [descriptor.clone='deep'] - The clone strategy for set.
93
+ * @param {string} [descriptor.cloneOnGet] - The clone strategy for get. Defaults to 'shallow' if clone is 'deep' or 'shallow', and 'none' if clone is 'none'.
71
94
  * @param {Function} [descriptor.isEqual=Neo.isEqual] - The equality comparison function.
95
+ * @param {string} [descriptor.merge='deep'] - The merge strategy.
96
+ * @param {any} descriptor.value - The default value for the config (not set by this method).
72
97
  */
73
- initDescriptor({isEqual, merge, value}) {
98
+ initDescriptor({clone, cloneOnGet, isEqual, merge}) {
74
99
  let me = this;
75
100
 
76
- me.#value = value
77
- me.mergeStrategy = merge || me.mergeStrategy;
78
- me.isEqual = isEqual || me.isEqual;
101
+ if (clone && clone !== me.clone) {
102
+ Object.defineProperty(me, 'clone', {
103
+ configurable: true, enumerable: true, value: clone, writable: true
104
+ })
105
+ }
106
+
107
+ if (cloneOnGet && cloneOnGet !== me.cloneOnGet) {
108
+ Object.defineProperty(me, 'cloneOnGet', {
109
+ configurable: true, enumerable: true, value: cloneOnGet, writable: true
110
+ })
111
+ }
112
+
113
+ if (isEqual && isEqual !== me.isEqual) {
114
+ Object.defineProperty(me, 'isEqual', {
115
+ configurable: true, enumerable: true, value: isEqual, writable: true
116
+ })
117
+ }
118
+
119
+ if (merge && merge !== me.mergeStrategy) {
120
+ Object.defineProperty(me, 'mergeStrategy', {
121
+ configurable: true, enumerable: true, value: merge, writable: true
122
+ })
123
+ }
79
124
  }
80
125
 
81
126
  /**
82
127
  * Notifies all subscribed callbacks about a change in the config's value.
128
+ * It iterates through the nested subscriber structure to ensure each callback
129
+ * is executed with its intended scope.
83
130
  * @param {any} newValue - The new value of the config.
84
131
  * @param {any} oldValue - The old value of the config.
85
132
  */
86
133
  notify(newValue, oldValue) {
87
- for (const callback of this.#subscribers) {
88
- callback(newValue, oldValue);
134
+ for (const id in this.#subscribers) {
135
+ if (this.#subscribers.hasOwnProperty(id)) {
136
+ const subscriberMap = this.#subscribers[id];
137
+ for (const [fn, scopeSet] of subscriberMap) {
138
+ for (const scope of scopeSet) {
139
+ fn.call(scope || null, newValue, oldValue)
140
+ }
141
+ }
142
+ }
89
143
  }
90
144
  }
91
145
 
@@ -127,13 +181,63 @@ class Config {
127
181
  /**
128
182
  * Subscribes a callback function to changes in this config's value.
129
183
  * The callback will be invoked with `(newValue, oldValue)` whenever the config changes.
130
- * @param {Function} callback - The function to call when the config value changes.
184
+ * @param {Object} options - An object containing the subscription details.
185
+ * @param {String} options.id - The ID of the subscription owner (e.g., a Neo.core.Base instance's id).
186
+ * @param {Function} options.fn - The callback function.
187
+ * @param {Object} [options.scope] - The scope to execute the callback in.
131
188
  * @returns {Function} A cleanup function to unsubscribe the callback.
132
189
  */
133
- subscribe(callback) {
134
- this.#subscribers.add(callback);
135
- return () => this.#subscribers.delete(callback)
190
+ subscribe({id, fn, scope}) {
191
+ if (typeof id !== 'string' || id.length === 0 || typeof fn !== 'function') {
192
+ throw new Error([
193
+ 'Config.subscribe: options must be an object with a non-empty string `id` ',
194
+ '(the subscription owner\'s id), and a callback function `fn`.'
195
+ ].join(''))
196
+ }
197
+
198
+ const me = this;
199
+
200
+ // Get or create the top-level Map for the subscription owner.
201
+ if (!me.#subscribers[id]) {
202
+ me.#subscribers[id] = new Map()
203
+ }
204
+
205
+ const subscriberMap = me.#subscribers[id];
206
+
207
+ // Get or create the Set of scopes for the specific callback function.
208
+ if (!subscriberMap.has(fn)) {
209
+ subscriberMap.set(fn, new Set())
210
+ }
211
+
212
+ const scopeSet = subscriberMap.get(fn);
213
+ scopeSet.add(scope);
214
+
215
+ // The returned cleanup function is precise. It removes only the specific
216
+ // scope for the function, and cleans up the parent data structures
217
+ // (the Set and the Maps) only if they become empty.
218
+ return () => {
219
+ const currentSubscriberMap = me.#subscribers[id];
220
+ if (currentSubscriberMap) {
221
+ const currentScopeSet = currentSubscriberMap.get(fn);
222
+ if (currentScopeSet) {
223
+ currentScopeSet.delete(scope);
224
+ if (currentScopeSet.size === 0) {
225
+ currentSubscriberMap.delete(fn);
226
+ if (currentSubscriberMap.size === 0) {
227
+ delete me.#subscribers[id]
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
136
233
  }
137
234
  }
138
235
 
139
- export default Config;
236
+ Object.defineProperties(Config.prototype, {
237
+ clone : {configurable: true, enumerable: false, value: 'deep', writable: false},
238
+ cloneOnGet : {configurable: true, enumerable: false, value: null, writable: false},
239
+ isEqual : {configurable: true, enumerable: false, value: Neo.isEqual, writable: false},
240
+ mergeStrategy: {configurable: true, enumerable: false, value: 'replace', writable: false}
241
+ });
242
+
243
+ export default Neo.gatekeep(Config, 'Neo.core.Config');