neo.mjs 10.0.0-beta.5 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.1.md +20 -0
  2. package/.github/RELEASE_NOTES/v10.0.0-beta.2.md +73 -0
  3. package/.github/RELEASE_NOTES/v10.0.0-beta.3.md +39 -0
  4. package/.github/RELEASE_NOTES/v10.0.0-beta.5.md +70 -0
  5. package/.github/RELEASE_NOTES/v10.0.0-beta.6.md +48 -0
  6. package/.github/RELEASE_NOTES/v10.0.0.md +52 -0
  7. package/.github/epic-functional-components.md +498 -0
  8. package/.github/ticket-asymmetric-vdom-updates.md +122 -0
  9. package/README.md +0 -3
  10. package/ServiceWorker.mjs +2 -2
  11. package/apps/colors/store/Colors.mjs +1 -0
  12. package/apps/colors/view/GridContainer.mjs +3 -0
  13. package/apps/colors/view/HeaderToolbar.mjs +2 -0
  14. package/apps/colors/view/Viewport.mjs +3 -0
  15. package/apps/covid/view/FooterContainer.mjs +3 -0
  16. package/apps/covid/view/GalleryContainer.mjs +2 -0
  17. package/apps/covid/view/GalleryContainerController.mjs +1 -0
  18. package/apps/covid/view/HeaderContainer.mjs +2 -0
  19. package/apps/covid/view/HelixContainer.mjs +2 -0
  20. package/apps/covid/view/HelixContainerController.mjs +1 -0
  21. package/apps/covid/view/MainContainer.mjs +3 -0
  22. package/apps/covid/view/TableContainer.mjs +3 -0
  23. package/apps/covid/view/TableContainerController.mjs +1 -0
  24. package/apps/covid/view/WorldMapContainer.mjs +2 -0
  25. package/apps/covid/view/country/Gallery.mjs +3 -0
  26. package/apps/covid/view/country/Helix.mjs +8 -0
  27. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -0
  28. package/apps/covid/view/country/Table.mjs +2 -0
  29. package/apps/covid/view/mapboxGl/Component.mjs +1 -0
  30. package/apps/covid/view/mapboxGl/Container.mjs +2 -0
  31. package/apps/email/EPIC_PLAN.md +58 -0
  32. package/apps/email/neo-config.json +2 -2
  33. package/apps/email/store/Emails.mjs +11 -1
  34. package/apps/email/view/ComposeView.mjs +44 -0
  35. package/apps/email/view/MainView.mjs +89 -0
  36. package/apps/email/view/Viewport.mjs +4 -33
  37. package/apps/email/view/ViewportStateProvider.mjs +3 -3
  38. package/apps/form/store/SideNav.mjs +1 -0
  39. package/apps/form/view/FormContainer.mjs +1 -0
  40. package/apps/form/view/FormPageContainer.mjs +2 -0
  41. package/apps/form/view/SideNavList.mjs +1 -0
  42. package/apps/form/view/Viewport.mjs +3 -0
  43. package/apps/portal/childapps/preview/MainContainer.mjs +1 -0
  44. package/apps/portal/index.html +1 -1
  45. package/apps/portal/store/BlogPosts.mjs +2 -0
  46. package/apps/portal/store/Content.mjs +1 -0
  47. package/apps/portal/store/ContentSections.mjs +1 -0
  48. package/apps/portal/store/Examples.mjs +1 -0
  49. package/apps/portal/view/HeaderToolbar.mjs +1 -0
  50. package/apps/portal/view/Viewport.mjs +5 -0
  51. package/apps/portal/view/ViewportController.mjs +11 -3
  52. package/apps/portal/view/about/Container.mjs +2 -0
  53. package/apps/portal/view/about/MemberContainer.mjs +7 -0
  54. package/apps/portal/view/blog/Container.mjs +2 -0
  55. package/apps/portal/view/blog/List.mjs +2 -0
  56. package/apps/portal/view/examples/List.mjs +29 -19
  57. package/apps/portal/view/examples/TabContainer.mjs +4 -0
  58. package/apps/portal/view/home/ContentBox.mjs +3 -0
  59. package/apps/portal/view/home/FeatureSection.mjs +8 -0
  60. package/apps/portal/view/home/FooterContainer.mjs +4 -1
  61. package/apps/portal/view/home/MainContainer.mjs +2 -0
  62. package/apps/portal/view/home/parts/AfterMath.mjs +2 -0
  63. package/apps/portal/view/home/parts/BaseContainer.mjs +1 -0
  64. package/apps/portal/view/home/parts/Colors.mjs +4 -0
  65. package/apps/portal/view/home/parts/Features.mjs +2 -0
  66. package/apps/portal/view/home/parts/Helix.mjs +5 -0
  67. package/apps/portal/view/home/parts/How.mjs +4 -0
  68. package/apps/portal/view/home/parts/MainNeo.mjs +1 -0
  69. package/apps/portal/view/home/parts/References.mjs +2 -0
  70. package/apps/portal/view/learn/ContentComponent.mjs +11 -5
  71. package/apps/portal/view/learn/ContentTreeList.mjs +2 -0
  72. package/apps/portal/view/learn/CubeLayoutButton.mjs +1 -0
  73. package/apps/portal/view/learn/MainContainer.mjs +4 -0
  74. package/apps/portal/view/learn/PageContainer.mjs +2 -0
  75. package/apps/portal/view/learn/PageSectionsContainer.mjs +3 -0
  76. package/apps/portal/view/learn/PageSectionsList.mjs +1 -0
  77. package/apps/portal/view/services/Component.mjs +1 -0
  78. package/apps/realworld/api/Base.mjs +1 -0
  79. package/apps/realworld/view/HeaderComponent.mjs +4 -0
  80. package/apps/realworld/view/HomeComponent.mjs +7 -0
  81. package/apps/realworld/view/MainContainer.mjs +2 -0
  82. package/apps/realworld/view/MainContainerController.mjs +2 -0
  83. package/apps/realworld/view/article/CommentComponent.mjs +3 -0
  84. package/apps/realworld/view/article/Component.mjs +17 -10
  85. package/apps/realworld/view/article/CreateCommentComponent.mjs +2 -0
  86. package/apps/realworld/view/article/CreateComponent.mjs +5 -0
  87. package/apps/realworld/view/article/PreviewComponent.mjs +9 -0
  88. package/apps/realworld/view/article/TagListComponent.mjs +2 -0
  89. package/apps/realworld/view/user/ProfileComponent.mjs +7 -0
  90. package/apps/realworld/view/user/SettingsComponent.mjs +5 -0
  91. package/apps/realworld/view/user/SignUpComponent.mjs +3 -0
  92. package/apps/realworld2/api/Base.mjs +1 -0
  93. package/apps/realworld2/view/FooterComponent.mjs +1 -0
  94. package/apps/realworld2/view/HeaderToolbar.mjs +3 -0
  95. package/apps/realworld2/view/HomeContainer.mjs +1 -0
  96. package/apps/realworld2/view/MainContainer.mjs +2 -0
  97. package/apps/realworld2/view/MainContainerController.mjs +1 -0
  98. package/apps/realworld2/view/article/Helix.mjs +1 -0
  99. package/apps/realworld2/view/article/PreviewComponent.mjs +9 -0
  100. package/apps/realworld2/view/article/PreviewList.mjs +1 -0
  101. package/apps/realworld2/view/article/TagListComponent.mjs +2 -0
  102. package/apps/route/view/CenterContainer.mjs +1 -0
  103. package/apps/route/view/MainView.mjs +1 -0
  104. package/apps/sharedcovid/childapps/sharedcovidchart/MainContainer.mjs +1 -0
  105. package/apps/sharedcovid/childapps/sharedcovidgallery/MainContainer.mjs +1 -0
  106. package/apps/sharedcovid/childapps/sharedcovidhelix/MainContainer.mjs +1 -0
  107. package/apps/sharedcovid/childapps/sharedcovidmap/MainContainer.mjs +1 -0
  108. package/apps/sharedcovid/view/FooterContainer.mjs +3 -0
  109. package/apps/sharedcovid/view/GalleryContainer.mjs +2 -0
  110. package/apps/sharedcovid/view/GalleryContainerController.mjs +1 -0
  111. package/apps/sharedcovid/view/HeaderContainer.mjs +2 -0
  112. package/apps/sharedcovid/view/HelixContainer.mjs +2 -0
  113. package/apps/sharedcovid/view/HelixContainerController.mjs +1 -0
  114. package/apps/sharedcovid/view/MainContainer.mjs +3 -0
  115. package/apps/sharedcovid/view/TableContainer.mjs +3 -0
  116. package/apps/sharedcovid/view/TableContainerController.mjs +1 -0
  117. package/apps/sharedcovid/view/WorldMapContainer.mjs +2 -0
  118. package/apps/sharedcovid/view/country/Gallery.mjs +3 -0
  119. package/apps/sharedcovid/view/country/Helix.mjs +8 -0
  120. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -0
  121. package/apps/sharedcovid/view/country/Table.mjs +2 -0
  122. package/apps/sharedcovid/view/mapboxGl/Component.mjs +1 -0
  123. package/apps/sharedcovid/view/mapboxGl/Container.mjs +2 -0
  124. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +2 -0
  125. package/apps/shareddialog/view/DemoDialog.mjs +2 -0
  126. package/apps/shareddialog/view/MainContainer.mjs +2 -0
  127. package/apps/shareddialog/view/MainContainerController.mjs +1 -0
  128. package/buildScripts/addReactiveTags.mjs +191 -0
  129. package/buildScripts/checkReactiveTags.mjs +160 -0
  130. package/docs/app/store/Api.mjs +1 -0
  131. package/docs/app/store/Examples.mjs +1 -0
  132. package/docs/app/view/ApiTreeList.mjs +1 -0
  133. package/docs/app/view/ContentTabContainer.mjs +2 -0
  134. package/docs/app/view/ExamplesTreeList.mjs +2 -0
  135. package/docs/app/view/HeaderContainer.mjs +3 -0
  136. package/docs/app/view/MainContainer.mjs +5 -0
  137. package/docs/app/view/classdetails/HeaderComponent.mjs +1 -0
  138. package/docs/app/view/classdetails/MainContainer.mjs +3 -0
  139. package/docs/app/view/classdetails/MembersList.mjs +5 -0
  140. package/docs/app/view/classdetails/SourceViewComponent.mjs +2 -0
  141. package/examples/ConfigurationViewport.mjs +14 -8
  142. package/examples/calendar/weekview/MainContainer.mjs +4 -0
  143. package/examples/component/coronaGallery/CountryGallery.mjs +2 -0
  144. package/examples/component/coronaGallery/CountryStore.mjs +1 -0
  145. package/examples/component/coronaGallery/Viewport.mjs +3 -0
  146. package/examples/component/coronaGallery/ViewportController.mjs +1 -0
  147. package/examples/component/coronaHelix/CountryHelix.mjs +7 -0
  148. package/examples/component/coronaHelix/MainContainer.mjs +1 -0
  149. package/examples/component/gallery/ImageStore.mjs +1 -0
  150. package/examples/component/helix/ImageStore.mjs +1 -0
  151. package/examples/component/helix/Viewport.mjs +3 -0
  152. package/examples/component/helix/ViewportController.mjs +1 -0
  153. package/examples/component/multiWindowCoronaGallery/childapp/Viewport.mjs +1 -0
  154. package/examples/component/multiWindowHelix/childapp/Viewport.mjs +1 -0
  155. package/examples/component/wrapper/googleMaps/MapComponent.mjs +2 -0
  156. package/examples/core/config/MainContainer.mjs +2 -0
  157. package/examples/dialog/DemoDialog.mjs +2 -0
  158. package/examples/dialog/MainContainer.mjs +1 -0
  159. package/examples/form/field/color/MainStore.mjs +1 -0
  160. package/examples/functional/button/base/MainContainer.mjs +207 -0
  161. package/examples/functional/button/base/app.mjs +6 -0
  162. package/examples/functional/button/base/index.html +11 -0
  163. package/examples/functional/button/base/neo-config.json +6 -0
  164. package/examples/functional/defineComponent/Component.mjs +18 -0
  165. package/examples/functional/defineComponent/MainContainer.mjs +41 -0
  166. package/examples/functional/defineComponent/app.mjs +6 -0
  167. package/examples/functional/defineComponent/index.html +11 -0
  168. package/examples/functional/defineComponent/neo-config.json +6 -0
  169. package/examples/functional/hostComponent/Component.mjs +32 -0
  170. package/examples/functional/hostComponent/MainContainer.mjs +48 -0
  171. package/examples/functional/hostComponent/app.mjs +6 -0
  172. package/examples/functional/hostComponent/index.html +11 -0
  173. package/examples/functional/hostComponent/neo-config.json +6 -0
  174. package/examples/grid/animatedRowSorting/Viewport.mjs +1 -1
  175. package/examples/grid/bigData/ControlsContainer.mjs +3 -0
  176. package/examples/grid/bigData/GridContainer.mjs +4 -2
  177. package/examples/grid/bigData/MainContainer.mjs +2 -0
  178. package/examples/grid/bigData/MainModel.mjs +1 -0
  179. package/examples/grid/bigData/MainStore.mjs +3 -0
  180. package/examples/grid/cellEditing/MainContainer.mjs +1 -1
  181. package/examples/grid/container/MainContainer.mjs +1 -1
  182. package/examples/grid/covid/GridContainer.mjs +3 -0
  183. package/examples/grid/covid/MainContainer.mjs +2 -0
  184. package/examples/grid/covid/Store.mjs +1 -0
  185. package/examples/grid/nestedRecordFields/EditUserDialog.mjs +3 -0
  186. package/examples/grid/nestedRecordFields/Viewport.mjs +3 -1
  187. package/examples/list/animate/List.mjs +4 -0
  188. package/examples/list/animate/MainContainer.mjs +2 -0
  189. package/examples/list/circle/MainStore.mjs +1 -0
  190. package/examples/list/color/MainStore.mjs +1 -0
  191. package/examples/preloadingAssets/view/MainContainer.mjs +2 -0
  192. package/examples/stateProvider/advanced/MainContainer.mjs +1 -0
  193. package/examples/stateProvider/dialog/EditUserDialog.mjs +2 -0
  194. package/examples/stateProvider/dialog/MainContainer.mjs +1 -0
  195. package/examples/stateProvider/extendedClass/MainContainer.mjs +2 -0
  196. package/examples/stateProvider/inline/MainContainer.mjs +1 -0
  197. package/examples/stateProvider/inlineNoStateProvider/MainContainer.mjs +1 -0
  198. package/examples/stateProvider/inlineNoStateProvider/MainContainerController.mjs +2 -0
  199. package/examples/stateProvider/multiWindow/EditUserDialog.mjs +3 -0
  200. package/examples/stateProvider/multiWindow/MainContainer.mjs +1 -0
  201. package/examples/stateProvider/multiWindow/Viewport.mjs +1 -0
  202. package/examples/stateProvider/nestedData/MainContainer.mjs +1 -0
  203. package/examples/stateProvider/table/MainContainer.mjs +1 -0
  204. package/examples/table/covid/MainContainer.mjs +2 -0
  205. package/examples/table/covid/Store.mjs +1 -0
  206. package/examples/table/covid/TableContainer.mjs +3 -0
  207. package/examples/table/nestedRecordFields/EditUserDialog.mjs +3 -0
  208. package/examples/table/nestedRecordFields/Viewport.mjs +1 -0
  209. package/examples/todoList/version1/MainComponent.mjs +1 -1
  210. package/examples/toolbar/breadcrumb/view/MainContainer.mjs +2 -0
  211. package/examples/toolbar/paging/store/Users.mjs +1 -0
  212. package/examples/toolbar/paging/view/AddUserDialog.mjs +3 -0
  213. package/examples/toolbar/paging/view/MainContainer.mjs +3 -0
  214. package/examples/treeAccordion/MainContainer.mjs +2 -2
  215. package/examples/worker/task/MainContainer.mjs +1 -0
  216. package/learn/Glossary.md +1 -0
  217. package/learn/UsingTheseTopics.md +1 -0
  218. package/learn/benefits/ConfigSystem.md +2 -0
  219. package/learn/benefits/Effort.md +1 -0
  220. package/learn/benefits/Features.md +1 -0
  221. package/learn/benefits/FormsEngine.md +1 -0
  222. package/learn/benefits/FourEnvironments.md +2 -0
  223. package/learn/benefits/Introduction.md +2 -0
  224. package/learn/benefits/MultiWindow.md +3 -1
  225. package/learn/benefits/OffTheMainThread.md +2 -0
  226. package/learn/benefits/Quick.md +2 -0
  227. package/learn/benefits/RPCLayer.md +2 -0
  228. package/learn/benefits/Speed.md +2 -0
  229. package/learn/blog/v10-deep-dive-functional-components.md +293 -0
  230. package/learn/blog/v10-deep-dive-reactivity.md +522 -0
  231. package/learn/blog/v10-deep-dive-state-provider.md +432 -0
  232. package/learn/blog/v10-deep-dive-vdom-revolution.md +194 -0
  233. package/learn/blog/v10-post1-love-story.md +383 -0
  234. package/learn/comparisons/NeoVsAngular.md +90 -0
  235. package/learn/comparisons/NeoVsExtJs.md +178 -0
  236. package/learn/comparisons/NeoVsNextJs.md +124 -0
  237. package/learn/comparisons/NeoVsReact.md +95 -0
  238. package/learn/comparisons/NeoVsSolid.md +78 -0
  239. package/learn/comparisons/NeoVsVue.md +92 -0
  240. package/learn/comparisons/Overview.md +46 -0
  241. package/learn/gettingstarted/ComponentModels.md +2 -0
  242. package/learn/gettingstarted/Config.md +2 -0
  243. package/learn/gettingstarted/DescribingTheUI.md +2 -0
  244. package/learn/gettingstarted/Events.md +2 -0
  245. package/learn/gettingstarted/Extending.md +2 -0
  246. package/learn/gettingstarted/References.md +2 -0
  247. package/learn/gettingstarted/Setup.md +3 -2
  248. package/learn/gettingstarted/Workspaces.md +2 -0
  249. package/learn/guides/datahandling/Collections.md +1 -0
  250. package/learn/guides/datahandling/Records.md +1 -0
  251. package/learn/guides/datahandling/StateProviders.md +130 -16
  252. package/learn/guides/datahandling/Tables.md +1 -1
  253. package/learn/guides/fundamentals/ConfigSystemDeepDive.md +1 -0
  254. package/learn/guides/fundamentals/DeclarativeComponentTreesVsImperativeVdom.md +2 -0
  255. package/learn/guides/fundamentals/DeclarativeVDOMWithEffects.md +10 -8
  256. package/learn/guides/fundamentals/ExtendingNeoClasses.md +1 -0
  257. package/learn/guides/fundamentals/InstanceLifecycle.md +3 -1
  258. package/learn/guides/fundamentals/MainThreadAddons.md +2 -0
  259. package/learn/guides/specificfeatures/Mixins.md +3 -1
  260. package/learn/guides/specificfeatures/MultiWindow.md +3 -1
  261. package/learn/guides/specificfeatures/PortalApp.md +2 -0
  262. package/learn/guides/uibuildingblocks/ComponentsAndContainers.md +2 -0
  263. package/learn/guides/uibuildingblocks/CustomComponents.md +2 -0
  264. package/learn/guides/uibuildingblocks/Layouts.md +2 -0
  265. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +28 -2
  266. package/learn/guides/userinteraction/Forms.md +2 -0
  267. package/learn/guides/userinteraction/events/CustomEvents.md +2 -1
  268. package/learn/guides/userinteraction/events/DomEvents.md +2 -0
  269. package/learn/javascript/ClassFeatures.md +4 -3
  270. package/learn/javascript/Classes.md +10 -13
  271. package/learn/javascript/Overrides.md +10 -6
  272. package/learn/javascript/Super.md +12 -8
  273. package/learn/tree.json +71 -64
  274. package/learn/tutorials/Earthquakes.md +2 -0
  275. package/learn/tutorials/RSP.md +3 -1
  276. package/learn/tutorials/TodoList.md +103 -7
  277. package/package.json +8 -6
  278. package/resources/scss/src/apps/email/ComposeView.scss +16 -0
  279. package/resources/scss/src/apps/email/MainView.scss +5 -0
  280. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +5 -4
  281. package/src/DefaultConfig.mjs +12 -2
  282. package/src/Main.mjs +1 -0
  283. package/src/Neo.mjs +219 -166
  284. package/src/Xhr.mjs +1 -0
  285. package/src/button/Base.mjs +13 -0
  286. package/src/button/Effect.mjs +16 -2
  287. package/src/button/Split.mjs +2 -0
  288. package/src/calendar/store/Calendars.mjs +1 -0
  289. package/src/calendar/store/Colors.mjs +1 -0
  290. package/src/calendar/store/Events.mjs +1 -0
  291. package/src/calendar/view/DayComponent.mjs +2 -0
  292. package/src/calendar/view/EditEventContainer.mjs +4 -1
  293. package/src/calendar/view/MainContainer.mjs +13 -0
  294. package/src/calendar/view/MainContainerStateProvider.mjs +14 -28
  295. package/src/calendar/view/SettingsContainer.mjs +1 -0
  296. package/src/calendar/view/YearComponent.mjs +16 -0
  297. package/src/calendar/view/calendars/ColorsList.mjs +2 -0
  298. package/src/calendar/view/calendars/Container.mjs +2 -0
  299. package/src/calendar/view/calendars/EditContainer.mjs +1 -0
  300. package/src/calendar/view/month/Component.mjs +11 -0
  301. package/src/calendar/view/settings/GeneralContainer.mjs +1 -0
  302. package/src/calendar/view/settings/MonthContainer.mjs +1 -0
  303. package/src/calendar/view/settings/WeekContainer.mjs +1 -0
  304. package/src/calendar/view/settings/YearContainer.mjs +1 -0
  305. package/src/calendar/view/week/Component.mjs +15 -1
  306. package/src/calendar/view/week/TimeAxisComponent.mjs +4 -0
  307. package/src/code/LivePreview.mjs +51 -23
  308. package/src/collection/Base.mjs +7 -10
  309. package/src/collection/Filter.mjs +6 -0
  310. package/src/collection/Sorter.mjs +3 -0
  311. package/src/component/Abstract.mjs +412 -0
  312. package/src/component/Base.mjs +48 -1077
  313. package/src/component/Canvas.mjs +1 -0
  314. package/src/component/Chip.mjs +4 -0
  315. package/src/component/Circle.mjs +14 -0
  316. package/src/component/Clock.mjs +4 -0
  317. package/src/component/DateSelector.mjs +12 -0
  318. package/src/component/Gallery.mjs +11 -0
  319. package/src/component/Helix.mjs +24 -0
  320. package/src/component/Label.mjs +1 -0
  321. package/src/component/Legend.mjs +3 -0
  322. package/src/component/MagicMoveText.mjs +4 -0
  323. package/src/component/Progress.mjs +3 -0
  324. package/src/component/Splitter.mjs +3 -0
  325. package/src/component/StatusBadge.mjs +6 -0
  326. package/src/component/Timer.mjs +4 -0
  327. package/src/component/Toast.mjs +6 -0
  328. package/src/component/Video.mjs +1 -0
  329. package/src/component/mwc/Button.mjs +7 -0
  330. package/src/component/mwc/TextField.mjs +9 -0
  331. package/src/component/wrapper/AmChart.mjs +2 -0
  332. package/src/component/wrapper/GoogleMaps.mjs +3 -0
  333. package/src/component/wrapper/MapboxGL.mjs +5 -0
  334. package/src/component/wrapper/MonacoEditor.mjs +12 -0
  335. package/src/container/Accordion.mjs +2 -0
  336. package/src/container/Base.mjs +7 -3
  337. package/src/container/Panel.mjs +1 -0
  338. package/src/container/Viewport.mjs +1 -0
  339. package/src/controller/Application.mjs +1 -0
  340. package/src/controller/Base.mjs +1 -0
  341. package/src/controller/Component.mjs +1 -0
  342. package/src/core/Base.mjs +86 -33
  343. package/src/core/Compare.mjs +4 -7
  344. package/src/core/Config.mjs +65 -52
  345. package/src/core/Effect.mjs +86 -24
  346. package/src/core/EffectManager.mjs +117 -8
  347. package/src/core/IdGenerator.mjs +13 -44
  348. package/src/core/Observable.mjs +69 -65
  349. package/src/data/Model.mjs +2 -0
  350. package/src/data/Store.mjs +7 -0
  351. package/src/data/connection/WebSocket.mjs +2 -0
  352. package/src/date/DayViewComponent.mjs +2 -0
  353. package/src/date/SelectorContainer.mjs +14 -0
  354. package/src/dialog/Base.mjs +8 -0
  355. package/src/draggable/DragZone.mjs +5 -0
  356. package/src/draggable/tree/DragZone.mjs +1 -0
  357. package/src/filter/BooleanContainer.mjs +2 -0
  358. package/src/filter/NumberContainer.mjs +3 -0
  359. package/src/filter/ToggleOperatorsButton.mjs +2 -0
  360. package/src/form/Fieldset.mjs +6 -0
  361. package/src/form/field/Base.mjs +7 -0
  362. package/src/form/field/CheckBox.mjs +18 -0
  363. package/src/form/field/Chip.mjs +1 -0
  364. package/src/form/field/ComboBox.mjs +8 -0
  365. package/src/form/field/Country.mjs +1 -0
  366. package/src/form/field/Currency.mjs +2 -0
  367. package/src/form/field/Date.mjs +4 -0
  368. package/src/form/field/Display.mjs +1 -0
  369. package/src/form/field/Email.mjs +1 -0
  370. package/src/form/field/FileUpload.mjs +7 -0
  371. package/src/form/field/Hidden.mjs +1 -0
  372. package/src/form/field/Number.mjs +7 -0
  373. package/src/form/field/Password.mjs +1 -0
  374. package/src/form/field/Phone.mjs +3 -0
  375. package/src/form/field/Picker.mjs +2 -0
  376. package/src/form/field/Radio.mjs +1 -0
  377. package/src/form/field/Range.mjs +3 -0
  378. package/src/form/field/Search.mjs +2 -0
  379. package/src/form/field/Text.mjs +43 -5
  380. package/src/form/field/TextArea.mjs +7 -0
  381. package/src/form/field/Time.mjs +6 -0
  382. package/src/form/field/Url.mjs +3 -0
  383. package/src/form/field/ZipCode.mjs +2 -0
  384. package/src/form/field/trigger/Base.mjs +3 -0
  385. package/src/form/field/trigger/Clear.mjs +2 -0
  386. package/src/form/field/trigger/CopyToClipboard.mjs +2 -0
  387. package/src/form/field/trigger/Date.mjs +1 -0
  388. package/src/form/field/trigger/Picker.mjs +1 -0
  389. package/src/form/field/trigger/Search.mjs +1 -0
  390. package/src/form/field/trigger/SpinDown.mjs +2 -0
  391. package/src/form/field/trigger/SpinUp.mjs +1 -0
  392. package/src/form/field/trigger/Time.mjs +2 -0
  393. package/src/functional/_export.mjs +6 -0
  394. package/src/functional/button/Base.mjs +384 -0
  395. package/src/functional/component/Base.mjs +405 -0
  396. package/src/functional/defineComponent.mjs +102 -0
  397. package/src/functional/useConfig.mjs +52 -0
  398. package/src/functional/useEvent.mjs +43 -0
  399. package/src/grid/Body.mjs +20 -1
  400. package/src/grid/Container.mjs +50 -60
  401. package/src/grid/ScrollManager.mjs +2 -0
  402. package/src/grid/VerticalScrollbar.mjs +2 -0
  403. package/src/grid/column/Base.mjs +2 -0
  404. package/src/grid/header/Button.mjs +7 -0
  405. package/src/grid/header/Toolbar.mjs +6 -0
  406. package/src/grid/plugin/AnimateRows.mjs +2 -0
  407. package/src/layout/Base.mjs +3 -0
  408. package/src/layout/Card.mjs +1 -0
  409. package/src/layout/Cube.mjs +18 -4
  410. package/src/layout/Fit.mjs +1 -0
  411. package/src/layout/Flexbox.mjs +7 -0
  412. package/src/layout/Form.mjs +2 -0
  413. package/src/layout/Grid.mjs +1 -0
  414. package/src/layout/HBox.mjs +1 -0
  415. package/src/layout/VBox.mjs +1 -0
  416. package/src/list/Base.mjs +13 -0
  417. package/src/list/Chip.mjs +1 -0
  418. package/src/list/Circle.mjs +2 -0
  419. package/src/list/Color.mjs +1 -0
  420. package/src/list/plugin/Animate.mjs +2 -0
  421. package/src/main/DeltaUpdates.mjs +1 -0
  422. package/src/main/DomEvents.mjs +2 -0
  423. package/src/main/addon/CloneNode.mjs +1 -0
  424. package/src/main/addon/Cookie.mjs +1 -0
  425. package/src/main/addon/GoogleMaps.mjs +1 -0
  426. package/src/main/addon/LocalStorage.mjs +1 -0
  427. package/src/main/addon/MapboxGL.mjs +1 -0
  428. package/src/main/addon/Markdown.mjs +1 -0
  429. package/src/main/addon/Navigator.mjs +1 -0
  430. package/src/main/addon/Popover.mjs +1 -0
  431. package/src/main/addon/Stylesheet.mjs +1 -0
  432. package/src/main/addon/WindowPosition.mjs +1 -0
  433. package/src/manager/Component.mjs +0 -71
  434. package/src/manager/VDomUpdate.mjs +320 -0
  435. package/src/menu/List.mjs +6 -0
  436. package/src/menu/Model.mjs +1 -0
  437. package/src/menu/Panel.mjs +3 -0
  438. package/src/menu/Store.mjs +1 -0
  439. package/src/mixin/DomEvents.mjs +130 -0
  440. package/src/mixin/VdomLifecycle.mjs +670 -0
  441. package/src/plugin/Base.mjs +1 -0
  442. package/src/plugin/Resizable.mjs +2 -0
  443. package/src/selection/Model.mjs +15 -18
  444. package/src/selection/grid/BaseModel.mjs +1 -0
  445. package/src/sitemap/Component.mjs +1 -0
  446. package/src/state/Provider.mjs +129 -87
  447. package/src/state/createHierarchicalDataProxy.mjs +39 -25
  448. package/src/tab/Container.mjs +6 -0
  449. package/src/tab/Strip.mjs +1 -0
  450. package/src/tab/header/Button.mjs +2 -0
  451. package/src/tab/header/EffectButton.mjs +2 -0
  452. package/src/tab/header/Toolbar.mjs +1 -0
  453. package/src/table/Body.mjs +3 -0
  454. package/src/table/Container.mjs +10 -0
  455. package/src/table/header/Button.mjs +8 -0
  456. package/src/table/header/Toolbar.mjs +5 -0
  457. package/src/table/plugin/CellEditing.mjs +1 -0
  458. package/src/toolbar/Base.mjs +4 -0
  459. package/src/toolbar/Breadcrumb.mjs +3 -0
  460. package/src/toolbar/Paging.mjs +5 -0
  461. package/src/tooltip/Base.mjs +2 -0
  462. package/src/tree/List.mjs +3 -0
  463. package/src/util/HashHistory.mjs +1 -0
  464. package/src/util/KeyNavigation.mjs +2 -0
  465. package/src/util/Matrix.mjs +1 -0
  466. package/src/util/VDom.mjs +18 -5
  467. package/src/util/VNode.mjs +7 -1
  468. package/src/util/vdom/TreeBuilder.mjs +105 -0
  469. package/src/vdom/Helper.mjs +35 -23
  470. package/src/vdom/VNode.mjs +4 -6
  471. package/src/worker/App.mjs +1 -0
  472. package/src/worker/Base.mjs +2 -0
  473. package/src/worker/Manager.mjs +2 -0
  474. package/src/worker/ServiceBase.mjs +6 -1
  475. package/src/worker/mixin/RemoteMethodAccess.mjs +1 -6
  476. package/test/siesta/siesta.js +17 -2
  477. package/test/siesta/tests/VdomCalendar.mjs +19 -15
  478. package/test/siesta/tests/VdomHelper.mjs +7 -7
  479. package/test/siesta/tests/classic/Button.mjs +113 -0
  480. package/test/siesta/tests/core/Effect.mjs +10 -14
  481. package/test/siesta/tests/core/EffectBatching.mjs +72 -79
  482. package/test/siesta/tests/functional/Button.mjs +113 -0
  483. package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +314 -0
  484. package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +42 -55
  485. package/test/siesta/tests/vdom/Advanced.mjs +14 -8
  486. package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +366 -0
  487. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +249 -0
  488. package/test/siesta/tests/vdom/layout/Cube.mjs +11 -7
  489. package/test/siesta/tests/vdom/table/Container.mjs +9 -5
  490. package/learn/javascript/NewNode.md +0 -31
  491. package/src/core/EffectBatchManager.mjs +0 -68
@@ -14,13 +14,26 @@ import {isDescriptor} from './ConfigSymbols.mjs';
14
14
  */
15
15
  class Config {
16
16
  /**
17
- * A Set to store callback functions that subscribe to changes in this config's value.
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
32
  */
20
33
  #subscribers = {}
21
34
  /**
22
35
  * The internal value of the config property.
23
- * @member #value
36
+ * @member {*} #value
24
37
  * @private
25
38
  */
26
39
  #value
@@ -67,7 +80,7 @@ class Config {
67
80
  get() {
68
81
  // Registers this Config instance as a dependency with the currently active Effect,
69
82
  // enabling automatic re-execution when this Config's value changes.
70
- EffectManager.getActiveEffect()?.addDependency(this);
83
+ EffectManager.addDependency(this);
71
84
  return this.#value
72
85
  }
73
86
 
@@ -75,52 +88,56 @@ class Config {
75
88
  * Initializes the `Config` instance using a descriptor object.
76
89
  * Extracts `clone`, `mergeStrategy` and `isEqual` from the descriptor.
77
90
  * The internal `#value` is NOT set by this method.
78
- * @param {Object} descriptor - The descriptor object for the config.
79
- * @param {any} descriptor.value - The default value for the config (not set by this method).
91
+ * @param {Object} descriptor - The descriptor object for the config.
80
92
  * @param {string} [descriptor.clone='deep'] - The clone strategy for set.
81
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'.
82
- * @param {string} [descriptor.merge='deep'] - The merge strategy.
83
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).
84
97
  */
85
98
  initDescriptor({clone, cloneOnGet, isEqual, merge}) {
86
99
  let me = this;
87
100
 
88
101
  if (clone && clone !== me.clone) {
89
102
  Object.defineProperty(me, 'clone', {
90
- value: clone, writable: true, configurable: true, enumerable: true
103
+ configurable: true, enumerable: true, value: clone, writable: true
91
104
  })
92
105
  }
93
106
 
94
107
  if (cloneOnGet && cloneOnGet !== me.cloneOnGet) {
95
108
  Object.defineProperty(me, 'cloneOnGet', {
96
- value: cloneOnGet, writable: true, configurable: true, enumerable: true
109
+ configurable: true, enumerable: true, value: cloneOnGet, writable: true
97
110
  })
98
111
  }
99
112
 
100
113
  if (isEqual && isEqual !== me.isEqual) {
101
114
  Object.defineProperty(me, 'isEqual', {
102
- value: isEqual, writable: true, configurable: true, enumerable: true
115
+ configurable: true, enumerable: true, value: isEqual, writable: true
103
116
  })
104
117
  }
105
118
 
106
119
  if (merge && merge !== me.mergeStrategy) {
107
120
  Object.defineProperty(me, 'mergeStrategy', {
108
- value: merge, writable: true, configurable: true, enumerable: true
121
+ configurable: true, enumerable: true, value: merge, writable: true
109
122
  })
110
123
  }
111
124
  }
112
125
 
113
126
  /**
114
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.
115
130
  * @param {any} newValue - The new value of the config.
116
131
  * @param {any} oldValue - The old value of the config.
117
132
  */
118
133
  notify(newValue, oldValue) {
119
134
  for (const id in this.#subscribers) {
120
135
  if (this.#subscribers.hasOwnProperty(id)) {
121
- const subscriberSet = this.#subscribers[id];
122
- for (const callback of subscriberSet) {
123
- callback(newValue, oldValue)
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
+ }
124
141
  }
125
142
  }
126
143
  }
@@ -164,12 +181,13 @@ class Config {
164
181
  /**
165
182
  * Subscribes a callback function to changes in this config's value.
166
183
  * The callback will be invoked with `(newValue, oldValue)` whenever the config changes.
167
- * @param {Object} options - An object containing the subscription details.
168
- * @param {String} options.id - The ID of the subscription owner (e.g., a Neo.core.Base instance's id).
169
- * @param {Function} options.fn - The callback function.
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.
170
188
  * @returns {Function} A cleanup function to unsubscribe the callback.
171
189
  */
172
- subscribe({id, fn}) {
190
+ subscribe({id, fn, scope}) {
173
191
  if (typeof id !== 'string' || id.length === 0 || typeof fn !== 'function') {
174
192
  throw new Error([
175
193
  'Config.subscribe: options must be an object with a non-empty string `id` ',
@@ -179,18 +197,36 @@ class Config {
179
197
 
180
198
  const me = this;
181
199
 
200
+ // Get or create the top-level Map for the subscription owner.
182
201
  if (!me.#subscribers[id]) {
183
- me.#subscribers[id] = new Set()
202
+ me.#subscribers[id] = new Map()
184
203
  }
185
204
 
186
- me.#subscribers[id].add(fn);
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
+ }
187
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.
188
218
  return () => {
189
- const subscriberSet = me.#subscribers[id];
190
- if (subscriberSet) {
191
- subscriberSet.delete(fn);
192
- if (subscriberSet.size === 0) {
193
- delete me.#subscribers[id]
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
+ }
194
230
  }
195
231
  }
196
232
  }
@@ -198,33 +234,10 @@ class Config {
198
234
  }
199
235
 
200
236
  Object.defineProperties(Config.prototype, {
201
- clone: {
202
- value: 'deep',
203
- writable: false,
204
- configurable: true,
205
- enumerable: false
206
- },
207
- cloneOnGet: {
208
- value: null,
209
- writable: false,
210
- configurable: true,
211
- enumerable: false
212
- },
213
- isEqual: {
214
- value: Neo.isEqual,
215
- writable: false,
216
- configurable: true,
217
- enumerable: false
218
- },
219
- mergeStrategy: {
220
- value: 'replace',
221
- writable: false,
222
- configurable: true,
223
- enumerable: false
224
- }
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}
225
241
  });
226
242
 
227
- const ns = Neo.ns('Neo.core', true);
228
- ns.Config = Config;
229
-
230
- export default Config;
243
+ export default Neo.gatekeep(Config, 'Neo.core.Config');
@@ -1,6 +1,6 @@
1
- import EffectManager from './EffectManager.mjs';
2
- import EffectBatchManager from './EffectBatchManager.mjs';
3
- import IdGenerator from './IdGenerator.mjs';
1
+ import Config from './Config.mjs';
2
+ import EffectManager from './EffectManager.mjs';
3
+ import IdGenerator from './IdGenerator.mjs';
4
4
 
5
5
  /**
6
6
  * Creates a reactive effect that automatically tracks its dependencies and re-runs when any of them change.
@@ -9,6 +9,11 @@ import IdGenerator from './IdGenerator.mjs';
9
9
  * @class Neo.core.Effect
10
10
  */
11
11
  class Effect {
12
+ /**
13
+ * The optional component id this effect belongs to.
14
+ * @member {String|null} componentId=null
15
+ */
16
+ componentId = null
12
17
  /**
13
18
  * A Map containing Config instances as keys and their cleanup functions as values.
14
19
  * @member {Map} dependencies=new Map()
@@ -31,10 +36,10 @@ class Effect {
31
36
  */
32
37
  isDestroyed = false
33
38
  /**
34
- * @member {Boolean}
39
+ * @member {Neo.core.Config}
35
40
  * @protected
36
41
  */
37
- isRunning = false
42
+ isRunning = null
38
43
 
39
44
  /**
40
45
  * @member fn
@@ -51,11 +56,44 @@ class Effect {
51
56
  }
52
57
 
53
58
  /**
54
- * @param {Object} config
55
- * @param {Function} config.fn The function to execute for the effect.
59
+ * @param {Function|Object} fn - The function to execute, or a config object for the effect.
60
+ * @param {Function} [fn.fn] - The function to execute for the effect (if the first argument is an object).
61
+ * @param {String} [fn.componentId] - The component id this effect belongs to.
62
+ * @param {Boolean} [fn.lazy=false] - If true, the effect will not run immediately upon creation.
63
+ * @param {Object|Object[]} [fn.subscriber] - A single subscriber or an array of subscribers for the isRunning config.
64
+ * @param {Object} [options={}] - Optional. Used if the first argument is a function, this object contains the options.
65
+ * @example
66
+ * // Signature 1: Function and Options
67
+ * const myEffect = new Effect(() => console.log('Run'), {lazy: true});
68
+ * @example
69
+ * // Signature 2: Single Config Object
70
+ * const myEffect = new Effect({fn: () => console.log('Run'), lazy: true});
56
71
  */
57
- constructor({fn}) {
58
- this.fn = fn
72
+ constructor(fn, options={}) {
73
+ const me = this;
74
+
75
+ const {
76
+ fn: effectFn,
77
+ componentId,
78
+ lazy = false,
79
+ subscriber
80
+ } = (typeof fn === 'function') ? { ...options, fn } : (fn || {});
81
+
82
+ if (componentId) {
83
+ me.componentId = componentId
84
+ }
85
+
86
+ me.isRunning = new Config(false);
87
+
88
+ if (subscriber) {
89
+ [].concat(subscriber).forEach(sub => me.isRunning.subscribe(sub))
90
+ }
91
+
92
+ if (lazy) {
93
+ me._fn = effectFn
94
+ } else {
95
+ me.fn = effectFn
96
+ }
59
97
  }
60
98
 
61
99
  /**
@@ -70,34 +108,53 @@ class Effect {
70
108
  }
71
109
 
72
110
  /**
73
- * Executes the effect function, tracking its dependencies.
74
- * This is called automatically on creation and whenever a dependency changes.
75
- * The dynamic re-tracking ensures the effect always reflects its current dependencies,
76
- * even if the logic within `fn` changes conditionally.
111
+ * Executes the effect function, re-evaluating its dependencies.
112
+ * If the EffectManager is paused (e.g., inside a batch), it queues itself to be run later.
77
113
  * @protected
78
114
  */
79
115
  run() {
80
116
  const me = this;
81
117
 
82
- if (me.isDestroyed || me.isRunning) return;
118
+ if (me.isDestroyed) {
119
+ return
120
+ }
121
+
122
+ // Check if already running without creating a dependency on `isRunning`.
123
+ EffectManager.pauseTracking();
124
+ const isRunning = me.isRunning.get();
125
+ EffectManager.resumeTracking();
126
+
127
+ if (isRunning) {
128
+ return
129
+ }
83
130
 
84
- if (EffectBatchManager.isBatchActive()) {
85
- EffectBatchManager.queueEffect(me);
131
+ // If the manager is globally paused for batching, queue this effect and stop.
132
+ if (EffectManager.isPaused()) {
133
+ EffectManager.queue(me);
86
134
  return
87
135
  }
88
136
 
89
- me.isRunning = true;
137
+ // Set `isRunning` to true without creating a dependency.
138
+ EffectManager.pauseTracking();
139
+ me.isRunning.set(true);
140
+ EffectManager.resumeTracking();
90
141
 
142
+ // Clear old dependencies and set this as the active effect.
91
143
  me.dependencies.forEach(cleanup => cleanup());
92
144
  me.dependencies.clear();
93
-
94
145
  EffectManager.push(me);
95
146
 
96
147
  try {
148
+ // Execute the function, which will collect new dependencies.
97
149
  me.fn()
98
150
  } finally {
151
+ // Clean up after the run.
99
152
  EffectManager.pop();
100
- me.isRunning = false;
153
+
154
+ // Set `isRunning` to false without creating a dependency.
155
+ EffectManager.pauseTracking();
156
+ me.isRunning.set(false);
157
+ EffectManager.resumeTracking()
101
158
  }
102
159
  }
103
160
 
@@ -109,7 +166,6 @@ class Effect {
109
166
  addDependency(config) {
110
167
  const me = this;
111
168
 
112
- // Only add if not already a dependency. Map uses strict equality (===) for object keys.
113
169
  if (!me.dependencies.has(config)) {
114
170
  const cleanup = config.subscribe({
115
171
  id: me.id,
@@ -121,7 +177,13 @@ class Effect {
121
177
  }
122
178
  }
123
179
 
124
- const ns = Neo.ns('Neo.core', true);
125
- ns.Effect = Effect;
126
-
127
- export default Effect;
180
+ export default Neo.gatekeep(Effect, 'Neo.core.Effect', () => {
181
+ /**
182
+ * Factory shortcut to create a new Neo.core.Effect instance.
183
+ * @function Neo.effect
184
+ * @param {Function|Object} fn - The function to execute, or a config object for the effect.
185
+ * @param {Object} [options] - Optional. Used if the first argument is a function.
186
+ * @returns {Neo.core.Effect}
187
+ */
188
+ Neo.effect = (fn, options) => new Effect(fn, options)
189
+ });
@@ -1,11 +1,48 @@
1
1
  /**
2
- * A singleton manager to track the currently running effect.
3
- * This allows reactive properties to know which effect to subscribe to.
2
+ * A singleton manager to track the currently running effect and control global effect execution.
3
+ * It provides a centralized mechanism for pausing, resuming, and batching effect runs.
4
4
  * @class Neo.core.EffectManager
5
5
  * @singleton
6
6
  */
7
7
  const EffectManager = {
8
+ /**
9
+ * A stack to keep track of the currently active effect and its predecessors.
10
+ * @member {Neo.core.Effect[]} effectStack=[]
11
+ * @protected
12
+ */
8
13
  effectStack: [],
14
+ /**
15
+ * A flag to temporarily disable dependency tracking for the active effect.
16
+ * This is used internally to prevent effects from depending on their own state, like `isRunning`.
17
+ * @member {Boolean} isTrackingPaused=false
18
+ * @protected
19
+ */
20
+ isTrackingPaused: false,
21
+ /**
22
+ * A counter to manage nested calls to pause() and resume(). Effect execution is
23
+ * paused or batched while this counter is greater than 0.
24
+ * @member {Number} pauseCounter=0
25
+ * @protected
26
+ */
27
+ pauseCounter: 0,
28
+ /**
29
+ * A Set to store unique effects that are triggered while the manager is paused.
30
+ * These effects will be run when resume() is called and the pauseCounter returns to 0.
31
+ * @member {Set<Neo.core.Effect>} queuedEffects=new Set()
32
+ * @protected
33
+ */
34
+ queuedEffects: new Set(),
35
+
36
+ /**
37
+ * Adds a `Neo.core.Config` instance as a dependency for the currently active effect,
38
+ * unless dependency tracking is explicitly paused.
39
+ * @param {Neo.core.Config} config The config instance to add as a dependency.
40
+ */
41
+ addDependency(config) {
42
+ if (!this.isTrackingPaused) {
43
+ this.getActiveEffect()?.addDependency(config)
44
+ }
45
+ },
9
46
 
10
47
  /**
11
48
  * Returns the effect currently at the top of the stack (i.e., the one currently running).
@@ -16,7 +53,31 @@ const EffectManager = {
16
53
  },
17
54
 
18
55
  /**
19
- * Pops the current effect from the stack, returning to the previous effect (if any).
56
+ * Checks if effect execution is currently paused or batched.
57
+ * @returns {Boolean} True if the pauseCounter is greater than 0.
58
+ */
59
+ isPaused() {
60
+ return this.pauseCounter > 0
61
+ },
62
+
63
+ /**
64
+ * Pauses effect execution and begins batching.
65
+ * Each call to pause() increments a counter, allowing for nested control.
66
+ */
67
+ pause() {
68
+ this.pauseCounter++
69
+ },
70
+
71
+ /**
72
+ * Disables dependency tracking for the currently active effect.
73
+ * @protected
74
+ */
75
+ pauseTracking() {
76
+ this.isTrackingPaused = true
77
+ },
78
+
79
+ /**
80
+ * Pops the current effect from the stack.
20
81
  * @returns {Neo.core.Effect|null}
21
82
  */
22
83
  pop() {
@@ -24,15 +85,63 @@ const EffectManager = {
24
85
  },
25
86
 
26
87
  /**
27
- * Pushes an effect onto the stack, marking it as the currently running effect.
88
+ * Pushes an effect onto the stack.
28
89
  * @param {Neo.core.Effect} effect The effect to push.
29
90
  */
30
91
  push(effect) {
31
92
  this.effectStack.push(effect)
93
+ },
94
+
95
+ /**
96
+ * Queues a unique effect to be run later.
97
+ * @param {Neo.core.Effect} effect The effect to queue.
98
+ * @protected
99
+ */
100
+ queue(effect) {
101
+ this.queuedEffects.add(effect)
102
+ },
103
+
104
+ /**
105
+ * Resumes effect execution. If the pause counter returns to zero and effects
106
+ * have been queued, they will all be executed synchronously.
107
+ */
108
+ resume() {
109
+ let me = this;
110
+
111
+ if (me.pauseCounter > 0) {
112
+ me.pauseCounter--;
113
+
114
+ if (me.pauseCounter === 0 && me.queuedEffects.size > 0) {
115
+ const effectsToRun = [...me.queuedEffects];
116
+ me.queuedEffects.clear();
117
+ effectsToRun.forEach(effect => effect.run())
118
+ }
119
+ }
120
+ },
121
+
122
+ /**
123
+ * Re-enables dependency tracking for the currently active effect.
124
+ * @protected
125
+ */
126
+ resumeTracking() {
127
+ this.isTrackingPaused = false
32
128
  }
33
129
  };
34
130
 
35
- const ns = Neo.ns('Neo.core', true);
36
- ns.EffectManager = EffectManager;
37
-
38
- export default EffectManager;
131
+ export default Neo.gatekeep(EffectManager, 'Neo.core.EffectManager', () => {
132
+ /**
133
+ * Wraps a function in a batch operation, ensuring that all effects triggered
134
+ * within it are run only once after the function completes.
135
+ * @function Neo.batch
136
+ * @param {Function} fn The function to execute.
137
+ */
138
+ Neo.batch = function(fn) {
139
+ EffectManager.pause();
140
+ try {
141
+ fn()
142
+ } finally {
143
+ // The public resume() method handles running queued effects.
144
+ EffectManager.resume()
145
+ }
146
+ }
147
+ });
@@ -1,44 +1,17 @@
1
1
  /**
2
- * This class gets used by core.Base, so it can not extend it.
3
- * It could get simplified to just being an object (needs to manually get put into the Neo namespace in this case).
4
- * @class Neo.core.IdGenerator
5
- * @singleton
2
+ * Provides a singleton utility for generating unique IDs.
3
+ * @namespace Neo.core.IdGenerator
6
4
  */
7
- class IdGenerator {
8
- static config = {
9
- /**
10
- * @member {String} className='Neo.core.IdGenerator'
11
- * @protected
12
- */
13
- className: 'Neo.core.IdGenerator',
14
- /**
15
- * @member {String} ntype='id-generator'
16
- * @protected
17
- */
18
- ntype: 'id-generator',
19
- /**
20
- * The default prefix for neo instance ids
21
- * @member {String} base='neo-'
22
- */
23
- base: 'neo-',
24
- /**
25
- * @member {Boolean} singleton='true
26
- * @protected
27
- */
28
- singleton: true
29
- }
30
-
5
+ const IdGenerator = {
31
6
  /**
32
- * @param config
7
+ * The default prefix for neo instance ids
8
+ * @member {String} base='neo-'
33
9
  */
34
- construct(config) {
35
- let me = this;
36
-
37
- me.idCounter = {};
38
-
39
- // alias
40
- Neo.getId = me.getId.bind(me);
41
- }
10
+ base: 'neo-',
11
+ /**
12
+ * @member {Object} idCounter={}
13
+ */
14
+ idCounter: {},
42
15
 
43
16
  /**
44
17
  * @param name
@@ -55,12 +28,8 @@ class IdGenerator {
55
28
 
56
29
  return me.base + (name === 'neo' ? '' : name + '-') + count;
57
30
  }
58
-
59
- init() {}
60
-
61
- onAfterConstructed() {}
62
-
63
- onConstructed() {}
64
31
  }
65
32
 
66
- export default Neo.setupClass(IdGenerator);
33
+ export default Neo.gatekeep(IdGenerator, 'Neo.core.IdGenerator', () => {
34
+ Neo.getId = IdGenerator.getId.bind(IdGenerator);
35
+ });