neo.mjs 10.0.0-beta.5 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (491) hide show
  1. package/.github/RELEASE_NOTES/v10.0.0-beta.1.md +20 -0
  2. package/.github/RELEASE_NOTES/v10.0.0-beta.2.md +73 -0
  3. package/.github/RELEASE_NOTES/v10.0.0-beta.3.md +39 -0
  4. package/.github/RELEASE_NOTES/v10.0.0-beta.5.md +70 -0
  5. package/.github/RELEASE_NOTES/v10.0.0-beta.6.md +48 -0
  6. package/.github/RELEASE_NOTES/v10.0.0.md +52 -0
  7. package/.github/epic-functional-components.md +498 -0
  8. package/.github/ticket-asymmetric-vdom-updates.md +122 -0
  9. package/README.md +0 -3
  10. package/ServiceWorker.mjs +2 -2
  11. package/apps/colors/store/Colors.mjs +1 -0
  12. package/apps/colors/view/GridContainer.mjs +3 -0
  13. package/apps/colors/view/HeaderToolbar.mjs +2 -0
  14. package/apps/colors/view/Viewport.mjs +3 -0
  15. package/apps/covid/view/FooterContainer.mjs +3 -0
  16. package/apps/covid/view/GalleryContainer.mjs +2 -0
  17. package/apps/covid/view/GalleryContainerController.mjs +1 -0
  18. package/apps/covid/view/HeaderContainer.mjs +2 -0
  19. package/apps/covid/view/HelixContainer.mjs +2 -0
  20. package/apps/covid/view/HelixContainerController.mjs +1 -0
  21. package/apps/covid/view/MainContainer.mjs +3 -0
  22. package/apps/covid/view/TableContainer.mjs +3 -0
  23. package/apps/covid/view/TableContainerController.mjs +1 -0
  24. package/apps/covid/view/WorldMapContainer.mjs +2 -0
  25. package/apps/covid/view/country/Gallery.mjs +3 -0
  26. package/apps/covid/view/country/Helix.mjs +8 -0
  27. package/apps/covid/view/country/HistoricalDataTable.mjs +1 -0
  28. package/apps/covid/view/country/Table.mjs +2 -0
  29. package/apps/covid/view/mapboxGl/Component.mjs +1 -0
  30. package/apps/covid/view/mapboxGl/Container.mjs +2 -0
  31. package/apps/email/EPIC_PLAN.md +58 -0
  32. package/apps/email/neo-config.json +2 -2
  33. package/apps/email/store/Emails.mjs +11 -1
  34. package/apps/email/view/ComposeView.mjs +44 -0
  35. package/apps/email/view/MainView.mjs +89 -0
  36. package/apps/email/view/Viewport.mjs +4 -33
  37. package/apps/email/view/ViewportStateProvider.mjs +3 -3
  38. package/apps/form/store/SideNav.mjs +1 -0
  39. package/apps/form/view/FormContainer.mjs +1 -0
  40. package/apps/form/view/FormPageContainer.mjs +2 -0
  41. package/apps/form/view/SideNavList.mjs +1 -0
  42. package/apps/form/view/Viewport.mjs +3 -0
  43. package/apps/portal/childapps/preview/MainContainer.mjs +1 -0
  44. package/apps/portal/index.html +1 -1
  45. package/apps/portal/store/BlogPosts.mjs +2 -0
  46. package/apps/portal/store/Content.mjs +1 -0
  47. package/apps/portal/store/ContentSections.mjs +1 -0
  48. package/apps/portal/store/Examples.mjs +1 -0
  49. package/apps/portal/view/HeaderToolbar.mjs +1 -0
  50. package/apps/portal/view/Viewport.mjs +5 -0
  51. package/apps/portal/view/ViewportController.mjs +11 -3
  52. package/apps/portal/view/about/Container.mjs +2 -0
  53. package/apps/portal/view/about/MemberContainer.mjs +7 -0
  54. package/apps/portal/view/blog/Container.mjs +2 -0
  55. package/apps/portal/view/blog/List.mjs +2 -0
  56. package/apps/portal/view/examples/List.mjs +29 -19
  57. package/apps/portal/view/examples/TabContainer.mjs +4 -0
  58. package/apps/portal/view/home/ContentBox.mjs +3 -0
  59. package/apps/portal/view/home/FeatureSection.mjs +8 -0
  60. package/apps/portal/view/home/FooterContainer.mjs +4 -1
  61. package/apps/portal/view/home/MainContainer.mjs +2 -0
  62. package/apps/portal/view/home/parts/AfterMath.mjs +2 -0
  63. package/apps/portal/view/home/parts/BaseContainer.mjs +1 -0
  64. package/apps/portal/view/home/parts/Colors.mjs +4 -0
  65. package/apps/portal/view/home/parts/Features.mjs +2 -0
  66. package/apps/portal/view/home/parts/Helix.mjs +5 -0
  67. package/apps/portal/view/home/parts/How.mjs +4 -0
  68. package/apps/portal/view/home/parts/MainNeo.mjs +1 -0
  69. package/apps/portal/view/home/parts/References.mjs +2 -0
  70. package/apps/portal/view/learn/ContentComponent.mjs +11 -5
  71. package/apps/portal/view/learn/ContentTreeList.mjs +2 -0
  72. package/apps/portal/view/learn/CubeLayoutButton.mjs +1 -0
  73. package/apps/portal/view/learn/MainContainer.mjs +4 -0
  74. package/apps/portal/view/learn/PageContainer.mjs +2 -0
  75. package/apps/portal/view/learn/PageSectionsContainer.mjs +3 -0
  76. package/apps/portal/view/learn/PageSectionsList.mjs +1 -0
  77. package/apps/portal/view/services/Component.mjs +1 -0
  78. package/apps/realworld/api/Base.mjs +1 -0
  79. package/apps/realworld/view/HeaderComponent.mjs +4 -0
  80. package/apps/realworld/view/HomeComponent.mjs +7 -0
  81. package/apps/realworld/view/MainContainer.mjs +2 -0
  82. package/apps/realworld/view/MainContainerController.mjs +2 -0
  83. package/apps/realworld/view/article/CommentComponent.mjs +3 -0
  84. package/apps/realworld/view/article/Component.mjs +17 -10
  85. package/apps/realworld/view/article/CreateCommentComponent.mjs +2 -0
  86. package/apps/realworld/view/article/CreateComponent.mjs +5 -0
  87. package/apps/realworld/view/article/PreviewComponent.mjs +9 -0
  88. package/apps/realworld/view/article/TagListComponent.mjs +2 -0
  89. package/apps/realworld/view/user/ProfileComponent.mjs +7 -0
  90. package/apps/realworld/view/user/SettingsComponent.mjs +5 -0
  91. package/apps/realworld/view/user/SignUpComponent.mjs +3 -0
  92. package/apps/realworld2/api/Base.mjs +1 -0
  93. package/apps/realworld2/view/FooterComponent.mjs +1 -0
  94. package/apps/realworld2/view/HeaderToolbar.mjs +3 -0
  95. package/apps/realworld2/view/HomeContainer.mjs +1 -0
  96. package/apps/realworld2/view/MainContainer.mjs +2 -0
  97. package/apps/realworld2/view/MainContainerController.mjs +1 -0
  98. package/apps/realworld2/view/article/Helix.mjs +1 -0
  99. package/apps/realworld2/view/article/PreviewComponent.mjs +9 -0
  100. package/apps/realworld2/view/article/PreviewList.mjs +1 -0
  101. package/apps/realworld2/view/article/TagListComponent.mjs +2 -0
  102. package/apps/route/view/CenterContainer.mjs +1 -0
  103. package/apps/route/view/MainView.mjs +1 -0
  104. package/apps/sharedcovid/childapps/sharedcovidchart/MainContainer.mjs +1 -0
  105. package/apps/sharedcovid/childapps/sharedcovidgallery/MainContainer.mjs +1 -0
  106. package/apps/sharedcovid/childapps/sharedcovidhelix/MainContainer.mjs +1 -0
  107. package/apps/sharedcovid/childapps/sharedcovidmap/MainContainer.mjs +1 -0
  108. package/apps/sharedcovid/view/FooterContainer.mjs +3 -0
  109. package/apps/sharedcovid/view/GalleryContainer.mjs +2 -0
  110. package/apps/sharedcovid/view/GalleryContainerController.mjs +1 -0
  111. package/apps/sharedcovid/view/HeaderContainer.mjs +2 -0
  112. package/apps/sharedcovid/view/HelixContainer.mjs +2 -0
  113. package/apps/sharedcovid/view/HelixContainerController.mjs +1 -0
  114. package/apps/sharedcovid/view/MainContainer.mjs +3 -0
  115. package/apps/sharedcovid/view/TableContainer.mjs +3 -0
  116. package/apps/sharedcovid/view/TableContainerController.mjs +1 -0
  117. package/apps/sharedcovid/view/WorldMapContainer.mjs +2 -0
  118. package/apps/sharedcovid/view/country/Gallery.mjs +3 -0
  119. package/apps/sharedcovid/view/country/Helix.mjs +8 -0
  120. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +1 -0
  121. package/apps/sharedcovid/view/country/Table.mjs +2 -0
  122. package/apps/sharedcovid/view/mapboxGl/Component.mjs +1 -0
  123. package/apps/sharedcovid/view/mapboxGl/Container.mjs +2 -0
  124. package/apps/shareddialog/childapps/shareddialog2/view/MainContainer.mjs +2 -0
  125. package/apps/shareddialog/view/DemoDialog.mjs +2 -0
  126. package/apps/shareddialog/view/MainContainer.mjs +2 -0
  127. package/apps/shareddialog/view/MainContainerController.mjs +1 -0
  128. package/buildScripts/addReactiveTags.mjs +191 -0
  129. package/buildScripts/checkReactiveTags.mjs +160 -0
  130. package/docs/app/store/Api.mjs +1 -0
  131. package/docs/app/store/Examples.mjs +1 -0
  132. package/docs/app/view/ApiTreeList.mjs +1 -0
  133. package/docs/app/view/ContentTabContainer.mjs +2 -0
  134. package/docs/app/view/ExamplesTreeList.mjs +2 -0
  135. package/docs/app/view/HeaderContainer.mjs +3 -0
  136. package/docs/app/view/MainContainer.mjs +5 -0
  137. package/docs/app/view/classdetails/HeaderComponent.mjs +1 -0
  138. package/docs/app/view/classdetails/MainContainer.mjs +3 -0
  139. package/docs/app/view/classdetails/MembersList.mjs +5 -0
  140. package/docs/app/view/classdetails/SourceViewComponent.mjs +2 -0
  141. package/examples/ConfigurationViewport.mjs +14 -8
  142. package/examples/calendar/weekview/MainContainer.mjs +4 -0
  143. package/examples/component/coronaGallery/CountryGallery.mjs +2 -0
  144. package/examples/component/coronaGallery/CountryStore.mjs +1 -0
  145. package/examples/component/coronaGallery/Viewport.mjs +3 -0
  146. package/examples/component/coronaGallery/ViewportController.mjs +1 -0
  147. package/examples/component/coronaHelix/CountryHelix.mjs +7 -0
  148. package/examples/component/coronaHelix/MainContainer.mjs +1 -0
  149. package/examples/component/gallery/ImageStore.mjs +1 -0
  150. package/examples/component/helix/ImageStore.mjs +1 -0
  151. package/examples/component/helix/Viewport.mjs +3 -0
  152. package/examples/component/helix/ViewportController.mjs +1 -0
  153. package/examples/component/multiWindowCoronaGallery/childapp/Viewport.mjs +1 -0
  154. package/examples/component/multiWindowHelix/childapp/Viewport.mjs +1 -0
  155. package/examples/component/wrapper/googleMaps/MapComponent.mjs +2 -0
  156. package/examples/core/config/MainContainer.mjs +2 -0
  157. package/examples/dialog/DemoDialog.mjs +2 -0
  158. package/examples/dialog/MainContainer.mjs +1 -0
  159. package/examples/form/field/color/MainStore.mjs +1 -0
  160. package/examples/functional/button/base/MainContainer.mjs +207 -0
  161. package/examples/functional/button/base/app.mjs +6 -0
  162. package/examples/functional/button/base/index.html +11 -0
  163. package/examples/functional/button/base/neo-config.json +6 -0
  164. package/examples/functional/defineComponent/Component.mjs +18 -0
  165. package/examples/functional/defineComponent/MainContainer.mjs +41 -0
  166. package/examples/functional/defineComponent/app.mjs +6 -0
  167. package/examples/functional/defineComponent/index.html +11 -0
  168. package/examples/functional/defineComponent/neo-config.json +6 -0
  169. package/examples/functional/hostComponent/Component.mjs +32 -0
  170. package/examples/functional/hostComponent/MainContainer.mjs +48 -0
  171. package/examples/functional/hostComponent/app.mjs +6 -0
  172. package/examples/functional/hostComponent/index.html +11 -0
  173. package/examples/functional/hostComponent/neo-config.json +6 -0
  174. package/examples/grid/animatedRowSorting/Viewport.mjs +1 -1
  175. package/examples/grid/bigData/ControlsContainer.mjs +3 -0
  176. package/examples/grid/bigData/GridContainer.mjs +4 -2
  177. package/examples/grid/bigData/MainContainer.mjs +2 -0
  178. package/examples/grid/bigData/MainModel.mjs +1 -0
  179. package/examples/grid/bigData/MainStore.mjs +3 -0
  180. package/examples/grid/cellEditing/MainContainer.mjs +1 -1
  181. package/examples/grid/container/MainContainer.mjs +1 -1
  182. package/examples/grid/covid/GridContainer.mjs +3 -0
  183. package/examples/grid/covid/MainContainer.mjs +2 -0
  184. package/examples/grid/covid/Store.mjs +1 -0
  185. package/examples/grid/nestedRecordFields/EditUserDialog.mjs +3 -0
  186. package/examples/grid/nestedRecordFields/Viewport.mjs +3 -1
  187. package/examples/list/animate/List.mjs +4 -0
  188. package/examples/list/animate/MainContainer.mjs +2 -0
  189. package/examples/list/circle/MainStore.mjs +1 -0
  190. package/examples/list/color/MainStore.mjs +1 -0
  191. package/examples/preloadingAssets/view/MainContainer.mjs +2 -0
  192. package/examples/stateProvider/advanced/MainContainer.mjs +1 -0
  193. package/examples/stateProvider/dialog/EditUserDialog.mjs +2 -0
  194. package/examples/stateProvider/dialog/MainContainer.mjs +1 -0
  195. package/examples/stateProvider/extendedClass/MainContainer.mjs +2 -0
  196. package/examples/stateProvider/inline/MainContainer.mjs +1 -0
  197. package/examples/stateProvider/inlineNoStateProvider/MainContainer.mjs +1 -0
  198. package/examples/stateProvider/inlineNoStateProvider/MainContainerController.mjs +2 -0
  199. package/examples/stateProvider/multiWindow/EditUserDialog.mjs +3 -0
  200. package/examples/stateProvider/multiWindow/MainContainer.mjs +1 -0
  201. package/examples/stateProvider/multiWindow/Viewport.mjs +1 -0
  202. package/examples/stateProvider/nestedData/MainContainer.mjs +1 -0
  203. package/examples/stateProvider/table/MainContainer.mjs +1 -0
  204. package/examples/table/covid/MainContainer.mjs +2 -0
  205. package/examples/table/covid/Store.mjs +1 -0
  206. package/examples/table/covid/TableContainer.mjs +3 -0
  207. package/examples/table/nestedRecordFields/EditUserDialog.mjs +3 -0
  208. package/examples/table/nestedRecordFields/Viewport.mjs +1 -0
  209. package/examples/todoList/version1/MainComponent.mjs +1 -1
  210. package/examples/toolbar/breadcrumb/view/MainContainer.mjs +2 -0
  211. package/examples/toolbar/paging/store/Users.mjs +1 -0
  212. package/examples/toolbar/paging/view/AddUserDialog.mjs +3 -0
  213. package/examples/toolbar/paging/view/MainContainer.mjs +3 -0
  214. package/examples/treeAccordion/MainContainer.mjs +2 -2
  215. package/examples/worker/task/MainContainer.mjs +1 -0
  216. package/learn/Glossary.md +1 -0
  217. package/learn/UsingTheseTopics.md +1 -0
  218. package/learn/benefits/ConfigSystem.md +2 -0
  219. package/learn/benefits/Effort.md +1 -0
  220. package/learn/benefits/Features.md +1 -0
  221. package/learn/benefits/FormsEngine.md +1 -0
  222. package/learn/benefits/FourEnvironments.md +2 -0
  223. package/learn/benefits/Introduction.md +2 -0
  224. package/learn/benefits/MultiWindow.md +3 -1
  225. package/learn/benefits/OffTheMainThread.md +2 -0
  226. package/learn/benefits/Quick.md +2 -0
  227. package/learn/benefits/RPCLayer.md +2 -0
  228. package/learn/benefits/Speed.md +2 -0
  229. package/learn/blog/v10-deep-dive-functional-components.md +293 -0
  230. package/learn/blog/v10-deep-dive-reactivity.md +522 -0
  231. package/learn/blog/v10-deep-dive-state-provider.md +432 -0
  232. package/learn/blog/v10-deep-dive-vdom-revolution.md +194 -0
  233. package/learn/blog/v10-post1-love-story.md +383 -0
  234. package/learn/comparisons/NeoVsAngular.md +90 -0
  235. package/learn/comparisons/NeoVsExtJs.md +178 -0
  236. package/learn/comparisons/NeoVsNextJs.md +124 -0
  237. package/learn/comparisons/NeoVsReact.md +95 -0
  238. package/learn/comparisons/NeoVsSolid.md +78 -0
  239. package/learn/comparisons/NeoVsVue.md +92 -0
  240. package/learn/comparisons/Overview.md +46 -0
  241. package/learn/gettingstarted/ComponentModels.md +2 -0
  242. package/learn/gettingstarted/Config.md +2 -0
  243. package/learn/gettingstarted/DescribingTheUI.md +2 -0
  244. package/learn/gettingstarted/Events.md +2 -0
  245. package/learn/gettingstarted/Extending.md +2 -0
  246. package/learn/gettingstarted/References.md +2 -0
  247. package/learn/gettingstarted/Setup.md +3 -2
  248. package/learn/gettingstarted/Workspaces.md +2 -0
  249. package/learn/guides/datahandling/Collections.md +1 -0
  250. package/learn/guides/datahandling/Records.md +1 -0
  251. package/learn/guides/datahandling/StateProviders.md +130 -16
  252. package/learn/guides/datahandling/Tables.md +1 -1
  253. package/learn/guides/fundamentals/ConfigSystemDeepDive.md +1 -0
  254. package/learn/guides/fundamentals/DeclarativeComponentTreesVsImperativeVdom.md +2 -0
  255. package/learn/guides/fundamentals/DeclarativeVDOMWithEffects.md +10 -8
  256. package/learn/guides/fundamentals/ExtendingNeoClasses.md +1 -0
  257. package/learn/guides/fundamentals/InstanceLifecycle.md +3 -1
  258. package/learn/guides/fundamentals/MainThreadAddons.md +2 -0
  259. package/learn/guides/specificfeatures/Mixins.md +3 -1
  260. package/learn/guides/specificfeatures/MultiWindow.md +3 -1
  261. package/learn/guides/specificfeatures/PortalApp.md +2 -0
  262. package/learn/guides/uibuildingblocks/ComponentsAndContainers.md +2 -0
  263. package/learn/guides/uibuildingblocks/CustomComponents.md +2 -0
  264. package/learn/guides/uibuildingblocks/Layouts.md +2 -0
  265. package/learn/guides/uibuildingblocks/WorkingWithVDom.md +28 -2
  266. package/learn/guides/userinteraction/Forms.md +2 -0
  267. package/learn/guides/userinteraction/events/CustomEvents.md +2 -1
  268. package/learn/guides/userinteraction/events/DomEvents.md +2 -0
  269. package/learn/javascript/ClassFeatures.md +4 -3
  270. package/learn/javascript/Classes.md +10 -13
  271. package/learn/javascript/Overrides.md +10 -6
  272. package/learn/javascript/Super.md +12 -8
  273. package/learn/tree.json +71 -64
  274. package/learn/tutorials/Earthquakes.md +2 -0
  275. package/learn/tutorials/RSP.md +3 -1
  276. package/learn/tutorials/TodoList.md +103 -7
  277. package/package.json +8 -6
  278. package/resources/scss/src/apps/email/ComposeView.scss +16 -0
  279. package/resources/scss/src/apps/email/MainView.scss +5 -0
  280. package/resources/scss/src/apps/portal/learn/ContentComponent.scss +5 -4
  281. package/src/DefaultConfig.mjs +12 -2
  282. package/src/Main.mjs +1 -0
  283. package/src/Neo.mjs +219 -166
  284. package/src/Xhr.mjs +1 -0
  285. package/src/button/Base.mjs +13 -0
  286. package/src/button/Effect.mjs +16 -2
  287. package/src/button/Split.mjs +2 -0
  288. package/src/calendar/store/Calendars.mjs +1 -0
  289. package/src/calendar/store/Colors.mjs +1 -0
  290. package/src/calendar/store/Events.mjs +1 -0
  291. package/src/calendar/view/DayComponent.mjs +2 -0
  292. package/src/calendar/view/EditEventContainer.mjs +4 -1
  293. package/src/calendar/view/MainContainer.mjs +13 -0
  294. package/src/calendar/view/MainContainerStateProvider.mjs +14 -28
  295. package/src/calendar/view/SettingsContainer.mjs +1 -0
  296. package/src/calendar/view/YearComponent.mjs +16 -0
  297. package/src/calendar/view/calendars/ColorsList.mjs +2 -0
  298. package/src/calendar/view/calendars/Container.mjs +2 -0
  299. package/src/calendar/view/calendars/EditContainer.mjs +1 -0
  300. package/src/calendar/view/month/Component.mjs +11 -0
  301. package/src/calendar/view/settings/GeneralContainer.mjs +1 -0
  302. package/src/calendar/view/settings/MonthContainer.mjs +1 -0
  303. package/src/calendar/view/settings/WeekContainer.mjs +1 -0
  304. package/src/calendar/view/settings/YearContainer.mjs +1 -0
  305. package/src/calendar/view/week/Component.mjs +15 -1
  306. package/src/calendar/view/week/TimeAxisComponent.mjs +4 -0
  307. package/src/code/LivePreview.mjs +51 -23
  308. package/src/collection/Base.mjs +7 -10
  309. package/src/collection/Filter.mjs +6 -0
  310. package/src/collection/Sorter.mjs +3 -0
  311. package/src/component/Abstract.mjs +412 -0
  312. package/src/component/Base.mjs +48 -1077
  313. package/src/component/Canvas.mjs +1 -0
  314. package/src/component/Chip.mjs +4 -0
  315. package/src/component/Circle.mjs +14 -0
  316. package/src/component/Clock.mjs +4 -0
  317. package/src/component/DateSelector.mjs +12 -0
  318. package/src/component/Gallery.mjs +11 -0
  319. package/src/component/Helix.mjs +24 -0
  320. package/src/component/Label.mjs +1 -0
  321. package/src/component/Legend.mjs +3 -0
  322. package/src/component/MagicMoveText.mjs +4 -0
  323. package/src/component/Progress.mjs +3 -0
  324. package/src/component/Splitter.mjs +3 -0
  325. package/src/component/StatusBadge.mjs +6 -0
  326. package/src/component/Timer.mjs +4 -0
  327. package/src/component/Toast.mjs +6 -0
  328. package/src/component/Video.mjs +1 -0
  329. package/src/component/mwc/Button.mjs +7 -0
  330. package/src/component/mwc/TextField.mjs +9 -0
  331. package/src/component/wrapper/AmChart.mjs +2 -0
  332. package/src/component/wrapper/GoogleMaps.mjs +3 -0
  333. package/src/component/wrapper/MapboxGL.mjs +5 -0
  334. package/src/component/wrapper/MonacoEditor.mjs +12 -0
  335. package/src/container/Accordion.mjs +2 -0
  336. package/src/container/Base.mjs +7 -3
  337. package/src/container/Panel.mjs +1 -0
  338. package/src/container/Viewport.mjs +1 -0
  339. package/src/controller/Application.mjs +1 -0
  340. package/src/controller/Base.mjs +1 -0
  341. package/src/controller/Component.mjs +1 -0
  342. package/src/core/Base.mjs +86 -33
  343. package/src/core/Compare.mjs +4 -7
  344. package/src/core/Config.mjs +65 -52
  345. package/src/core/Effect.mjs +86 -24
  346. package/src/core/EffectManager.mjs +117 -8
  347. package/src/core/IdGenerator.mjs +13 -44
  348. package/src/core/Observable.mjs +69 -65
  349. package/src/data/Model.mjs +2 -0
  350. package/src/data/Store.mjs +7 -0
  351. package/src/data/connection/WebSocket.mjs +2 -0
  352. package/src/date/DayViewComponent.mjs +2 -0
  353. package/src/date/SelectorContainer.mjs +14 -0
  354. package/src/dialog/Base.mjs +8 -0
  355. package/src/draggable/DragZone.mjs +5 -0
  356. package/src/draggable/tree/DragZone.mjs +1 -0
  357. package/src/filter/BooleanContainer.mjs +2 -0
  358. package/src/filter/NumberContainer.mjs +3 -0
  359. package/src/filter/ToggleOperatorsButton.mjs +2 -0
  360. package/src/form/Fieldset.mjs +6 -0
  361. package/src/form/field/Base.mjs +7 -0
  362. package/src/form/field/CheckBox.mjs +18 -0
  363. package/src/form/field/Chip.mjs +1 -0
  364. package/src/form/field/ComboBox.mjs +8 -0
  365. package/src/form/field/Country.mjs +1 -0
  366. package/src/form/field/Currency.mjs +2 -0
  367. package/src/form/field/Date.mjs +4 -0
  368. package/src/form/field/Display.mjs +1 -0
  369. package/src/form/field/Email.mjs +1 -0
  370. package/src/form/field/FileUpload.mjs +7 -0
  371. package/src/form/field/Hidden.mjs +1 -0
  372. package/src/form/field/Number.mjs +7 -0
  373. package/src/form/field/Password.mjs +1 -0
  374. package/src/form/field/Phone.mjs +3 -0
  375. package/src/form/field/Picker.mjs +2 -0
  376. package/src/form/field/Radio.mjs +1 -0
  377. package/src/form/field/Range.mjs +3 -0
  378. package/src/form/field/Search.mjs +2 -0
  379. package/src/form/field/Text.mjs +43 -5
  380. package/src/form/field/TextArea.mjs +7 -0
  381. package/src/form/field/Time.mjs +6 -0
  382. package/src/form/field/Url.mjs +3 -0
  383. package/src/form/field/ZipCode.mjs +2 -0
  384. package/src/form/field/trigger/Base.mjs +3 -0
  385. package/src/form/field/trigger/Clear.mjs +2 -0
  386. package/src/form/field/trigger/CopyToClipboard.mjs +2 -0
  387. package/src/form/field/trigger/Date.mjs +1 -0
  388. package/src/form/field/trigger/Picker.mjs +1 -0
  389. package/src/form/field/trigger/Search.mjs +1 -0
  390. package/src/form/field/trigger/SpinDown.mjs +2 -0
  391. package/src/form/field/trigger/SpinUp.mjs +1 -0
  392. package/src/form/field/trigger/Time.mjs +2 -0
  393. package/src/functional/_export.mjs +6 -0
  394. package/src/functional/button/Base.mjs +384 -0
  395. package/src/functional/component/Base.mjs +405 -0
  396. package/src/functional/defineComponent.mjs +102 -0
  397. package/src/functional/useConfig.mjs +52 -0
  398. package/src/functional/useEvent.mjs +43 -0
  399. package/src/grid/Body.mjs +20 -1
  400. package/src/grid/Container.mjs +50 -60
  401. package/src/grid/ScrollManager.mjs +2 -0
  402. package/src/grid/VerticalScrollbar.mjs +2 -0
  403. package/src/grid/column/Base.mjs +2 -0
  404. package/src/grid/header/Button.mjs +7 -0
  405. package/src/grid/header/Toolbar.mjs +6 -0
  406. package/src/grid/plugin/AnimateRows.mjs +2 -0
  407. package/src/layout/Base.mjs +3 -0
  408. package/src/layout/Card.mjs +1 -0
  409. package/src/layout/Cube.mjs +18 -4
  410. package/src/layout/Fit.mjs +1 -0
  411. package/src/layout/Flexbox.mjs +7 -0
  412. package/src/layout/Form.mjs +2 -0
  413. package/src/layout/Grid.mjs +1 -0
  414. package/src/layout/HBox.mjs +1 -0
  415. package/src/layout/VBox.mjs +1 -0
  416. package/src/list/Base.mjs +13 -0
  417. package/src/list/Chip.mjs +1 -0
  418. package/src/list/Circle.mjs +2 -0
  419. package/src/list/Color.mjs +1 -0
  420. package/src/list/plugin/Animate.mjs +2 -0
  421. package/src/main/DeltaUpdates.mjs +1 -0
  422. package/src/main/DomEvents.mjs +2 -0
  423. package/src/main/addon/CloneNode.mjs +1 -0
  424. package/src/main/addon/Cookie.mjs +1 -0
  425. package/src/main/addon/GoogleMaps.mjs +1 -0
  426. package/src/main/addon/LocalStorage.mjs +1 -0
  427. package/src/main/addon/MapboxGL.mjs +1 -0
  428. package/src/main/addon/Markdown.mjs +1 -0
  429. package/src/main/addon/Navigator.mjs +1 -0
  430. package/src/main/addon/Popover.mjs +1 -0
  431. package/src/main/addon/Stylesheet.mjs +1 -0
  432. package/src/main/addon/WindowPosition.mjs +1 -0
  433. package/src/manager/Component.mjs +0 -71
  434. package/src/manager/VDomUpdate.mjs +320 -0
  435. package/src/menu/List.mjs +6 -0
  436. package/src/menu/Model.mjs +1 -0
  437. package/src/menu/Panel.mjs +3 -0
  438. package/src/menu/Store.mjs +1 -0
  439. package/src/mixin/DomEvents.mjs +130 -0
  440. package/src/mixin/VdomLifecycle.mjs +670 -0
  441. package/src/plugin/Base.mjs +1 -0
  442. package/src/plugin/Resizable.mjs +2 -0
  443. package/src/selection/Model.mjs +15 -18
  444. package/src/selection/grid/BaseModel.mjs +1 -0
  445. package/src/sitemap/Component.mjs +1 -0
  446. package/src/state/Provider.mjs +129 -87
  447. package/src/state/createHierarchicalDataProxy.mjs +39 -25
  448. package/src/tab/Container.mjs +6 -0
  449. package/src/tab/Strip.mjs +1 -0
  450. package/src/tab/header/Button.mjs +2 -0
  451. package/src/tab/header/EffectButton.mjs +2 -0
  452. package/src/tab/header/Toolbar.mjs +1 -0
  453. package/src/table/Body.mjs +3 -0
  454. package/src/table/Container.mjs +10 -0
  455. package/src/table/header/Button.mjs +8 -0
  456. package/src/table/header/Toolbar.mjs +5 -0
  457. package/src/table/plugin/CellEditing.mjs +1 -0
  458. package/src/toolbar/Base.mjs +4 -0
  459. package/src/toolbar/Breadcrumb.mjs +3 -0
  460. package/src/toolbar/Paging.mjs +5 -0
  461. package/src/tooltip/Base.mjs +2 -0
  462. package/src/tree/List.mjs +3 -0
  463. package/src/util/HashHistory.mjs +1 -0
  464. package/src/util/KeyNavigation.mjs +2 -0
  465. package/src/util/Matrix.mjs +1 -0
  466. package/src/util/VDom.mjs +18 -5
  467. package/src/util/VNode.mjs +7 -1
  468. package/src/util/vdom/TreeBuilder.mjs +105 -0
  469. package/src/vdom/Helper.mjs +35 -23
  470. package/src/vdom/VNode.mjs +4 -6
  471. package/src/worker/App.mjs +1 -0
  472. package/src/worker/Base.mjs +2 -0
  473. package/src/worker/Manager.mjs +2 -0
  474. package/src/worker/ServiceBase.mjs +6 -1
  475. package/src/worker/mixin/RemoteMethodAccess.mjs +1 -6
  476. package/test/siesta/siesta.js +17 -2
  477. package/test/siesta/tests/VdomCalendar.mjs +19 -15
  478. package/test/siesta/tests/VdomHelper.mjs +7 -7
  479. package/test/siesta/tests/classic/Button.mjs +113 -0
  480. package/test/siesta/tests/core/Effect.mjs +10 -14
  481. package/test/siesta/tests/core/EffectBatching.mjs +72 -79
  482. package/test/siesta/tests/functional/Button.mjs +113 -0
  483. package/test/siesta/tests/state/ProviderNestedDataConfigs.mjs +314 -0
  484. package/test/siesta/tests/state/createHierarchicalDataProxy.mjs +42 -55
  485. package/test/siesta/tests/vdom/Advanced.mjs +14 -8
  486. package/test/siesta/tests/vdom/VdomAsymmetricUpdates.mjs +366 -0
  487. package/test/siesta/tests/vdom/VdomRealWorldUpdates.mjs +249 -0
  488. package/test/siesta/tests/vdom/layout/Cube.mjs +11 -7
  489. package/test/siesta/tests/vdom/table/Container.mjs +9 -5
  490. package/learn/javascript/NewNode.md +0 -31
  491. package/src/core/EffectBatchManager.mjs +0 -68
@@ -0,0 +1,405 @@
1
+ import Abstract from '../../component/Abstract.mjs';
2
+ import Effect from '../../core/Effect.mjs';
3
+ import NeoArray from '../../util/Array.mjs';
4
+
5
+ const
6
+ activeDomListenersSymbol = Symbol.for('activeDomListeners'),
7
+ hookIndexSymbol = Symbol.for('hookIndex'),
8
+ hooksSymbol = Symbol.for('hooks'),
9
+ pendingDomEventsSymbol = Symbol.for('pendingDomEvents'),
10
+ vdomToApplySymbol = Symbol('vdomToApply');
11
+
12
+ /**
13
+ * @class Neo.functional.component.Base
14
+ * @extends Neo.component.Abstract
15
+ */
16
+ class FunctionalBase extends Abstract {
17
+ static config = {
18
+ /**
19
+ * @member {String} className='Neo.functional.component.Base'
20
+ * @protected
21
+ */
22
+ className: 'Neo.functional.component.Base',
23
+ /**
24
+ * @member {String} ntype='functional-component'
25
+ * @protected
26
+ */
27
+ ntype: 'functional-component',
28
+ /**
29
+ * The vdom markup for this component.
30
+ * @member {Object} vdom={}
31
+ */
32
+ vdom: {}
33
+ }
34
+
35
+ /**
36
+ * Neo component instances, which got defined inside createVdom()
37
+ * @member {Map|null} childComponents=null
38
+ */
39
+ childComponents = null
40
+ /**
41
+ * Internal Map to store the next set of components after the createVdom() Effect has run.
42
+ * @member {Map|null} nextChildComponents=null
43
+ * @private
44
+ */
45
+ #nextChildComponents = null
46
+
47
+ /**
48
+ * @param {Object} config
49
+ */
50
+ construct(config) {
51
+ super.construct(config);
52
+
53
+ let me = this,
54
+ opts = {configurable: true, enumerable: false, writable: true};
55
+
56
+ Object.defineProperties(me, {
57
+ [activeDomListenersSymbol]: {...opts, value: []},
58
+ [hookIndexSymbol] : {...opts, value: 0},
59
+ [hooksSymbol] : {...opts, value: []},
60
+ [pendingDomEventsSymbol] : {...opts, value: []},
61
+ [vdomToApplySymbol] : {...opts, value: null}
62
+ });
63
+
64
+ // Creates a reactive effect that re-executes createVdom() when dependencies change.
65
+ me.vdomEffect = new Effect({
66
+ fn: () => {
67
+ me[hookIndexSymbol] = 0;
68
+ me[pendingDomEventsSymbol] = []; // Clear pending events for new render
69
+ me[vdomToApplySymbol] = me.createVdom(me)
70
+ },
71
+ componentId: me.id,
72
+ subscriber : {
73
+ id : me.id,
74
+ fn : me.onEffectRunStateChange,
75
+ scope: me
76
+ }
77
+ })
78
+ }
79
+
80
+ /**
81
+ * Triggered after the mounted config got changed
82
+ * @param {Boolean} value
83
+ * @param {Boolean} oldValue
84
+ * @protected
85
+ */
86
+ afterSetMounted(value, oldValue) {
87
+ super.afterSetMounted(value, oldValue);
88
+
89
+ if (oldValue !== undefined) {
90
+ const me = this;
91
+
92
+ if (value) { // mount
93
+ // Initial registration of DOM event listeners when component mounts
94
+ me.applyPendingDomListeners();
95
+ }
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Triggered after the windowId config got changed
101
+ * @param {Number|null} value
102
+ * @param {Number|null} oldValue
103
+ * @protected
104
+ */
105
+ afterSetWindowId(value, oldValue) {
106
+ super.afterSetWindowId(value, oldValue);
107
+
108
+ this.childComponents?.forEach(childData => {
109
+ childData.instance.windowId = value
110
+ })
111
+ }
112
+
113
+ /**
114
+ * Applies the pending DOM event listeners and updates the active list.
115
+ * @private
116
+ */
117
+ applyPendingDomListeners() {
118
+ const
119
+ me = this,
120
+ activeEvents = me[activeDomListenersSymbol],
121
+ pendingEvents = me[pendingDomEventsSymbol];
122
+
123
+ if (pendingEvents.length > 0) {
124
+ if (!Neo.isEqual(activeEvents, pendingEvents)) {
125
+ if (activeEvents?.length > 0) {
126
+ // Remove old dynamic listeners
127
+ me.removeDomListeners(me[activeDomListenersSymbol])
128
+ }
129
+
130
+ me.addDomListeners([...pendingEvents]);
131
+
132
+ me[activeDomListenersSymbol] = [...pendingEvents]
133
+ }
134
+
135
+ // Clear pending events for next `createVdom()` Effect run
136
+ me[pendingDomEventsSymbol] = []
137
+ }
138
+ }
139
+
140
+ /**
141
+ * A lifecycle hook that runs after a state change has been detected but before the
142
+ * VDOM update is dispatched. It provides a dedicated place for logic that needs to
143
+ * execute before rendering, such as calculating derived data or caching values.
144
+ *
145
+ * You can prevent the VDOM update by returning `false` from this method. This is
146
+ * useful for advanced cases where you might want to manually trigger a different
147
+ * update after modifying other component configs.
148
+ *
149
+ * **IMPORTANT**: Do not change the value of any config that is used as a dependency
150
+ * within the `createVdom` method from inside this hook, as it will cause an
151
+ * infinite update loop. This hook is for one-way data flow, not for triggering
152
+ * cascading reactive changes.
153
+ *
154
+ * @returns {Boolean|undefined} Return `false` to cancel the upcoming VDOM update.
155
+ * @example
156
+ * beforeUpdate() {
157
+ * // Perform an expensive calculation and cache the result on the instance
158
+ * this.processedData = this.processRawData(this.rawData);
159
+ *
160
+ * // Example of conditionally cancelling an update
161
+ * if (this.processedData.length === 0 && this.vdom.cn?.length === 0) {
162
+ * return false; // Don't re-render if there's nothing to show
163
+ * }
164
+ * }
165
+ */
166
+ beforeUpdate() {
167
+ // This method can be overridden by subclasses
168
+ }
169
+
170
+ /**
171
+ * Override this method in your functional component to return its VDOM structure.
172
+ * This method will be automatically re-executed when any of the component's configs change.
173
+ * To access data from a state provider, use `config.data`.
174
+ * @param {Neo.functional.component.Base} config - Mental model: while it contains the instance, it makes it clear to access configs
175
+ * @returns {Object} The VDOM structure for the component.
176
+ */
177
+ createVdom(config) {
178
+ // This method should be overridden by subclasses
179
+ return {}
180
+ }
181
+
182
+ /**
183
+ *
184
+ */
185
+ destroy() {
186
+ const me = this;
187
+
188
+ me.vdomEffect?.destroy();
189
+
190
+ // Destroy all classic components instantiated by this functional component
191
+ me.childComponents?.forEach(childData => {
192
+ childData.instance.destroy()
193
+ });
194
+ me.childComponents?.clear();
195
+
196
+ // Remove any pending DOM event listeners that might not have been mounted
197
+ me[pendingDomEventsSymbol] = null;
198
+
199
+ super.destroy()
200
+ }
201
+
202
+ /**
203
+ * This method recursively compares the new VDOM config with the last applied config
204
+ * for a given component instance and its sub-instances.
205
+ * @param {Neo.core.Base} instance The component instance to update.
206
+ * @param {Object} newConfig The new configuration object from the VDOM.
207
+ * @param {Object} lastConfig The last applied configuration object.
208
+ * @private
209
+ */
210
+ diffAndSet(instance, newConfig, lastConfig) {
211
+ const deltaConfig = {};
212
+
213
+ for (const key in newConfig) {
214
+ const newValue = newConfig[key],
215
+ oldValue = lastConfig[key];
216
+
217
+ if (!Neo.isEqual(newValue, oldValue)) {
218
+ // If the config property is an object and it maps to a sub-component instance, recurse.
219
+ if (Neo.typeOf(newValue) === 'Object' && Neo.typeOf(instance[key]) === 'NeoInstance') {
220
+ this.diffAndSet(instance[key], newValue, oldValue || {})
221
+ } else {
222
+ // Otherwise, add it to the delta to be set on the current instance.
223
+ deltaConfig[key] = newValue
224
+ }
225
+ }
226
+ }
227
+
228
+ // Only call set() if there are actual changes for the current instance.
229
+ if (Object.keys(deltaConfig).length > 0) {
230
+ instance.set(deltaConfig)
231
+ }
232
+ }
233
+
234
+ /**
235
+ * This handler runs when the effect's `isRunning` state changes.
236
+ * It runs outside the effect's tracking scope, preventing feedback loops.
237
+ * @param {Boolean} value
238
+ * @param {Boolean} oldValue
239
+ */
240
+ onEffectRunStateChange(value, oldValue) {
241
+ // When the effect has just finished running...
242
+ if (value === false) {
243
+ const me = this,
244
+ newVdom = me[vdomToApplySymbol];
245
+
246
+ if (newVdom) {
247
+ // Create a new map for components instantiated in this render cycle
248
+ me.#nextChildComponents = new Map();
249
+
250
+ // Process the newVdom to instantiate components
251
+ // The parentId for these components will be the functional component's id
252
+ const processedVdom = me.processVdomForComponents(newVdom, me.id);
253
+
254
+ // Destroy components that are no longer present in the new VDOM
255
+ if (me.childComponents?.size > 0) {
256
+ [...me.childComponents].forEach(([key, childData]) => {
257
+ if (!me.#nextChildComponents.has(key)) {
258
+ me.childComponents.delete(key);
259
+ childData.instance.destroy()
260
+ }
261
+ })
262
+ }
263
+
264
+ // If this component created other classic or functional components,
265
+ // include their full vdom into the next update cycle.
266
+ const oldKeys = me.childComponents ? new Set(me.childComponents.keys()) : new Set();
267
+ let hasNewChildren = false;
268
+
269
+ for (const newKey of me.#nextChildComponents.keys()) {
270
+ if (!oldKeys.has(newKey)) {
271
+ hasNewChildren = true;
272
+ break
273
+ }
274
+ }
275
+
276
+ if (hasNewChildren) {
277
+ // When new child components are created, we need to send their full VDOM
278
+ // to the vdom-worker, so they can get rendered.
279
+ // Subsequent updates will be granular via diffAndSet() => set() on the child.
280
+ me.updateDepth = -1;
281
+ }
282
+
283
+ // Update the main map of instantiated components
284
+ me.childComponents = me.#nextChildComponents;
285
+
286
+ // Clear the old vdom properties
287
+ for (const key in me.vdom) {
288
+ delete me.vdom[key]
289
+ }
290
+
291
+ // Assign the new properties
292
+ Object.assign(me.vdom, processedVdom); // Use processedVdom here
293
+
294
+ me[vdomToApplySymbol] = null;
295
+
296
+ const root = me.getVdomRoot();
297
+
298
+ if (me.cls) {
299
+ root.cls = NeoArray.union(me.cls, root.cls)
300
+ }
301
+
302
+ if (me.id) {
303
+ root.id = me.id
304
+ }
305
+
306
+ // Re-hydrate the new vdom with stable IDs from the previous vnode tree.
307
+ // This is crucial for functional components where the vdom is recreated on every render,
308
+ // ensuring the diffing algorithm can track nodes correctly.
309
+ me.syncVdomIds();
310
+
311
+ if (me.beforeUpdate() !== false) {
312
+ me.updateVdom()
313
+ }
314
+
315
+ // Update DOM event listeners based on the new render
316
+ if (me.mounted) {
317
+ me.applyPendingDomListeners()
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ /**
324
+ * Recursively processes a VDOM node to instantiate components defined within it.
325
+ * @param {Object} vdomTree The VDOM node to process.
326
+ * @param {String} parentId The ID of the parent component (the functional component hosting it).
327
+ * @param {Number} [parentIndex] The index of the vdomNode within its parent's children.
328
+ * @returns {Object} The processed VDOM node, potentially replaced with a component reference.
329
+ * @private
330
+ */
331
+ processVdomForComponents(vdomTree, parentId, parentIndex) {
332
+ if (!vdomTree) {
333
+ return vdomTree
334
+ }
335
+
336
+ // If it's already a component reference, no need to process further
337
+ if (vdomTree.componentId) {
338
+ return vdomTree
339
+ }
340
+
341
+ const me = this;
342
+
343
+ // Check if it's a component definition (functional or classic)
344
+ if (vdomTree.className || vdomTree.module || vdomTree.ntype) {
345
+ // Components are reconciled based on their `id` property in the VDOM definition.
346
+ // If no `id` is provided, a new instance will be created on every render.
347
+ const componentKey = vdomTree.id;
348
+
349
+ if (!componentKey) {
350
+ console.error([
351
+ 'Component definition in functional component VDOM is missing an "id". For stable reconciliation, ',
352
+ 'especially in dynamic lists, provide a unique "id" property.'
353
+ ].join(''),
354
+ vdomTree
355
+ )
356
+ }
357
+
358
+ let childData = me.childComponents?.get(componentKey),
359
+ newConfig = {...vdomTree}, // Shallow copy
360
+ instance;
361
+
362
+ delete newConfig.className;
363
+ delete newConfig.id;
364
+ delete newConfig.module;
365
+ delete newConfig.ntype;
366
+
367
+ if (!childData) {
368
+ me.childComponents ??= new Map();
369
+
370
+ // Instantiate the component
371
+ instance = Neo[(vdomTree.className || vdomTree.module) ? 'create' : 'ntype']({
372
+ ...vdomTree,
373
+ parentId,
374
+ parentIndex,
375
+ windowId: me.windowId
376
+ });
377
+ } else {
378
+ instance = childData.instance;
379
+
380
+ // Recursively diff and set configs
381
+ this.diffAndSet(instance, newConfig, childData.lastConfig);
382
+ }
383
+
384
+ // Add to the new map for tracking in this render cycle
385
+ me.#nextChildComponents.set(componentKey, {
386
+ instance,
387
+ lastConfig: newConfig
388
+ });
389
+
390
+ // Replace the definition with a reference using the component's own method
391
+ return instance.createVdomReference();
392
+ }
393
+
394
+ // Recursively process children
395
+ if (vdomTree.cn && Array.isArray(vdomTree.cn)) {
396
+ vdomTree.cn = vdomTree.cn.map((child, index) =>
397
+ me.processVdomForComponents(child, parentId, index)
398
+ )
399
+ }
400
+
401
+ return vdomTree
402
+ }
403
+ }
404
+
405
+ 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;