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/Neo.mjs CHANGED
@@ -1,18 +1,46 @@
1
- import DefaultConfig from './DefaultConfig.mjs';
2
- import {isDescriptor} from './core/ConfigSymbols.mjs';
1
+ import DefaultConfig from './DefaultConfig.mjs';
2
+ import {isDescriptor} from './core/ConfigSymbols.mjs';
3
3
 
4
4
  const
5
5
  camelRegex = /-./g,
6
6
  configSymbol = Symbol.for('configSymbol'),
7
7
  getSetCache = Symbol('getSetCache'),
8
+ cloneMap = {
9
+ Array(obj, deep, ignoreNeoInstances) {
10
+ return !deep ? [...obj] : [...obj.map(val => Neo.clone(val, deep, ignoreNeoInstances))]
11
+ },
12
+ Date(obj) {
13
+ return new Date(obj.valueOf())
14
+ },
15
+ Map(obj) {
16
+ return new Map(obj) // shallow copy
17
+ },
18
+ NeoInstance(obj, ignoreNeoInstances) {
19
+ return ignoreNeoInstances ? obj : Neo.cloneNeoInstance(obj)
20
+ },
21
+ Set(obj) {
22
+ return new Set(obj)
23
+ },
24
+ Object(obj, deep, ignoreNeoInstances) {
25
+ const out = {};
26
+
27
+ // Use Reflect.ownKeys() to include symbol properties (e.g., for config descriptors)
28
+ Reflect.ownKeys(obj).forEach(key => {
29
+ const value = obj[key];
30
+ out[key] = !deep ? value : Neo.clone(value, deep, ignoreNeoInstances)
31
+ });
32
+
33
+ return out
34
+ }
35
+ },
8
36
  typeDetector = {
9
37
  function: item => {
10
- if (item.prototype?.constructor.isClass) {
38
+ if (item.prototype?.constructor?.isClass) {
11
39
  return 'NeoClass'
12
40
  }
13
41
  },
14
42
  object: item => {
15
- if (item.constructor.isClass && item instanceof Neo.core.Base) {
43
+ if (item.constructor?.isClass && item instanceof Neo.core.Base) {
16
44
  return 'NeoInstance'
17
45
  }
18
46
  }
@@ -23,7 +51,6 @@ const
23
51
  * @module Neo
24
52
  * @singleton
25
53
  * @borrows Neo.core.Util.bindMethods as bindMethods
26
- * @borrows Neo.core.Util.capitalize as capitalize
27
54
  * @borrows Neo.core.Util.createStyleObject as createStyleObject
28
55
  * @borrows Neo.core.Util.createStyles as createStyles
29
56
  * @borrows Neo.core.Util.decamel as decamel
@@ -178,25 +205,7 @@ Neo = globalThis.Neo = Object.assign({
178
205
  * @returns {Object|Array|*} the cloned input
179
206
  */
180
207
  clone(obj, deep=false, ignoreNeoInstances=false) {
181
- let out;
182
-
183
- return {
184
- Array : () => !deep ? [...obj] : [...obj.map(val => Neo.clone(val, deep, ignoreNeoInstances))],
185
- Date : () => new Date(obj.valueOf()),
186
- Map : () => new Map(obj), // shallow copy
187
- NeoInstance: () => ignoreNeoInstances ? obj : this.cloneNeoInstance(obj),
188
- Set : () => new Set(obj),
189
-
190
- Object: () => {
191
- out = {};
192
-
193
- Object.entries(obj).forEach(([key, value]) => {
194
- out[key] = !deep ? value : Neo.clone(value, deep, ignoreNeoInstances)
195
- });
196
-
197
- return out
198
- }
199
- }[Neo.typeOf(obj)]?.() || obj
208
+ return cloneMap[Neo.typeOf(obj)]?.(obj, deep, ignoreNeoInstances) || obj
200
209
  },
201
210
 
202
211
  /**
@@ -264,7 +273,7 @@ Neo = globalThis.Neo = Object.assign({
264
273
  return null
265
274
  }
266
275
 
267
- className = config.className || config.module.prototype.className;
276
+ className = config.className || config.module.prototype.className
268
277
  }
269
278
 
270
279
  if (!exists(className)) {
@@ -284,11 +293,195 @@ Neo = globalThis.Neo = Object.assign({
284
293
  return instance
285
294
  },
286
295
 
296
+ /**
297
+ * Defines a reactive configuration property on a target object (prototype or instance).
298
+ * This method creates getters and setters that fully participate in Neo.mjs's reactive config system,
299
+ * including lifecycle hooks.
300
+ *
301
+ * @param {Neo.core.Base} target - The instance or prototype on which to define the config.
302
+ * @param {String} key - The name of the config property (without the '_' suffix).
303
+ * @param {*} [initialValue] - The initial value for the config.
304
+ */
305
+ createConfig(target, key, initialValue) {
306
+ if (Neo.hasPropertySetter(target, key)) {
307
+ throw(
308
+ `Invalid config in ${target.className}: '${key}_'. The config '${key}' is already defined as reactive by a parent class.
309
+ To override the default value, use '${key}' (without the underscore) in your static config.
310
+ If you intended to create custom logic, use the 'beforeGet${Neo.capitalize(key)}()', 'beforeSet${Neo.capitalize(key)}()', and 'afterSet${Neo.capitalize(key)}()' hooks instead of redefining the config.`
311
+ )
312
+ }
313
+
314
+ const
315
+ _key = '_' + key,
316
+ uKey = key[0].toUpperCase() + key.slice(1),
317
+ beforeGet = 'beforeGet' + uKey,
318
+ beforeSet = 'beforeSet' + uKey,
319
+ afterSet = 'afterSet' + uKey;
320
+
321
+ Neo[getSetCache] ??= {};
322
+
323
+ if (!Neo[getSetCache][key]) {
324
+ // Public Descriptor
325
+ Neo[getSetCache][key] = {
326
+ get() {
327
+ let me = this,
328
+ config = me.getConfig(key),
329
+ hasNewKey = Object.hasOwn(me[configSymbol], key),
330
+ newKey = me[configSymbol][key],
331
+ value = hasNewKey ? newKey : me[_key];
332
+
333
+ if (value instanceof Date) {
334
+ value = new Date(value.valueOf());
335
+ }
336
+ // new, explicit opt-in path
337
+ else if (config.cloneOnGet) {
338
+ const {cloneOnGet} = config;
339
+
340
+ if (cloneOnGet === 'deep') {
341
+ value = Neo.clone(value, true, true);
342
+ } else if (cloneOnGet === 'shallow') {
343
+ const type = Neo.typeOf(value);
344
+
345
+ if (type === 'Array') {
346
+ value = [...value];
347
+ } else if (type === 'Object') {
348
+ value = {...value};
349
+ }
350
+ }
351
+ }
352
+ // legacy behavior
353
+ else if (Array.isArray(value)) {
354
+ value = [...value];
355
+ }
356
+
357
+ if (hasNewKey) {
358
+ me[key] = value; // We do want to trigger the setter => beforeSet, afterSet
359
+ value = me[_key]; // Return the value parsed by the setter
360
+ delete me[configSymbol][key]
361
+ }
362
+
363
+ if (typeof me[beforeGet] === 'function') {
364
+ value = me[beforeGet](value)
365
+ }
366
+
367
+ return value
368
+ },
369
+ set(value) {
370
+ if (value === undefined) return;
371
+
372
+ const config = this.getConfig(key);
373
+ if (!config) return;
374
+
375
+ let me = this,
376
+ oldValue = config.get(), // Get the old value from the Config instance
377
+ {EffectBatchManager} = Neo.core,
378
+ isNewBatch = !EffectBatchManager?.isBatchActive();
379
+
380
+ // If a config change is not triggered via `core.Base#set()`, honor changes inside hooks.
381
+ isNewBatch && EffectBatchManager?.startBatch();
382
+
383
+ // 1. Prevent infinite loops:
384
+ // Immediately remove the pending value from the configSymbol to prevent a getter from
385
+ // recursively re-triggering this setter.
386
+ delete me[configSymbol][key];
387
+
388
+ switch (config.clone) {
389
+ case 'deep':
390
+ value = Neo.clone(value, true, true);
391
+ break;
392
+ case 'shallow':
393
+ value = Neo.clone(value, false, true);
394
+ break;
395
+ }
396
+
397
+ // 2. Create a temporary state for beforeSet hooks:
398
+ // Set the new value directly on the private backing property. This allows any beforeSet
399
+ // hook to access the new value of this and other configs within the same `set()` call.
400
+ me[_key] = value;
401
+
402
+ if (typeof me[beforeSet] === 'function') {
403
+ value = me[beforeSet](value, oldValue);
404
+
405
+ // If they don't return a value, that means no change
406
+ if (value === undefined) {
407
+ // Restore the original value if the update is canceled.
408
+ me[_key] = oldValue;
409
+ isNewBatch && EffectBatchManager?.endBatch();
410
+ return
411
+ }
412
+ }
413
+
414
+ // 3. Restore state for change detection:
415
+ // Revert the private backing property to its original value. This is crucial for the
416
+ // `config.set()` method to correctly detect if the value has actually changed.
417
+ me[_key] = oldValue;
418
+
419
+ // 4. Finalize the change:
420
+ // The config.set() method performs the final check and, if the value changed,
421
+ // triggers afterSet hooks and notifies subscribers.
422
+ if (config.set(value)) {
423
+ me[afterSet]?.(value, oldValue);
424
+ me.afterSetConfig?.(key, value, oldValue)
425
+ }
426
+
427
+ isNewBatch && EffectBatchManager?.endBatch()
428
+ }
429
+ };
430
+
431
+ // Private Descriptor
432
+ Neo[getSetCache][_key] = {
433
+ get() {
434
+ return this.getConfig(key)?.get()
435
+ },
436
+ set(value) {
437
+ this.getConfig(key)?.setRaw(value)
438
+ }
439
+ }
440
+ }
441
+
442
+ Object.defineProperty(target, key, Neo[getSetCache][key]);
443
+ Object.defineProperty(target, _key, Neo[getSetCache][_key]);
444
+
445
+ if (initialValue !== undefined) {
446
+ target[key] = initialValue
447
+ }
448
+ },
449
+
287
450
  /**
288
451
  *
289
452
  */
290
453
  emptyFn() {},
291
454
 
455
+ /**
456
+ * Ensures a class is assigned to the Neo namespace only once, preventing duplicates.
457
+ * This is a lightweight version of `Neo.setupClass` for simple classes
458
+ * that do not extend `Neo.core.Base`.
459
+ * It follows a "first one wins" strategy.
460
+ *
461
+ * @param {Function|Object} module - The class constructor or singleton object to register.
462
+ * @param {String} classPath - The fully qualified name (e.g., 'Neo.core.Config').
463
+ * @param {Function} [onFirst] - An optional callback that runs only the first time the class is registered.
464
+ * @returns {Function|Object} The class or singleton from the Neo namespace (either the existing one or the newly set one).
465
+ */
466
+ gatekeep(module, classPath, onFirst) {
467
+ const existingClass = Neo.ns(classPath, false);
468
+
469
+ if (existingClass) {
470
+ return existingClass
471
+ }
472
+
473
+ const
474
+ nsArray = classPath.split('.'),
475
+ className = nsArray.pop(),
476
+ parentNs = Neo.ns(nsArray, true);
477
+
478
+ parentNs[className] = module;
479
+
480
+ onFirst?.(module);
481
+
482
+ return parentNs[className]
483
+ },
484
+
292
485
  /**
293
486
  * Checks if there is a set method for a given property key inside the prototype chain
294
487
  * @memberOf module:Neo
@@ -325,6 +518,10 @@ Neo = globalThis.Neo = Object.assign({
325
518
  return Neo.merge(Neo.merge(target, defaults), source)
326
519
  }
327
520
 
521
+ if (!target) {
522
+ return source
523
+ }
524
+
328
525
  for (const key in source) {
329
526
  const value = source[key];
330
527
 
@@ -338,6 +535,35 @@ Neo = globalThis.Neo = Object.assign({
338
535
  return target
339
536
  },
340
537
 
538
+ /**
539
+ * Merges a new value into an existing config value based on a specified strategy.
540
+ * This method is used during instance creation to apply merge strategies defined in config descriptors.
541
+ * @param {any} defaultValue - The default value of the config (from static config).
542
+ * @param {any} instanceValue - The value provided during instance creation.
543
+ * @param {string|Function} strategy - The merge strategy: 'shallow', 'deep', 'replace', or a custom function.
544
+ * @returns {any} The merged value.
545
+ */
546
+ mergeConfig(defaultValue, instanceValue, strategy) {
547
+ const
548
+ defaultValueType = Neo.typeOf(defaultValue),
549
+ instanceValueType = Neo.typeOf(instanceValue);
550
+
551
+ if (strategy === 'shallow') {
552
+ if (defaultValueType === 'Object' && instanceValueType === 'Object') {
553
+ return {...defaultValue, ...instanceValue}
554
+ }
555
+ } else if (strategy === 'deep') {
556
+ if (defaultValueType === 'Object' && instanceValueType === 'Object') {
557
+ return Neo.merge(Neo.clone(defaultValue, true), instanceValue)
558
+ }
559
+ } else if (typeof strategy === 'function') {
560
+ return strategy(defaultValue, instanceValue)
561
+ }
562
+
563
+ // Default to 'replace' or if strategy is not recognized
564
+ return instanceValue
565
+ },
566
+
341
567
  /**
342
568
  * Maps a className string into a given or global namespace
343
569
  * @example
@@ -438,36 +664,49 @@ Neo = globalThis.Neo = Object.assign({
438
664
  },
439
665
 
440
666
  /**
441
- * Updates the global Neo.config object across all active workers and connected browser windows.
442
- *
443
- * This is the unified entry point for changing global framework configurations.
444
- * The framework automatically handles the complex multi-threaded and multi-window
445
- * synchronization (via App Workers and Shared Workers, if active), ensuring
446
- * consistency across the entire application without boilerplate.
667
+ * This is the final and most critical step in the Neo.mjs class creation process.
668
+ * It is called at the end of every class module definition.
447
669
  *
448
- * You can pass a partial config object to update specific keys.
449
- * For nested objects, Neo.mjs performs a deep merge to preserve existing properties.
670
+ * `setupClass` performs several key operations:
671
+ * 1. **Mixed-Environment Gatekeeper:** It first checks if the class's namespace already exists.
672
+ * If it does, it immediately returns the existing class. This is the crucial "first comes wins"
673
+ * strategy that enables Neo.mjs to safely combine environments. For example, a bundled
674
+ * `dist/production` app can dynamically load an unbundled module from `dist/esm` at runtime.
675
+ * If that module imports a class already present in the main bundle, this check ensures the
676
+ * original, bundled class is used, preventing conflicts and maintaining application integrity.
677
+ * 1. **Configuration Merging:** It traverses the prototype chain to merge `static config`
678
+ * objects from parent classes into the current class, creating a unified `config`.
679
+ * 2. **Applying Overwrites:** It calls the static `applyOverwrites()` method on the class,
680
+ * allowing the global `Neo.overwrites` object to modify the class's default prototype
681
+ * configs. This is a key mechanism for external theming and configuration.
682
+ * 3. **Reactive Getter/Setter Generation:** For any config ending with an underscore (e.g., `myConfig_`),
683
+ * it automatically generates the corresponding public getter and setter. This enables optional
684
+ * lifecycle hooks that are called automatically if implemented on the class:
685
+ * - `beforeGetMyConfig(value)`
686
+ * - `beforeSetMyConfig(newValue, oldValue)`
687
+ * - `afterSetMyConfig(newValue, oldValue)`
688
+ * 4. **Prototype-based Configs:** Non-reactive configs (without an underscore) are set
689
+ * directly on the prototype for memory efficiency.
690
+ * 5. **Mixin Application:** It processes the `mixins` config to blend in functionality from
691
+ * other classes.
692
+ * 6. **Namespace Registration:** It registers the class in the global `Neo` namespace.
693
+ * 7. **Singleton Instantiation:** If the class is configured as a singleton, it creates the
694
+ * single instance.
450
695
  *
451
696
  * @memberOf module:Neo
452
- * @function setGlobalConfig
453
- * @param {Object} config The partial or full Neo.config object with changes to apply.
454
- */
455
-
456
- /**
457
- * Internally used at the end of each class / module definition
458
- * @memberOf module:Neo
459
697
  * @template T
460
- * @param {T} cls
461
- * @returns {T}
698
+ * @param {T} cls The class constructor to process.
699
+ * @returns {T} The processed and finalized class constructor or singleton instance.
462
700
  */
463
701
  setupClass(cls) {
464
- let baseCfg = null,
465
- ntypeChain = [],
466
- {ntypeMap} = Neo,
467
- proto = cls.prototype || cls,
468
- ns = Neo.ns(proto.constructor.config.className, false),
469
- protos = [],
470
- cfg, config, ctor, hierarchyInfo, ntype;
702
+ let baseConfig = null,
703
+ baseConfigDescriptors = null,
704
+ ntypeChain = [],
705
+ {ntypeMap} = Neo,
706
+ proto = cls.prototype || cls,
707
+ ns = Neo.ns(proto.constructor.config.className, false),
708
+ protos = [],
709
+ cfg, config, configDescriptors, ctor, hierarchyInfo, ntype;
471
710
 
472
711
  /*
473
712
  * If the namespace already exists, directly return it.
@@ -481,12 +720,16 @@ Neo = globalThis.Neo = Object.assign({
481
720
  return ns
482
721
  }
483
722
 
723
+ // Traverse the prototype chain to collect inherited configs and descriptors
484
724
  while (proto.__proto__) {
485
725
  ctor = proto.constructor;
486
726
 
727
+ // If a class in the prototype chain has already had its config applied,
728
+ // we can use its pre-processed config and descriptors as a base.
487
729
  if (Object.hasOwn(ctor, 'classConfigApplied')) {
488
- baseCfg = Neo.clone(ctor.config, true);
489
- ntypeChain = [...ctor.ntypeChain];
730
+ baseConfig = Neo.clone(ctor.config, true);
731
+ baseConfigDescriptors = Neo.clone(ctor.configDescriptors, true);
732
+ ntypeChain = [...ctor.ntypeChain];
490
733
  break
491
734
  }
492
735
 
@@ -494,29 +737,43 @@ Neo = globalThis.Neo = Object.assign({
494
737
  proto = proto.__proto__
495
738
  }
496
739
 
497
- config = baseCfg || {};
740
+ // Initialize accumulated config and descriptors
741
+ config = baseConfig || {};
742
+ configDescriptors = baseConfigDescriptors || {};
498
743
 
744
+ // Process each class in the prototype chain (from top to bottom)
499
745
  protos.forEach(element => {
500
746
  let mixins;
501
747
 
502
748
  ctor = element.constructor;
503
-
504
- cfg = ctor.config || {};
749
+ cfg = ctor.config || {};
505
750
 
506
751
  if (Neo.overwrites) {
507
752
  ctor.applyOverwrites?.(cfg)
508
753
  }
509
754
 
755
+ // Process each config property defined in the current class's static config
510
756
  Object.entries(cfg).forEach(([key, value]) => {
511
- if (key.slice(-1) === '_') {
512
- delete cfg[key];
513
- key = key.slice(0, -1);
514
- cfg[key] = value;
515
- autoGenerateGetSet(element, key)
757
+ const
758
+ isReactive = key.slice(-1) === '_',
759
+ baseKey = isReactive ? key.slice(0, -1) : key;
760
+
761
+ // 1. Handle descriptors: If the value is a descriptor object, store it.
762
+ // The 'value' property of the descriptor is then used as the actual config value.
763
+ if (Neo.isObject(value) && value[isDescriptor] === true) {
764
+ ctor.configDescriptors ??= {};
765
+ ctor.configDescriptors[baseKey] = Neo.clone(value, true); // Deep clone to prevent mutation
766
+ value = value.value // Use the descriptor's value as the config value
516
767
  }
517
768
 
518
- // Only apply properties which have no setters inside the prototype chain.
519
- // Those will get applied on create (Neo.core.Base -> initConfig)
769
+ // 2. Handle reactive vs. non-reactive configs: Generate getters/setters for reactive configs.
770
+ if (isReactive) {
771
+ delete cfg[key]; // Remove original key with underscore
772
+ cfg[baseKey] = value; // Use the potentially modified value
773
+ Neo.createConfig(element, baseKey)
774
+ }
775
+ // This part handles non-reactive configs (including those that were descriptors)
776
+ // If no property setter exists, define it directly on the prototype.
520
777
  else if (!Neo.hasPropertySetter(element, key)) {
521
778
  Object.defineProperty(element, key, {
522
779
  enumerable: true,
@@ -526,6 +783,17 @@ Neo = globalThis.Neo = Object.assign({
526
783
  }
527
784
  });
528
785
 
786
+ // Merge configDescriptors: Apply "first-defined wins" strategy.
787
+ // If a descriptor for a key already exists (from a parent class), it is not overwritten.
788
+ if (ctor.configDescriptors) {
789
+ for (const key in ctor.configDescriptors) {
790
+ if (!Object.hasOwn(configDescriptors, key)) {
791
+ configDescriptors[key] = Neo.clone(ctor.configDescriptors[key], true) // Deep clone for immutability
792
+ }
793
+ }
794
+ }
795
+
796
+ // Process ntype and ntypeChain
529
797
  if (Object.hasOwn(cfg, 'ntype')) {
530
798
  ntype = cfg.ntype;
531
799
 
@@ -540,6 +808,7 @@ Neo = globalThis.Neo = Object.assign({
540
808
  ntypeMap[ntype] = cfg.className
541
809
  }
542
810
 
811
+ // Process mixins
543
812
  mixins = Object.hasOwn(config, 'mixins') && config.mixins || [];
544
813
 
545
814
  if (ctor.observable) {
@@ -551,7 +820,7 @@ Neo = globalThis.Neo = Object.assign({
551
820
  }
552
821
 
553
822
  if (mixins.length > 0) {
554
- applyMixins(ctor, mixins);
823
+ applyMixins(ctor, mixins, cfg);
555
824
 
556
825
  if (Neo.ns('Neo.core.Observable', false, ctor.prototype.mixins)) {
557
826
  ctor.observable = true
@@ -561,29 +830,45 @@ Neo = globalThis.Neo = Object.assign({
561
830
  delete cfg.mixins;
562
831
  delete config.mixins;
563
832
 
564
- Object.assign(config, cfg);
833
+ // Hierarchical merging of static config values based on descriptors.
834
+ // This ensures that values are merged (e.g., shallow/deep) instead of simply overwritten.
835
+ Object.entries(cfg).forEach(([key, value]) => {
836
+ const descriptor = configDescriptors[key];
565
837
 
838
+ if (descriptor?.merge) {
839
+ config[key] = Neo.mergeConfig(config[key], value, descriptor.merge)
840
+ } else {
841
+ config[key] = value
842
+ }
843
+ });
844
+
845
+ // Assign final processed config and descriptors to the class constructor
566
846
  Object.assign(ctor, {
567
847
  classConfigApplied: true,
568
- config : Neo.clone(config, true),
848
+ config : Neo.clone(config, true), // Deep clone final config for immutability
849
+ configDescriptors : Neo.clone(configDescriptors, true), // Deep clone final descriptors for immutability
569
850
  isClass : true,
570
851
  ntypeChain
571
852
  });
572
853
 
854
+ // Apply to global namespace if not a singleton
573
855
  !config.singleton && this.applyToGlobalNs(cls)
574
856
  });
575
857
 
576
858
  proto = cls.prototype || cls;
577
859
 
860
+ // Add is<Ntype> flags to the prototype
578
861
  ntypeChain.forEach(ntype => {
579
862
  proto[`is${Neo.capitalize(Neo.camel(ntype))}`] = true
580
863
  });
581
864
 
865
+ // If it's a singleton, create and apply the instance to the global namespace
582
866
  if (proto.singleton) {
583
867
  cls = Neo.create(cls);
584
868
  Neo.applyToGlobalNs(cls)
585
869
  }
586
870
 
871
+ // Add class hierarchy information to the manager or a temporary map
587
872
  hierarchyInfo = {
588
873
  className : proto.className,
589
874
  module : cls,
@@ -606,11 +891,12 @@ Neo = globalThis.Neo = Object.assign({
606
891
  * @returns {String|null}
607
892
  */
608
893
  typeOf(item) {
609
- if (item === null || item === undefined) {
894
+ // Return null for null or undefined
895
+ if (item == null) {
610
896
  return null
611
897
  }
612
898
 
613
- return typeDetector[typeof item]?.(item) || item.constructor.name
899
+ return typeDetector[typeof item]?.(item) || item.constructor?.name
614
900
  }
615
901
  }, Neo);
616
902
 
@@ -624,6 +910,7 @@ const ignoreMixin = [
624
910
  'classConfigApplied',
625
911
  'className',
626
912
  'constructor',
913
+ 'id',
627
914
  'isClass',
628
915
  'mixin',
629
916
  'ntype',
@@ -636,9 +923,10 @@ const ignoreMixin = [
636
923
  /**
637
924
  * @param {Neo.core.Base} cls
638
925
  * @param {Array} mixins
926
+ * @param {Object} classConfig
639
927
  * @private
640
928
  */
641
- function applyMixins(cls, mixins) {
929
+ function applyMixins(cls, mixins, classConfig) {
642
930
  if (!Array.isArray(mixins)) {
643
931
  mixins = [mixins];
644
932
  }
@@ -660,119 +948,17 @@ function applyMixins(cls, mixins) {
660
948
  }
661
949
 
662
950
  mixinCls = Neo.ns(mixin);
663
- mixinProto = mixinCls.prototype;
951
+ mixinProto = mixinCls.prototype
664
952
  }
665
953
 
666
954
  mixinProto.className.split('.').reduce(mixReduce(mixinCls), mixinClasses);
667
955
 
668
- Object.getOwnPropertyNames(mixinProto).forEach(mixinProperty(cls.prototype, mixinProto))
956
+ Object.entries(Object.getOwnPropertyDescriptors(mixinProto)).forEach(mixinProperty(cls.prototype, mixinProto, classConfig))
669
957
  }
670
958
 
671
959
  cls.prototype.mixins = mixinClasses // todo: we should do a deep merge
672
960
  }
673
961
 
674
- /**
675
- * Creates get / set methods for class configs ending with an underscore
676
- * @param {Neo.core.Base} proto
677
- * @param {String} key
678
- * @private
679
- * @tutorial 02_ClassSystem
680
- */
681
- function autoGenerateGetSet(proto, key) {
682
- if (Neo.hasPropertySetter(proto, key)) {
683
- throw('Config ' + key + '_ (' + proto.className + ') already has a set method, use beforeGet, beforeSet & afterSet instead')
684
- }
685
-
686
- const
687
- _key = '_' + key,
688
- uKey = key[0].toUpperCase() + key.slice(1),
689
- beforeGet = 'beforeGet' + uKey,
690
- beforeSet = 'beforeSet' + uKey,
691
- afterSet = 'afterSet' + uKey;
692
-
693
- if (!Neo[getSetCache]) {
694
- Neo[getSetCache] = {}
695
- }
696
-
697
- if (!Neo[getSetCache][key]) {
698
- const publicDescriptor = {
699
- get() {
700
- let me = this,
701
- hasNewKey = Object.hasOwn(me[configSymbol], key),
702
- newKey = me[configSymbol][key],
703
- value = hasNewKey ? newKey : me[_key];
704
-
705
- if (Array.isArray(value)) {
706
- if (key !== 'items') {
707
- value = [...value]
708
- }
709
- } else if (value instanceof Date) {
710
- value = new Date(value.valueOf())
711
- }
712
-
713
- if (hasNewKey) {
714
- me[key] = value; // We do want to trigger the setter => beforeSet, afterSet
715
- value = me[_key]; // Return the value parsed by the setter
716
- delete me[configSymbol][key]
717
- }
718
-
719
- if (typeof me[beforeGet] === 'function') {
720
- value = me[beforeGet](value)
721
- }
722
-
723
- return value
724
- },
725
- set(value) {
726
- if (value === undefined) return;
727
-
728
- const config = this.getConfig(key);
729
- if (!config) return;
730
-
731
- let me = this,
732
- oldValue = config.get(); // Get the old value from the Config instance
733
-
734
- // every set call has to delete the matching symbol
735
- delete me[configSymbol][key];
736
-
737
- if (key !== 'items' && key !== 'vnode') {
738
- value = Neo.clone(value, true, true)
739
- }
740
-
741
- if (typeof me[beforeSet] === 'function') {
742
- value = me[beforeSet](value, oldValue);
743
-
744
- // If they don't return a value, that means no change
745
- if (value === undefined) {
746
- return
747
- }
748
- }
749
-
750
- // Set the new value into the Config instance
751
- // The config.set() method will return true if the value actually changed.
752
- if (config.set(value)) {
753
- me[afterSet]?.(value, oldValue);
754
- me.afterSetConfig?.(key, value, oldValue)
755
- }
756
- }
757
- };
758
-
759
- const privateDescriptor = {
760
- get() {
761
- return this.getConfig(key)?.get();
762
- },
763
- set(value) {
764
- this.getConfig(key)?.setRaw(value);
765
- }
766
- };
767
-
768
- Neo[getSetCache][key] = publicDescriptor;
769
- Neo[getSetCache][_key] = privateDescriptor;
770
- }
771
-
772
- Object.defineProperty(proto, key, Neo[getSetCache][key]);
773
- Object.defineProperty(proto, _key, Neo[getSetCache][_key])
774
- }
775
-
776
962
  /**
777
963
  * @param {Boolean} create
778
964
  * @param {Object} current
@@ -791,9 +977,7 @@ function createArrayNs(create, current, prev) {
791
977
  arrRoot = prev[arrDetails[0]]
792
978
  }
793
979
 
794
- if (!arrRoot) {
795
- return
796
- }
980
+ if (!arrRoot) return;
797
981
 
798
982
  for (; i < len; i++) {
799
983
  arrItem = parseInt(arrDetails[i]);
@@ -827,12 +1011,27 @@ function exists(className) {
827
1011
  /**
828
1012
  * @param {Neo.core.Base} proto
829
1013
  * @param {Neo.core.Base} mixinProto
1014
+ * @param {Object} classConfig
830
1015
  * @returns {Function}
831
1016
  * @private
832
1017
  */
833
- function mixinProperty(proto, mixinProto) {
834
- return function(key) {
835
- if (~ignoreMixin.indexOf(key)) {
1018
+ function mixinProperty(proto, mixinProto, classConfig) {
1019
+ return function([key, descriptor]) {
1020
+ if (ignoreMixin.includes(key)) return;
1021
+
1022
+ // Mixins must not override existing class properties with a setter
1023
+ if (Neo.hasPropertySetter(proto, key)) return;
1024
+
1025
+ // Reactive neo configs, or public class fields defined via get() AND set()
1026
+ if (descriptor.get && descriptor.set) {
1027
+ Neo.createConfig(proto, key);
1028
+
1029
+ const mixinClassConfig = mixinProto.constructor.config;
1030
+
1031
+ if (Object.hasOwn(mixinClassConfig, key)) {
1032
+ classConfig[key] = mixinClassConfig[key];
1033
+ }
1034
+
836
1035
  return
837
1036
  }
838
1037
 
@@ -879,7 +1078,7 @@ function parseArrayFromString(str) {
879
1078
  )
880
1079
  }
881
1080
 
882
- Neo.config = Neo.config || {};
1081
+ Neo.config ??= {};
883
1082
 
884
1083
  Neo.assignDefaults(Neo.config, DefaultConfig);
885
1084