neo.mjs 10.0.0-beta.4 → 10.0.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (485) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.4.md +2 -2
  2. package/.github/RELEASE_NOTES/v10.0.0-beta.5.md +70 -0
  3. package/.github/RELEASE_NOTES/v10.0.0-beta.6.md +48 -0
  4. package/.github/epic-functional-components.md +498 -0
  5. package/.github/ticket-asymmetric-vdom-updates.md +122 -0
  6. package/README.md +0 -3
  7. package/ServiceWorker.mjs +2 -2
  8. package/apps/colors/store/Colors.mjs +1 -0
  9. package/apps/colors/view/GridContainer.mjs +3 -0
  10. package/apps/colors/view/HeaderToolbar.mjs +2 -0
  11. package/apps/colors/view/Viewport.mjs +3 -0
  12. package/apps/covid/view/FooterContainer.mjs +3 -0
  13. package/apps/covid/view/GalleryContainer.mjs +2 -0
  14. package/apps/covid/view/GalleryContainerController.mjs +1 -0
  15. package/apps/covid/view/HeaderContainer.mjs +2 -0
  16. package/apps/covid/view/HelixContainer.mjs +2 -0
  17. package/apps/covid/view/HelixContainerController.mjs +1 -0
  18. package/apps/covid/view/MainContainer.mjs +3 -0
  19. package/apps/covid/view/TableContainer.mjs +3 -0
  20. package/apps/covid/view/TableContainerController.mjs +1 -0
  21. package/apps/covid/view/WorldMapContainer.mjs +2 -0
  22. package/apps/covid/view/country/Gallery.mjs +3 -0
  23. package/apps/covid/view/country/Helix.mjs +8 -0
  24. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -0
  25. package/apps/covid/view/country/Table.mjs +2 -0
  26. package/apps/covid/view/mapboxGl/Component.mjs +1 -0
  27. package/apps/covid/view/mapboxGl/Container.mjs +2 -0
  28. package/apps/email/EPIC_PLAN.md +58 -0
  29. package/apps/email/neo-config.json +2 -2
  30. package/apps/email/store/Emails.mjs +11 -1
  31. package/apps/email/view/ComposeView.mjs +44 -0
  32. package/apps/email/view/MainView.mjs +89 -0
  33. package/apps/email/view/Viewport.mjs +4 -33
  34. package/apps/email/view/ViewportStateProvider.mjs +3 -3
  35. package/apps/form/store/SideNav.mjs +1 -0
  36. package/apps/form/view/FormContainer.mjs +1 -0
  37. package/apps/form/view/FormPageContainer.mjs +2 -0
  38. package/apps/form/view/SideNavList.mjs +1 -0
  39. package/apps/form/view/Viewport.mjs +3 -0
  40. package/apps/portal/childapps/preview/MainContainer.mjs +1 -0
  41. package/apps/portal/index.html +1 -1
  42. package/apps/portal/store/BlogPosts.mjs +2 -0
  43. package/apps/portal/store/Content.mjs +1 -0
  44. package/apps/portal/store/ContentSections.mjs +1 -0
  45. package/apps/portal/store/Examples.mjs +1 -0
  46. package/apps/portal/view/HeaderToolbar.mjs +1 -0
  47. package/apps/portal/view/Viewport.mjs +5 -0
  48. package/apps/portal/view/ViewportController.mjs +8 -2
  49. package/apps/portal/view/about/Container.mjs +2 -0
  50. package/apps/portal/view/about/MemberContainer.mjs +7 -0
  51. package/apps/portal/view/blog/Container.mjs +2 -0
  52. package/apps/portal/view/blog/List.mjs +2 -0
  53. package/apps/portal/view/examples/List.mjs +1 -0
  54. package/apps/portal/view/examples/TabContainer.mjs +4 -0
  55. package/apps/portal/view/home/ContentBox.mjs +3 -0
  56. package/apps/portal/view/home/FeatureSection.mjs +8 -0
  57. package/apps/portal/view/home/FooterContainer.mjs +4 -1
  58. package/apps/portal/view/home/MainContainer.mjs +2 -0
  59. package/apps/portal/view/home/parts/AfterMath.mjs +2 -0
  60. package/apps/portal/view/home/parts/BaseContainer.mjs +1 -0
  61. package/apps/portal/view/home/parts/Colors.mjs +4 -0
  62. package/apps/portal/view/home/parts/Features.mjs +2 -0
  63. package/apps/portal/view/home/parts/Helix.mjs +5 -0
  64. package/apps/portal/view/home/parts/How.mjs +4 -0
  65. package/apps/portal/view/home/parts/MainNeo.mjs +1 -0
  66. package/apps/portal/view/home/parts/References.mjs +2 -0
  67. package/apps/portal/view/learn/ContentComponent.mjs +11 -5
  68. package/apps/portal/view/learn/ContentTreeList.mjs +2 -0
  69. package/apps/portal/view/learn/CubeLayoutButton.mjs +1 -0
  70. package/apps/portal/view/learn/MainContainer.mjs +4 -0
  71. package/apps/portal/view/learn/PageContainer.mjs +2 -0
  72. package/apps/portal/view/learn/PageSectionsContainer.mjs +3 -0
  73. package/apps/portal/view/learn/PageSectionsList.mjs +1 -0
  74. package/apps/portal/view/services/Component.mjs +1 -0
  75. package/apps/realworld/api/Base.mjs +1 -0
  76. package/apps/realworld/view/HeaderComponent.mjs +4 -0
  77. package/apps/realworld/view/HomeComponent.mjs +7 -0
  78. package/apps/realworld/view/MainContainer.mjs +2 -0
  79. package/apps/realworld/view/MainContainerController.mjs +2 -0
  80. package/apps/realworld/view/article/CommentComponent.mjs +3 -0
  81. package/apps/realworld/view/article/Component.mjs +17 -10
  82. package/apps/realworld/view/article/CreateCommentComponent.mjs +2 -0
  83. package/apps/realworld/view/article/CreateComponent.mjs +5 -0
  84. package/apps/realworld/view/article/PreviewComponent.mjs +9 -0
  85. package/apps/realworld/view/article/TagListComponent.mjs +2 -0
  86. package/apps/realworld/view/user/ProfileComponent.mjs +7 -0
  87. package/apps/realworld/view/user/SettingsComponent.mjs +5 -0
  88. package/apps/realworld/view/user/SignUpComponent.mjs +3 -0
  89. package/apps/realworld2/api/Base.mjs +1 -0
  90. package/apps/realworld2/view/FooterComponent.mjs +1 -0
  91. package/apps/realworld2/view/HeaderToolbar.mjs +3 -0
  92. package/apps/realworld2/view/HomeContainer.mjs +1 -0
  93. package/apps/realworld2/view/MainContainer.mjs +2 -0
  94. package/apps/realworld2/view/MainContainerController.mjs +1 -0
  95. package/apps/realworld2/view/article/Helix.mjs +1 -0
  96. package/apps/realworld2/view/article/PreviewComponent.mjs +9 -0
  97. package/apps/realworld2/view/article/PreviewList.mjs +1 -0
  98. package/apps/realworld2/view/article/TagListComponent.mjs +2 -0
  99. package/apps/route/view/CenterContainer.mjs +1 -0
  100. package/apps/route/view/MainView.mjs +1 -0
  101. package/apps/sharedcovid/childapps/sharedcovidchart/MainContainer.mjs +1 -0
  102. package/apps/sharedcovid/childapps/sharedcovidgallery/MainContainer.mjs +1 -0
  103. package/apps/sharedcovid/childapps/sharedcovidhelix/MainContainer.mjs +1 -0
  104. package/apps/sharedcovid/childapps/sharedcovidmap/MainContainer.mjs +1 -0
  105. package/apps/sharedcovid/view/FooterContainer.mjs +3 -0
  106. package/apps/sharedcovid/view/GalleryContainer.mjs +2 -0
  107. package/apps/sharedcovid/view/GalleryContainerController.mjs +1 -0
  108. package/apps/sharedcovid/view/HeaderContainer.mjs +2 -0
  109. package/apps/sharedcovid/view/HelixContainer.mjs +2 -0
  110. package/apps/sharedcovid/view/HelixContainerController.mjs +1 -0
  111. package/apps/sharedcovid/view/MainContainer.mjs +3 -0
  112. package/apps/sharedcovid/view/TableContainer.mjs +3 -0
  113. package/apps/sharedcovid/view/TableContainerController.mjs +1 -0
  114. package/apps/sharedcovid/view/WorldMapContainer.mjs +2 -0
  115. package/apps/sharedcovid/view/country/Gallery.mjs +3 -0
  116. package/apps/sharedcovid/view/country/Helix.mjs +8 -0
  117. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -0
  118. package/apps/sharedcovid/view/country/Table.mjs +2 -0
  119. package/apps/sharedcovid/view/mapboxGl/Component.mjs +1 -0
  120. package/apps/sharedcovid/view/mapboxGl/Container.mjs +2 -0
  121. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +2 -0
  122. package/apps/shareddialog/view/DemoDialog.mjs +2 -0
  123. package/apps/shareddialog/view/MainContainer.mjs +2 -0
  124. package/apps/shareddialog/view/MainContainerController.mjs +1 -0
  125. package/buildScripts/addReactiveTags.mjs +191 -0
  126. package/buildScripts/checkReactiveTags.mjs +160 -0
  127. package/docs/app/store/Api.mjs +1 -0
  128. package/docs/app/store/Examples.mjs +1 -0
  129. package/docs/app/view/ApiTreeList.mjs +1 -0
  130. package/docs/app/view/ContentTabContainer.mjs +2 -0
  131. package/docs/app/view/ExamplesTreeList.mjs +2 -0
  132. package/docs/app/view/HeaderContainer.mjs +3 -0
  133. package/docs/app/view/MainContainer.mjs +5 -0
  134. package/docs/app/view/classdetails/HeaderComponent.mjs +1 -0
  135. package/docs/app/view/classdetails/MainContainer.mjs +3 -0
  136. package/docs/app/view/classdetails/MembersList.mjs +5 -0
  137. package/docs/app/view/classdetails/SourceViewComponent.mjs +2 -0
  138. package/examples/ConfigurationViewport.mjs +14 -8
  139. package/examples/button/effect/MainContainer.mjs +207 -0
  140. package/examples/button/effect/app.mjs +6 -0
  141. package/examples/button/effect/index.html +11 -0
  142. package/examples/button/effect/neo-config.json +6 -0
  143. package/examples/calendar/weekview/MainContainer.mjs +4 -0
  144. package/examples/component/coronaGallery/CountryGallery.mjs +2 -0
  145. package/examples/component/coronaGallery/CountryStore.mjs +1 -0
  146. package/examples/component/coronaGallery/Viewport.mjs +3 -0
  147. package/examples/component/coronaGallery/ViewportController.mjs +1 -0
  148. package/examples/component/coronaHelix/CountryHelix.mjs +7 -0
  149. package/examples/component/coronaHelix/MainContainer.mjs +1 -0
  150. package/examples/component/gallery/ImageStore.mjs +1 -0
  151. package/examples/component/helix/ImageStore.mjs +1 -0
  152. package/examples/component/helix/Viewport.mjs +3 -0
  153. package/examples/component/helix/ViewportController.mjs +1 -0
  154. package/examples/component/multiWindowCoronaGallery/childapp/Viewport.mjs +1 -0
  155. package/examples/component/multiWindowHelix/childapp/Viewport.mjs +1 -0
  156. package/examples/component/wrapper/googleMaps/MapComponent.mjs +2 -0
  157. package/examples/core/config/MainContainer.mjs +2 -0
  158. package/examples/dialog/DemoDialog.mjs +2 -0
  159. package/examples/dialog/MainContainer.mjs +1 -0
  160. package/examples/form/field/color/MainStore.mjs +1 -0
  161. package/examples/functional/defineComponent/Component.mjs +18 -0
  162. package/examples/functional/defineComponent/MainContainer.mjs +41 -0
  163. package/examples/functional/defineComponent/app.mjs +6 -0
  164. package/examples/functional/defineComponent/index.html +11 -0
  165. package/examples/functional/defineComponent/neo-config.json +6 -0
  166. package/examples/functional/hostComponent/Component.mjs +32 -0
  167. package/examples/functional/hostComponent/MainContainer.mjs +48 -0
  168. package/examples/functional/hostComponent/app.mjs +6 -0
  169. package/examples/functional/hostComponent/index.html +11 -0
  170. package/examples/functional/hostComponent/neo-config.json +6 -0
  171. package/examples/grid/animatedRowSorting/Viewport.mjs +1 -1
  172. package/examples/grid/bigData/ControlsContainer.mjs +3 -0
  173. package/examples/grid/bigData/GridContainer.mjs +4 -2
  174. package/examples/grid/bigData/MainContainer.mjs +2 -0
  175. package/examples/grid/bigData/MainModel.mjs +1 -0
  176. package/examples/grid/bigData/MainStore.mjs +3 -0
  177. package/examples/grid/cellEditing/MainContainer.mjs +1 -1
  178. package/examples/grid/container/MainContainer.mjs +1 -1
  179. package/examples/grid/covid/GridContainer.mjs +3 -0
  180. package/examples/grid/covid/MainContainer.mjs +2 -0
  181. package/examples/grid/covid/Store.mjs +1 -0
  182. package/examples/grid/nestedRecordFields/EditUserDialog.mjs +3 -0
  183. package/examples/grid/nestedRecordFields/Viewport.mjs +3 -1
  184. package/examples/list/animate/List.mjs +4 -0
  185. package/examples/list/animate/MainContainer.mjs +2 -0
  186. package/examples/list/circle/MainStore.mjs +1 -0
  187. package/examples/list/color/MainStore.mjs +1 -0
  188. package/examples/preloadingAssets/view/MainContainer.mjs +2 -0
  189. package/examples/stateProvider/advanced/MainContainer.mjs +1 -0
  190. package/examples/stateProvider/dialog/EditUserDialog.mjs +2 -0
  191. package/examples/stateProvider/dialog/MainContainer.mjs +1 -0
  192. package/examples/stateProvider/extendedClass/MainContainer.mjs +2 -0
  193. package/examples/stateProvider/inline/MainContainer.mjs +1 -0
  194. package/examples/stateProvider/inlineNoStateProvider/MainContainer.mjs +1 -0
  195. package/examples/stateProvider/inlineNoStateProvider/MainContainerController.mjs +2 -0
  196. package/examples/stateProvider/multiWindow/EditUserDialog.mjs +3 -0
  197. package/examples/stateProvider/multiWindow/MainContainer.mjs +1 -0
  198. package/examples/stateProvider/multiWindow/Viewport.mjs +1 -0
  199. package/examples/stateProvider/nestedData/MainContainer.mjs +1 -0
  200. package/examples/stateProvider/table/MainContainer.mjs +1 -0
  201. package/examples/table/covid/MainContainer.mjs +2 -0
  202. package/examples/table/covid/Store.mjs +1 -0
  203. package/examples/table/covid/TableContainer.mjs +3 -0
  204. package/examples/table/nestedRecordFields/EditUserDialog.mjs +3 -0
  205. package/examples/table/nestedRecordFields/Viewport.mjs +1 -0
  206. package/examples/todoList/version1/MainComponent.mjs +1 -1
  207. package/examples/toolbar/breadcrumb/view/MainContainer.mjs +2 -0
  208. package/examples/toolbar/paging/store/Users.mjs +1 -0
  209. package/examples/toolbar/paging/view/AddUserDialog.mjs +3 -0
  210. package/examples/toolbar/paging/view/MainContainer.mjs +3 -0
  211. package/examples/treeAccordion/MainContainer.mjs +2 -2
  212. package/examples/worker/task/MainContainer.mjs +1 -0
  213. package/learn/Glossary.md +1 -0
  214. package/learn/UsingTheseTopics.md +1 -0
  215. package/learn/benefits/ConfigSystem.md +2 -0
  216. package/learn/benefits/Effort.md +1 -0
  217. package/learn/benefits/Features.md +1 -0
  218. package/learn/benefits/FormsEngine.md +1 -0
  219. package/learn/benefits/FourEnvironments.md +2 -0
  220. package/learn/benefits/Introduction.md +2 -0
  221. package/learn/benefits/MultiWindow.md +3 -1
  222. package/learn/benefits/OffTheMainThread.md +2 -0
  223. package/learn/benefits/Quick.md +2 -0
  224. package/learn/benefits/RPCLayer.md +2 -0
  225. package/learn/benefits/Speed.md +2 -0
  226. package/learn/comparisons/NeoVsAngular.md +90 -0
  227. package/learn/comparisons/NeoVsExtJs.md +178 -0
  228. package/learn/comparisons/NeoVsNextJs.md +124 -0
  229. package/learn/comparisons/NeoVsReact.md +95 -0
  230. package/learn/comparisons/NeoVsSolid.md +78 -0
  231. package/learn/comparisons/NeoVsVue.md +92 -0
  232. package/learn/comparisons/Overview.md +46 -0
  233. package/learn/gettingstarted/ComponentModels.md +2 -0
  234. package/learn/gettingstarted/Config.md +2 -0
  235. package/learn/gettingstarted/DescribingTheUI.md +2 -0
  236. package/learn/gettingstarted/Events.md +2 -0
  237. package/learn/gettingstarted/Extending.md +2 -0
  238. package/learn/gettingstarted/References.md +2 -0
  239. package/learn/gettingstarted/Setup.md +3 -2
  240. package/learn/gettingstarted/Workspaces.md +2 -0
  241. package/learn/guides/datahandling/Collections.md +1 -0
  242. package/learn/guides/datahandling/Records.md +1 -0
  243. package/learn/guides/datahandling/StateProviders.md +131 -16
  244. package/learn/guides/datahandling/Tables.md +1 -1
  245. package/learn/guides/fundamentals/ConfigSystemDeepDive.md +1 -0
  246. package/learn/guides/fundamentals/DeclarativeComponentTreesVsImperativeVdom.md +2 -0
  247. package/learn/guides/fundamentals/DeclarativeVDOMWithEffects.md +168 -0
  248. package/learn/guides/fundamentals/ExtendingNeoClasses.md +1 -0
  249. package/learn/guides/fundamentals/InstanceLifecycle.md +3 -1
  250. package/learn/guides/fundamentals/MainThreadAddons.md +2 -0
  251. package/learn/guides/specificfeatures/Mixins.md +3 -1
  252. package/learn/guides/specificfeatures/MultiWindow.md +3 -1
  253. package/learn/guides/specificfeatures/PortalApp.md +2 -0
  254. package/learn/guides/uibuildingblocks/ComponentsAndContainers.md +2 -0
  255. package/learn/guides/uibuildingblocks/CustomComponents.md +2 -0
  256. package/learn/guides/uibuildingblocks/Layouts.md +2 -0
  257. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +2 -0
  258. package/learn/guides/userinteraction/Forms.md +2 -0
  259. package/learn/guides/userinteraction/events/CustomEvents.md +2 -1
  260. package/learn/guides/userinteraction/events/DomEvents.md +2 -0
  261. package/learn/javascript/ClassFeatures.md +4 -3
  262. package/learn/javascript/Classes.md +10 -13
  263. package/learn/javascript/Overrides.md +10 -6
  264. package/learn/javascript/Super.md +12 -8
  265. package/learn/tree.json +71 -63
  266. package/learn/tutorials/Earthquakes.md +2 -0
  267. package/learn/tutorials/RSP.md +3 -1
  268. package/learn/tutorials/TodoList.md +103 -7
  269. package/package.json +6 -4
  270. package/resources/scss/src/apps/email/ComposeView.scss +16 -0
  271. package/resources/scss/src/apps/email/MainView.scss +5 -0
  272. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +5 -4
  273. package/src/DefaultConfig.mjs +12 -2
  274. package/src/Main.mjs +1 -0
  275. package/src/Neo.mjs +377 -178
  276. package/src/Xhr.mjs +1 -0
  277. package/src/button/Base.mjs +13 -0
  278. package/src/button/Effect.mjs +449 -0
  279. package/src/button/Split.mjs +2 -0
  280. package/src/calendar/store/Calendars.mjs +1 -0
  281. package/src/calendar/store/Colors.mjs +1 -0
  282. package/src/calendar/store/Events.mjs +1 -0
  283. package/src/calendar/view/DayComponent.mjs +2 -0
  284. package/src/calendar/view/EditEventContainer.mjs +4 -1
  285. package/src/calendar/view/MainContainer.mjs +13 -0
  286. package/src/calendar/view/MainContainerStateProvider.mjs +14 -28
  287. package/src/calendar/view/SettingsContainer.mjs +1 -0
  288. package/src/calendar/view/YearComponent.mjs +16 -0
  289. package/src/calendar/view/calendars/ColorsList.mjs +2 -0
  290. package/src/calendar/view/calendars/Container.mjs +2 -0
  291. package/src/calendar/view/calendars/EditContainer.mjs +1 -0
  292. package/src/calendar/view/month/Component.mjs +11 -0
  293. package/src/calendar/view/settings/GeneralContainer.mjs +1 -0
  294. package/src/calendar/view/settings/MonthContainer.mjs +1 -0
  295. package/src/calendar/view/settings/WeekContainer.mjs +1 -0
  296. package/src/calendar/view/settings/YearContainer.mjs +1 -0
  297. package/src/calendar/view/week/Component.mjs +15 -1
  298. package/src/calendar/view/week/TimeAxisComponent.mjs +4 -0
  299. package/src/code/LivePreview.mjs +51 -23
  300. package/src/collection/Base.mjs +14 -12
  301. package/src/collection/Filter.mjs +6 -0
  302. package/src/collection/Sorter.mjs +3 -0
  303. package/src/component/Base.mjs +156 -802
  304. package/src/component/Canvas.mjs +1 -0
  305. package/src/component/Chip.mjs +4 -0
  306. package/src/component/Circle.mjs +14 -0
  307. package/src/component/Clock.mjs +4 -0
  308. package/src/component/DateSelector.mjs +12 -0
  309. package/src/component/Gallery.mjs +11 -0
  310. package/src/component/Helix.mjs +24 -0
  311. package/src/component/Label.mjs +1 -0
  312. package/src/component/Legend.mjs +3 -0
  313. package/src/component/MagicMoveText.mjs +4 -0
  314. package/src/component/Progress.mjs +3 -0
  315. package/src/component/Splitter.mjs +3 -0
  316. package/src/component/StatusBadge.mjs +6 -0
  317. package/src/component/Timer.mjs +4 -0
  318. package/src/component/Toast.mjs +6 -0
  319. package/src/component/Video.mjs +1 -0
  320. package/src/component/mwc/Button.mjs +7 -0
  321. package/src/component/mwc/TextField.mjs +9 -0
  322. package/src/component/wrapper/AmChart.mjs +2 -0
  323. package/src/component/wrapper/GoogleMaps.mjs +3 -0
  324. package/src/component/wrapper/MapboxGL.mjs +5 -0
  325. package/src/component/wrapper/MonacoEditor.mjs +12 -0
  326. package/src/container/Accordion.mjs +2 -0
  327. package/src/container/Base.mjs +34 -26
  328. package/src/container/Panel.mjs +1 -0
  329. package/src/container/Viewport.mjs +1 -0
  330. package/src/controller/Application.mjs +1 -0
  331. package/src/controller/Base.mjs +1 -0
  332. package/src/controller/Component.mjs +1 -0
  333. package/src/core/Base.mjs +193 -22
  334. package/src/core/Compare.mjs +4 -7
  335. package/src/core/Config.mjs +137 -33
  336. package/src/core/Effect.mjs +193 -0
  337. package/src/core/EffectBatchManager.mjs +67 -0
  338. package/src/core/EffectManager.mjs +60 -0
  339. package/src/core/IdGenerator.mjs +13 -44
  340. package/src/data/Model.mjs +2 -0
  341. package/src/data/Store.mjs +7 -0
  342. package/src/data/connection/WebSocket.mjs +2 -0
  343. package/src/date/DayViewComponent.mjs +2 -0
  344. package/src/date/SelectorContainer.mjs +14 -0
  345. package/src/dialog/Base.mjs +8 -0
  346. package/src/draggable/DragZone.mjs +5 -0
  347. package/src/draggable/tree/DragZone.mjs +1 -0
  348. package/src/filter/BooleanContainer.mjs +2 -0
  349. package/src/filter/NumberContainer.mjs +3 -0
  350. package/src/filter/ToggleOperatorsButton.mjs +2 -0
  351. package/src/form/Fieldset.mjs +6 -0
  352. package/src/form/field/Base.mjs +7 -0
  353. package/src/form/field/CheckBox.mjs +18 -0
  354. package/src/form/field/Chip.mjs +1 -0
  355. package/src/form/field/ComboBox.mjs +8 -0
  356. package/src/form/field/Country.mjs +1 -0
  357. package/src/form/field/Currency.mjs +2 -0
  358. package/src/form/field/Date.mjs +4 -0
  359. package/src/form/field/Display.mjs +1 -0
  360. package/src/form/field/Email.mjs +1 -0
  361. package/src/form/field/FileUpload.mjs +7 -0
  362. package/src/form/field/Hidden.mjs +1 -0
  363. package/src/form/field/Number.mjs +7 -0
  364. package/src/form/field/Password.mjs +1 -0
  365. package/src/form/field/Phone.mjs +3 -0
  366. package/src/form/field/Picker.mjs +2 -0
  367. package/src/form/field/Radio.mjs +1 -0
  368. package/src/form/field/Range.mjs +3 -0
  369. package/src/form/field/Search.mjs +2 -0
  370. package/src/form/field/Text.mjs +32 -0
  371. package/src/form/field/TextArea.mjs +7 -0
  372. package/src/form/field/Time.mjs +6 -0
  373. package/src/form/field/Url.mjs +3 -0
  374. package/src/form/field/ZipCode.mjs +2 -0
  375. package/src/form/field/trigger/Base.mjs +3 -0
  376. package/src/form/field/trigger/Clear.mjs +2 -0
  377. package/src/form/field/trigger/CopyToClipboard.mjs +2 -0
  378. package/src/form/field/trigger/Date.mjs +1 -0
  379. package/src/form/field/trigger/Picker.mjs +1 -0
  380. package/src/form/field/trigger/Search.mjs +1 -0
  381. package/src/form/field/trigger/SpinDown.mjs +2 -0
  382. package/src/form/field/trigger/SpinUp.mjs +1 -0
  383. package/src/form/field/trigger/Time.mjs +2 -0
  384. package/src/functional/_export.mjs +6 -0
  385. package/src/functional/component/Base.mjs +499 -0
  386. package/src/functional/defineComponent.mjs +102 -0
  387. package/src/functional/useConfig.mjs +52 -0
  388. package/src/functional/useEvent.mjs +43 -0
  389. package/src/grid/Body.mjs +20 -1
  390. package/src/grid/Container.mjs +57 -63
  391. package/src/grid/ScrollManager.mjs +2 -0
  392. package/src/grid/VerticalScrollbar.mjs +2 -0
  393. package/src/grid/column/Base.mjs +2 -0
  394. package/src/grid/column/Component.mjs +1 -1
  395. package/src/grid/header/Button.mjs +7 -0
  396. package/src/grid/header/Toolbar.mjs +6 -0
  397. package/src/grid/plugin/AnimateRows.mjs +2 -0
  398. package/src/layout/Base.mjs +3 -0
  399. package/src/layout/Card.mjs +1 -0
  400. package/src/layout/Cube.mjs +11 -1
  401. package/src/layout/Fit.mjs +1 -0
  402. package/src/layout/Flexbox.mjs +7 -0
  403. package/src/layout/Form.mjs +2 -0
  404. package/src/layout/Grid.mjs +1 -0
  405. package/src/layout/HBox.mjs +1 -0
  406. package/src/layout/VBox.mjs +1 -0
  407. package/src/list/Base.mjs +13 -0
  408. package/src/list/Chip.mjs +1 -0
  409. package/src/list/Circle.mjs +2 -0
  410. package/src/list/Color.mjs +1 -0
  411. package/src/list/plugin/Animate.mjs +2 -0
  412. package/src/main/DeltaUpdates.mjs +1 -0
  413. package/src/main/DomEvents.mjs +2 -0
  414. package/src/main/addon/CloneNode.mjs +1 -0
  415. package/src/main/addon/Cookie.mjs +1 -0
  416. package/src/main/addon/GoogleMaps.mjs +1 -0
  417. package/src/main/addon/LocalStorage.mjs +1 -0
  418. package/src/main/addon/MapboxGL.mjs +1 -0
  419. package/src/main/addon/Markdown.mjs +1 -0
  420. package/src/main/addon/Navigator.mjs +1 -0
  421. package/src/main/addon/Popover.mjs +1 -0
  422. package/src/main/addon/Stylesheet.mjs +1 -0
  423. package/src/main/addon/WindowPosition.mjs +1 -0
  424. package/src/manager/Component.mjs +0 -71
  425. package/src/manager/VDomUpdate.mjs +235 -0
  426. package/src/menu/List.mjs +6 -0
  427. package/src/menu/Model.mjs +1 -0
  428. package/src/menu/Panel.mjs +3 -0
  429. package/src/menu/Store.mjs +1 -0
  430. package/src/mixin/DomEvents.mjs +130 -0
  431. package/src/mixin/VdomLifecycle.mjs +667 -0
  432. package/src/plugin/Base.mjs +1 -0
  433. package/src/plugin/Resizable.mjs +2 -0
  434. package/src/selection/Model.mjs +15 -18
  435. package/src/selection/grid/BaseModel.mjs +1 -0
  436. package/src/sitemap/Component.mjs +1 -0
  437. package/src/state/Provider.mjs +376 -457
  438. package/src/state/createHierarchicalDataProxy.mjs +138 -0
  439. package/src/tab/Container.mjs +6 -0
  440. package/src/tab/Strip.mjs +1 -0
  441. package/src/tab/header/Button.mjs +2 -0
  442. package/src/tab/header/EffectButton.mjs +77 -0
  443. package/src/tab/header/Toolbar.mjs +1 -0
  444. package/src/table/Body.mjs +3 -0
  445. package/src/table/Container.mjs +10 -0
  446. package/src/table/header/Button.mjs +8 -0
  447. package/src/table/header/Toolbar.mjs +5 -0
  448. package/src/table/plugin/CellEditing.mjs +1 -0
  449. package/src/toolbar/Base.mjs +4 -0
  450. package/src/toolbar/Breadcrumb.mjs +3 -0
  451. package/src/toolbar/Paging.mjs +5 -0
  452. package/src/tooltip/Base.mjs +2 -0
  453. package/src/tree/List.mjs +3 -0
  454. package/src/util/HashHistory.mjs +1 -0
  455. package/src/util/KeyNavigation.mjs +2 -0
  456. package/src/util/Matrix.mjs +1 -0
  457. package/src/util/VDom.mjs +7 -1
  458. package/src/util/VNode.mjs +7 -1
  459. package/src/util/vdom/TreeBuilder.mjs +129 -0
  460. package/src/vdom/Helper.mjs +44 -33
  461. package/src/vdom/VNode.mjs +5 -7
  462. package/src/worker/App.mjs +1 -5
  463. package/src/worker/Base.mjs +2 -0
  464. package/src/worker/Manager.mjs +2 -0
  465. package/src/worker/ServiceBase.mjs +6 -1
  466. package/test/siesta/siesta.js +36 -1
  467. package/test/siesta/tests/CollectionBase.mjs +10 -10
  468. package/test/siesta/tests/VdomCalendar.mjs +13 -9
  469. package/test/siesta/tests/VdomHelper.mjs +22 -59
  470. package/test/siesta/tests/config/AfterSetConfig.mjs +100 -0
  471. package/test/siesta/tests/{ReactiveConfigs.mjs → config/Basic.mjs} +58 -21
  472. package/test/siesta/tests/config/CircularDependencies.mjs +166 -0
  473. package/test/siesta/tests/config/CustomFunctions.mjs +69 -0
  474. package/test/siesta/tests/config/Hierarchy.mjs +94 -0
  475. package/test/siesta/tests/config/MemoryLeak.mjs +92 -0
  476. package/test/siesta/tests/config/MultiLevelHierarchy.mjs +85 -0
  477. package/test/siesta/tests/core/Effect.mjs +127 -0
  478. package/test/siesta/tests/core/EffectBatching.mjs +310 -0
  479. package/test/siesta/tests/neo/MixinStaticConfig.mjs +138 -0
  480. package/test/siesta/tests/state/Provider.mjs +537 -0
  481. package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +255 -0
  482. package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +204 -0
  483. package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +366 -0
  484. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +249 -0
  485. package/learn/javascript/NewNode.md +0 -31
@@ -0,0 +1,499 @@
1
+ import Base from '../../core/Base.mjs';
2
+ import ComponentManager from '../../manager/Component.mjs';
3
+ import DomEvents from '../../mixin/DomEvents.mjs';
4
+ import Effect from '../../core/Effect.mjs';
5
+ import NeoArray from '../../util/Array.mjs';
6
+ import Observable from '../../core/Observable.mjs';
7
+ import VdomLifecycle from '../../mixin/VdomLifecycle.mjs';
8
+
9
+ const
10
+ activeDomListenersSymbol = Symbol.for('activeDomListeners'),
11
+ hookIndexSymbol = Symbol.for('hookIndex'),
12
+ hooksSymbol = Symbol.for('hooks'),
13
+ pendingDomEventsSymbol = Symbol.for('pendingDomEvents'),
14
+ vdomToApplySymbol = Symbol('vdomToApply');
15
+
16
+ /**
17
+ * @class Neo.functional.component.Base
18
+ * @extends Neo.core.Base
19
+ * @mixes Neo.component.mixin.DomEvents
20
+ * @mixes Neo.core.Observable
21
+ * @mixes Neo.component.mixin.VdomLifecycle
22
+ */
23
+ class FunctionalBase extends Base {
24
+ static config = {
25
+ /**
26
+ * @member {String} className='Neo.functional.component.Base'
27
+ * @protected
28
+ */
29
+ className: 'Neo.functional.component.Base',
30
+ /**
31
+ * @member {String} ntype='functional-component'
32
+ * @protected
33
+ */
34
+ ntype: 'functional-component',
35
+ /**
36
+ * Custom CSS selectors to apply to the root level node of this component
37
+ * @member {String[]} cls=null
38
+ * @reactive
39
+ */
40
+ cls: null,
41
+ /**
42
+ * @member {Neo.core.Base[]} mixins=[DomEvents, Observable, VdomLifecycle]
43
+ */
44
+ mixins: [DomEvents, Observable, VdomLifecycle],
45
+ /**
46
+ * True after the component render() method was called. Also fires the rendered event.
47
+ * @member {Boolean} mounted_=false
48
+ * @protected
49
+ * @reactive
50
+ */
51
+ mounted_: false,
52
+ /**
53
+ * @member {String|null} parentId_=null
54
+ * @protected
55
+ * @reactive
56
+ */
57
+ parentId_: null,
58
+ /**
59
+ * The vdom markup for this component.
60
+ * @member {Object} vdom={}
61
+ */
62
+ vdom: {},
63
+ /**
64
+ * The custom windowIs (timestamp) this component belongs to
65
+ * @member {Number|null} windowId_=null
66
+ * @reactive
67
+ */
68
+ windowId_: null
69
+ }
70
+
71
+ /**
72
+ * Neo component instances, which got defined inside createVdom()
73
+ * @member {Map|null} childComponents=null
74
+ */
75
+ childComponents = null
76
+ /**
77
+ * Internal flag which will get set to true while a component is waiting for its mountedPromise
78
+ * @member {Boolean} isAwaitingMount=false
79
+ * @protected
80
+ */
81
+ isAwaitingMount = false
82
+ /**
83
+ * Internal Map to store the next set of components after the createVdom() Effect has run.
84
+ * @member {Map|null} nextChildComponents=null
85
+ * @private
86
+ */
87
+ #nextChildComponents = null
88
+
89
+ /**
90
+ * A Promise that resolves when the component is mounted to the DOM.
91
+ * This provides a convenient way to wait for the component to be fully
92
+ * available and interactive before executing subsequent logic.
93
+ *
94
+ * It also handles unmounting by resetting the promise, so it can be safely
95
+ * awaited again if the component is remounted.
96
+ * @returns {Promise<Neo.component.Base>}
97
+ */
98
+ get mountedPromise() {
99
+ let me = this;
100
+
101
+ if (!me._mountedPromise) {
102
+ me._mountedPromise = new Promise(resolve => {
103
+ if (me.mounted) {
104
+ resolve(me);
105
+ } else {
106
+ me.mountedPromiseResolve = resolve
107
+ }
108
+ })
109
+ }
110
+
111
+ return me._mountedPromise
112
+ }
113
+
114
+ /**
115
+ * Convenience method to access the parent component
116
+ * @returns {Neo.component.Base|null}
117
+ */
118
+ get parent() {
119
+ let me = this;
120
+
121
+ return me.parentComponent || (me.parentId === 'document.body' ? null : Neo.getComponent(me.parentId))
122
+ }
123
+
124
+ /**
125
+ * @param {Object} config
126
+ */
127
+ construct(config) {
128
+ super.construct(config);
129
+
130
+ let me = this,
131
+ opts = {configurable: true, enumerable: false, writable: true};
132
+
133
+ Object.defineProperties(me, {
134
+ [activeDomListenersSymbol]: {...opts, value: []},
135
+ [hookIndexSymbol] : {...opts, value: 0},
136
+ [hooksSymbol] : {...opts, value: []},
137
+ [pendingDomEventsSymbol] : {...opts, value: []},
138
+ [vdomToApplySymbol] : {...opts, value: null}
139
+ });
140
+
141
+ // Creates a reactive effect that re-executes createVdom() when dependencies change.
142
+ me.vdomEffect = new Effect({
143
+ fn: () => {
144
+ me[hookIndexSymbol] = 0;
145
+ me[pendingDomEventsSymbol] = []; // Clear pending events for new render
146
+ me[vdomToApplySymbol] = me.createVdom(me, me.data)
147
+ },
148
+ componentId: me.id,
149
+ subscriber : {
150
+ id : me.id,
151
+ fn : me.onEffectRunStateChange,
152
+ scope: me
153
+ }
154
+ })
155
+ }
156
+
157
+ /**
158
+ * Triggered after the id config got changed
159
+ * @param {String|null} value
160
+ * @param {String|null} oldValue
161
+ * @protected
162
+ */
163
+ afterSetId(value, oldValue) {
164
+ super.afterSetId(value, oldValue);
165
+
166
+ oldValue && ComponentManager.unregister(oldValue);
167
+ value && ComponentManager.register(this)
168
+ }
169
+
170
+ /**
171
+ * Triggered after the mounted config got changed
172
+ * @param {Boolean} value
173
+ * @param {Boolean} oldValue
174
+ * @protected
175
+ */
176
+ afterSetMounted(value, oldValue) {
177
+ if (oldValue !== undefined) {
178
+ const me = this;
179
+
180
+ if (value) { // mount
181
+ me.initDomEvents();
182
+
183
+ // Initial registration of DOM event listeners when component mounts
184
+ me.applyPendingDomListeners();
185
+
186
+ me.mountedPromiseResolve?.(this);
187
+ delete me.mountedPromiseResolve
188
+ } else { // unmount
189
+ delete me._mountedPromise
190
+ }
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Triggered after the windowId config got changed
196
+ * @param {Number|null} value
197
+ * @param {Number|null} oldValue
198
+ * @protected
199
+ */
200
+ afterSetWindowId(value, oldValue) {
201
+ const me = this;
202
+
203
+ if (value) {
204
+ Neo.currentWorker.insertThemeFiles(value, me.__proto__)
205
+ }
206
+
207
+ me.childComponents?.forEach(childData => {
208
+ childData.instance.windowId = value
209
+ })
210
+
211
+ // If a component gets moved into a different window, an update cycle might still be running.
212
+ // Since the update might no longer get mapped, we want to re-enable this instance for future updates.
213
+ if (oldValue) {
214
+ me.isVdomUpdating = false
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Applies the pending DOM event listeners and updates the active list.
220
+ * @private
221
+ */
222
+ applyPendingDomListeners() {
223
+ const
224
+ me = this,
225
+ activeEvents = me[activeDomListenersSymbol],
226
+ pendingEvents = me[pendingDomEventsSymbol];
227
+
228
+ if (pendingEvents.length > 0) {
229
+ if (!Neo.isEqual(activeEvents, pendingEvents)) {
230
+ if (activeEvents?.length > 0) {
231
+ // Remove old dynamic listeners
232
+ me.removeDomListeners(me[activeDomListenersSymbol])
233
+ }
234
+
235
+ me.addDomListeners([...pendingEvents]);
236
+
237
+ me[activeDomListenersSymbol] = [...pendingEvents]
238
+ }
239
+
240
+ // Clear pending events for next `createVdom()` Effect run
241
+ me[pendingDomEventsSymbol] = []
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Override this method in your functional component to return its VDOM structure.
247
+ * This method will be automatically re-executed when any of the component's configs change.
248
+ * @param {Neo.functional.component.Base} config - Mental model: while it contains the instance, it makes it clear to access configs
249
+ * @param {Object} data - Convenience shortcut for accessing `state.Provider` data
250
+ * @returns {Object} The VDOM structure for the component.
251
+ */
252
+ createVdom(config, data) {
253
+ // This method should be overridden by subclasses
254
+ return {}
255
+ }
256
+
257
+ /**
258
+ *
259
+ */
260
+ destroy() {
261
+ const me = this;
262
+
263
+ me.vdomEffect?.destroy();
264
+
265
+ // Destroy all classic components instantiated by this functional component
266
+ me.childComponents?.forEach(childData => {
267
+ childData.instance.destroy()
268
+ });
269
+ me.childComponents?.clear();
270
+
271
+ me.removeDomEvents();
272
+
273
+ // Remove any pending DOM event listeners that might not have been mounted
274
+ me[pendingDomEventsSymbol] = null;
275
+
276
+ ComponentManager.unregister(me);
277
+
278
+ super.destroy()
279
+ }
280
+
281
+ /**
282
+ * This method recursively compares the new VDOM config with the last applied config
283
+ * for a given component instance and its sub-instances.
284
+ * @param {Neo.core.Base} instance The component instance to update.
285
+ * @param {Object} newConfig The new configuration object from the VDOM.
286
+ * @param {Object} lastConfig The last applied configuration object.
287
+ * @private
288
+ */
289
+ diffAndSet(instance, newConfig, lastConfig) {
290
+ const deltaConfig = {};
291
+
292
+ for (const key in newConfig) {
293
+ const newValue = newConfig[key],
294
+ oldValue = lastConfig[key];
295
+
296
+ if (!Neo.isEqual(newValue, oldValue)) {
297
+ // If the config property is an object and it maps to a sub-component instance, recurse.
298
+ if (Neo.typeOf(newValue) === 'Object' && Neo.typeOf(instance[key]) === 'NeoInstance') {
299
+ this.diffAndSet(instance[key], newValue, oldValue || {})
300
+ } else {
301
+ // Otherwise, add it to the delta to be set on the current instance.
302
+ deltaConfig[key] = newValue
303
+ }
304
+ }
305
+ }
306
+
307
+ // Only call set() if there are actual changes for the current instance.
308
+ if (Object.keys(deltaConfig).length > 0) {
309
+ instance.set(deltaConfig)
310
+ }
311
+ }
312
+
313
+ /**
314
+ * This handler runs when the effect's `isRunning` state changes.
315
+ * It runs outside the effect's tracking scope, preventing feedback loops.
316
+ * @param {Boolean} value
317
+ * @param {Boolean} oldValue
318
+ */
319
+ onEffectRunStateChange(value, oldValue) {
320
+ // When the effect has just finished running...
321
+ if (value === false) {
322
+ const me = this,
323
+ newVdom = me[vdomToApplySymbol];
324
+
325
+ if (newVdom) {
326
+ // Create a new map for components instantiated in this render cycle
327
+ me.#nextChildComponents = new Map();
328
+
329
+ // Process the newVdom to instantiate components
330
+ // The parentId for these components will be the functional component's id
331
+ const processedVdom = me.processVdomForComponents(newVdom, me.id);
332
+
333
+ // Destroy components that are no longer present in the new VDOM
334
+ if (me.childComponents?.size > 0) {
335
+ [...me.childComponents].forEach(([key, childData]) => {
336
+ if (!me.#nextChildComponents.has(key)) {
337
+ me.childComponents.delete(key);
338
+ childData.instance.destroy()
339
+ }
340
+ })
341
+ }
342
+
343
+ // If this component created other classic or functional components,
344
+ // include their full vdom into the next update cycle.
345
+ const oldKeys = me.childComponents ? new Set(me.childComponents.keys()) : new Set();
346
+ let hasNewChildren = false;
347
+
348
+ for (const newKey of me.#nextChildComponents.keys()) {
349
+ if (!oldKeys.has(newKey)) {
350
+ hasNewChildren = true;
351
+ break
352
+ }
353
+ }
354
+
355
+ if (hasNewChildren) {
356
+ // When new child components are created, we need to send their full VDOM
357
+ // to the vdom-worker, so they can get rendered.
358
+ // Subsequent updates will be granular via diffAndSet() => set() on the child.
359
+ me.updateDepth = -1;
360
+ }
361
+
362
+ // Update the main map of instantiated components
363
+ me.childComponents = me.#nextChildComponents;
364
+
365
+ // Clear the old vdom properties
366
+ for (const key in me.vdom) {
367
+ delete me.vdom[key]
368
+ }
369
+
370
+ // Assign the new properties
371
+ Object.assign(me.vdom, processedVdom); // Use processedVdom here
372
+
373
+ me[vdomToApplySymbol] = null;
374
+
375
+ const root = me.getVdomRoot();
376
+
377
+ if (me.cls) {
378
+ root.cls = NeoArray.union(me.cls, root.cls)
379
+ }
380
+
381
+ if (me.id) {
382
+ root.id = me.id
383
+ }
384
+
385
+ me.updateVdom();
386
+
387
+ // Update DOM event listeners based on the new render
388
+ if (me.mounted) {
389
+ me.applyPendingDomListeners()
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Recursively processes a VDOM node to instantiate components defined within it.
397
+ * @param {Object} vdomTree The VDOM node to process.
398
+ * @param {String} parentId The ID of the parent component (the functional component hosting it).
399
+ * @param {Number} [parentIndex] The index of the vdomNode within its parent's children.
400
+ * @returns {Object} The processed VDOM node, potentially replaced with a component reference.
401
+ * @private
402
+ */
403
+ processVdomForComponents(vdomTree, parentId, parentIndex) {
404
+ if (!vdomTree) {
405
+ return vdomTree
406
+ }
407
+
408
+ // If it's already a component reference, no need to process further
409
+ if (vdomTree.componentId) {
410
+ return vdomTree
411
+ }
412
+
413
+ const me = this;
414
+
415
+ // Check if it's a component definition (functional or classic)
416
+ if (vdomTree.className || vdomTree.module || vdomTree.ntype) {
417
+ // Components are reconciled based on their `id` property in the VDOM definition.
418
+ // If no `id` is provided, a new instance will be created on every render.
419
+ const componentKey = vdomTree.id;
420
+
421
+ if (!componentKey) {
422
+ console.error([
423
+ 'Component definition in functional component VDOM is missing an "id". For stable reconciliation, ',
424
+ 'especially in dynamic lists, provide a unique "id" property.'
425
+ ].join(''),
426
+ vdomTree
427
+ )
428
+ }
429
+
430
+ let childData = me.childComponents?.get(componentKey),
431
+ newConfig = {...vdomTree}, // Shallow copy
432
+ instance;
433
+
434
+ delete newConfig.className;
435
+ delete newConfig.id;
436
+ delete newConfig.module;
437
+ delete newConfig.ntype;
438
+
439
+ if (!childData) {
440
+ me.childComponents ??= new Map();
441
+
442
+ // Instantiate the component
443
+ instance = Neo[(vdomTree.className || vdomTree.module) ? 'create' : 'ntype']({
444
+ ...vdomTree,
445
+ parentId,
446
+ parentIndex,
447
+ windowId: me.windowId
448
+ });
449
+ } else {
450
+ instance = childData.instance;
451
+
452
+ // Recursively diff and set configs
453
+ this.diffAndSet(instance, newConfig, childData.lastConfig);
454
+ }
455
+
456
+ // Add to the new map for tracking in this render cycle
457
+ me.#nextChildComponents.set(componentKey, {
458
+ instance,
459
+ lastConfig: newConfig
460
+ });
461
+
462
+ // Replace the definition with a reference using the component's own method
463
+ return instance.createVdomReference();
464
+ }
465
+
466
+ // Recursively process children
467
+ if (vdomTree.cn && Array.isArray(vdomTree.cn)) {
468
+ vdomTree.cn = vdomTree.cn.map((child, index) =>
469
+ me.processVdomForComponents(child, parentId, index)
470
+ )
471
+ }
472
+
473
+ return vdomTree
474
+ }
475
+
476
+ /**
477
+ * Change multiple configs at once, ensuring that all afterSet methods get all new assigned values
478
+ * @param {Object} values={}
479
+ * @param {Boolean} silent=false
480
+ * @returns {Promise<*>}
481
+ */
482
+ set(values={}, silent=false) {
483
+ let me = this;
484
+
485
+ me.silentVdomUpdate = true;
486
+
487
+ super.set(values);
488
+
489
+ me.silentVdomUpdate = false;
490
+
491
+ if (silent || !me.needsVdomUpdate) {
492
+ return Promise.resolve()
493
+ }
494
+
495
+ return me.promiseUpdate()
496
+ }
497
+ }
498
+
499
+ export default Neo.setupClass(FunctionalBase);
@@ -0,0 +1,102 @@
1
+ import FunctionalBase from './component/Base.mjs';
2
+
3
+ /**
4
+ * Factory function to create a functional component class from a specification object.
5
+ * This enables a "Beginner Mode" for creating components without writing a class,
6
+ * while still providing access to the full class-based feature set.
7
+ *
8
+ * It's important to understand the two ways of managing state:
9
+ *
10
+ * 1. **Named Configs (Public API):** Defined in the `config` object (e.g., `text_`).
11
+ * - **Purpose:** Defines the component's public API for external control, similar to props.
12
+ * - **Use Case:** For data that a parent component will set or change.
13
+ * - **Features:** Integrates with the full config system (e.g., `afterSetText()` hooks).
14
+ *
15
+ * 2. **`useConfig()` (Internal State):** Used inside `createVdom` or other methods.
16
+ * - **Purpose:** Manages private, encapsulated state that is internal to the component.
17
+ * - **Use Case:** For state that the component manages itself and is not controlled by the parent.
18
+ * - **Features:** Provides a simple, concise way to handle local reactive state.
19
+ *
20
+ * @param {Object} spec - The specification object for the component.
21
+ * @returns {Neo.functional.component.Base} The generated component class.
22
+ *
23
+ * @example
24
+ * import { defineComponent } from 'neo/functional/defineComponent.mjs';
25
+ * import { useConfig } from 'neo/functional/useConfig.mjs';
26
+ *
27
+ * const MyComponent = defineComponent({
28
+ * // 1. Define the Public API via the `config` object.
29
+ * config: {
30
+ * className: 'MyApp.MyFunctionalComponent',
31
+ * ntype : 'my-functional-component',
32
+ *
33
+ * // `text_` is a NAMED CONFIG. It's part of the component's public API.
34
+ * // A parent can create this component with a `text` config.
35
+ * // It is reactive and will generate `afterSetText()` and `beforeSetText()` hooks.
36
+ * text_: 'Hello World'
37
+ * },
38
+ *
39
+ * // 2. Define the component's logic and VDOM.
40
+ * createVdom(config) {
41
+ * // `useConfig` creates ANONYMOUS, INTERNAL STATE.
42
+ * // The `count` state is private to this component and cannot be set by a parent.
43
+ * const [count, setCount] = useConfig(0);
44
+ *
45
+ * return {
46
+ * tag: 'div',
47
+ * cn: [{
48
+ * tag: 'h1',
49
+ * // Access the public, named config via the `config` parameter.
50
+ * text: config.text
51
+ * }, {
52
+ * tag: 'p',
53
+ * // Access the private, internal state directly.
54
+ * text: `You clicked ${count} times`
55
+ * }, {
56
+ * tag: 'button',
57
+ * text: 'Click me',
58
+ * // The setter from `useConfig` updates the internal state.
59
+ * onclick: () => setCount(count + 1)
60
+ * }]
61
+ * };
62
+ * },
63
+ *
64
+ * // 3. Lifecycle hooks for named configs work automatically.
65
+ * afterSetText(newValue, oldValue) {
66
+ * console.log(`Text changed from '${oldValue}' to '${newValue}'`);
67
+ * }
68
+ * });
69
+ *
70
+ * // The returned MyComponent is a class constructor that can be used with Neo.create()
71
+ * // const instance = Neo.create(MyComponent, {
72
+ * // text: 'Welcome to Neo.mjs!' // Set the public config on creation.
73
+ * // });
74
+ */
75
+ export function defineComponent(spec) {
76
+ const configSpec = spec.config;
77
+ delete spec.config;
78
+
79
+ if (!configSpec?.className) {
80
+ throw new Error('defineComponent requires a config object with a className.');
81
+ }
82
+
83
+ class FunctionalComponent extends FunctionalBase {
84
+ static config = {
85
+ ...configSpec
86
+ // We can add our own configurations here
87
+ }
88
+ }
89
+
90
+ // Assign instance methods
91
+ Object.entries(spec).forEach(([key, value]) => {
92
+ FunctionalComponent.prototype[key] = value
93
+ });
94
+
95
+ // To support multiple envs (like `devmode`, `dist/esm`, `dist/production` in parallel,
96
+ // we must re-assign FunctionalComponent to the setupClass() output.
97
+ FunctionalComponent = Neo.setupClass(FunctionalComponent);
98
+
99
+ return FunctionalComponent
100
+ }
101
+
102
+ export default defineComponent;
@@ -0,0 +1,52 @@
1
+ import Config from '../core/Config.mjs';
2
+ import EffectManager from '../core/EffectManager.mjs';
3
+
4
+ const
5
+ hookIndexSymbol = Symbol.for('hookIndex'),
6
+ hooksSymbol = Symbol.for('hooks');
7
+
8
+ /**
9
+ * A hook for managing reactive state within a functional component's `createVdom` method.
10
+ * It mirrors the behavior of React's `useState` but is powered by `Neo.core.Config` for reactivity.
11
+ * @param {*} initialValue The initial value for the state.
12
+ * @returns {Array<any>} A tuple containing the current value and a setter function.
13
+ */
14
+ export function useConfig(initialValue) {
15
+ EffectManager.pause();
16
+
17
+ const
18
+ effect = EffectManager.getActiveEffect(),
19
+ component = effect && Neo.getComponent(effect.componentId);
20
+
21
+ if (!component) {
22
+ throw new Error('useConfig must be called from within a functional component\'s createVdom method.')
23
+ }
24
+
25
+ const currentIndex = component[hookIndexSymbol];
26
+
27
+ // Increment the index for the next hook call within the same component render cycle.
28
+ component[hookIndexSymbol]++;
29
+
30
+ // If this is the first time this hook is being called for this component, initialize its state.
31
+ if (!component[hooksSymbol][currentIndex]) {
32
+ const config = new Config(initialValue);
33
+
34
+ const customSetter = (newValue) => {
35
+ if (typeof newValue === 'function') {
36
+ newValue = newValue(config.get())
37
+ }
38
+ config.set(newValue)
39
+ };
40
+
41
+ component[hooksSymbol][currentIndex] = [config, customSetter]
42
+ }
43
+
44
+ const [config, setter] = component[hooksSymbol][currentIndex];
45
+
46
+ EffectManager.resume();
47
+
48
+ // Call config.get() to ensure this component's effect tracks this config as a dependency.
49
+ return [config.get(), setter]
50
+ }
51
+
52
+ export default useConfig;
@@ -0,0 +1,43 @@
1
+ import EffectManager from '../core/EffectManager.mjs';
2
+
3
+ const pendingDomEventsSymbol = Symbol.for('pendingDomEvents');
4
+
5
+ /**
6
+ * A hook for registering DOM event listeners within a functional component.
7
+ * Event listeners registered via this hook will be managed by Neo.mjs's
8
+ * delegated DOM event system, ensuring efficient and proper lifecycle handling.
9
+ *
10
+ * @param {String} eventType - The type of DOM event to listen for (e.g., 'click', 'input').
11
+ * @param {Function} handler - The event handler function. It will receive the event data as its argument.
12
+ * @param {String} [delegate] - An optional CSS selector for event delegation. If provided,
13
+ * the handler will only fire if the event target matches this selector.
14
+ */
15
+ export function useEvent(eventType, handler, delegate) {
16
+ const
17
+ activeEffect = EffectManager.getActiveEffect(),
18
+ componentId = activeEffect?.componentId;
19
+
20
+ if (!componentId) {
21
+ throw new Error('useEvent must be called from within a functional component\'s createVdom method.')
22
+ }
23
+
24
+ EffectManager.pause();
25
+ const component = Neo.getComponent(componentId);
26
+ EffectManager.resume();
27
+
28
+ if (!component) {
29
+ throw new Error(`Component with id ${componentId} not found for useEvent hook.`);
30
+ }
31
+
32
+ // Ensure pendingDomEventsSymbol exists on the component instance
33
+ component[pendingDomEventsSymbol] ??= [];
34
+
35
+ // Add the event listener configuration to the component's pending list
36
+ component[pendingDomEventsSymbol].push({
37
+ [eventType]: handler,
38
+ delegate,
39
+ scope : component // The component instance itself will be the scope
40
+ })
41
+ }
42
+
43
+ export default useEvent;