mvc-kit 2.12.5 → 2.13.1

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 (382) hide show
  1. package/BEST_PRACTICES.md +1390 -0
  2. package/agent-config/bin/postinstall.mjs +4 -3
  3. package/agent-config/bin/setup.mjs +5 -1
  4. package/agent-config/claude-code/agents/mvc-kit-architect.md +16 -8
  5. package/agent-config/claude-code/skills/guide/SKILL.md +29 -7
  6. package/agent-config/claude-code/skills/guide/patterns.md +12 -0
  7. package/agent-config/claude-code/skills/guide/recipes.md +510 -0
  8. package/agent-config/claude-code/skills/guide/testing.md +297 -0
  9. package/agent-config/claude-code/skills/review/SKILL.md +3 -13
  10. package/agent-config/claude-code/skills/review/checklist.md +30 -5
  11. package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
  12. package/agent-config/lib/install-claude.mjs +90 -25
  13. package/dist/Channel.cjs +276 -300
  14. package/dist/Channel.cjs.map +1 -1
  15. package/dist/Channel.js +275 -299
  16. package/dist/Channel.js.map +1 -1
  17. package/dist/Collection.cjs +424 -504
  18. package/dist/Collection.cjs.map +1 -1
  19. package/dist/Collection.js +423 -503
  20. package/dist/Collection.js.map +1 -1
  21. package/dist/Controller.cjs +70 -67
  22. package/dist/Controller.cjs.map +1 -1
  23. package/dist/Controller.js +69 -66
  24. package/dist/Controller.js.map +1 -1
  25. package/dist/EventBus.cjs +77 -88
  26. package/dist/EventBus.cjs.map +1 -1
  27. package/dist/EventBus.js +76 -87
  28. package/dist/EventBus.js.map +1 -1
  29. package/dist/Feed.cjs +81 -77
  30. package/dist/Feed.cjs.map +1 -1
  31. package/dist/Feed.js +80 -76
  32. package/dist/Feed.js.map +1 -1
  33. package/dist/Model.cjs +181 -207
  34. package/dist/Model.cjs.map +1 -1
  35. package/dist/Model.js +179 -205
  36. package/dist/Model.js.map +1 -1
  37. package/dist/Pagination.cjs +75 -73
  38. package/dist/Pagination.cjs.map +1 -1
  39. package/dist/Pagination.js +74 -72
  40. package/dist/Pagination.js.map +1 -1
  41. package/dist/Pending.cjs +255 -287
  42. package/dist/Pending.cjs.map +1 -1
  43. package/dist/Pending.js +253 -285
  44. package/dist/Pending.js.map +1 -1
  45. package/dist/PersistentCollection.cjs +242 -285
  46. package/dist/PersistentCollection.cjs.map +1 -1
  47. package/dist/PersistentCollection.js +241 -284
  48. package/dist/PersistentCollection.js.map +1 -1
  49. package/dist/Resource.cjs +166 -174
  50. package/dist/Resource.cjs.map +1 -1
  51. package/dist/Resource.js +164 -172
  52. package/dist/Resource.js.map +1 -1
  53. package/dist/Selection.cjs +84 -94
  54. package/dist/Selection.cjs.map +1 -1
  55. package/dist/Selection.js +83 -93
  56. package/dist/Selection.js.map +1 -1
  57. package/dist/Service.cjs +54 -55
  58. package/dist/Service.cjs.map +1 -1
  59. package/dist/Service.js +53 -54
  60. package/dist/Service.js.map +1 -1
  61. package/dist/Sorting.cjs +102 -101
  62. package/dist/Sorting.cjs.map +1 -1
  63. package/dist/Sorting.js +102 -101
  64. package/dist/Sorting.js.map +1 -1
  65. package/dist/Trackable.cjs +112 -80
  66. package/dist/Trackable.cjs.map +1 -1
  67. package/dist/Trackable.js +111 -79
  68. package/dist/Trackable.js.map +1 -1
  69. package/dist/ViewModel.cjs +528 -576
  70. package/dist/ViewModel.cjs.map +1 -1
  71. package/dist/ViewModel.js +525 -573
  72. package/dist/ViewModel.js.map +1 -1
  73. package/dist/bindPublicMethods.cjs +43 -24
  74. package/dist/bindPublicMethods.cjs.map +1 -1
  75. package/dist/bindPublicMethods.js +43 -24
  76. package/dist/bindPublicMethods.js.map +1 -1
  77. package/dist/errors.cjs +67 -68
  78. package/dist/errors.cjs.map +1 -1
  79. package/dist/errors.js +68 -71
  80. package/dist/errors.js.map +1 -1
  81. package/dist/mvc-kit.cjs +44 -46
  82. package/dist/mvc-kit.js +5 -32
  83. package/dist/produceDraft.cjs +105 -95
  84. package/dist/produceDraft.cjs.map +1 -1
  85. package/dist/produceDraft.js +106 -97
  86. package/dist/produceDraft.js.map +1 -1
  87. package/dist/react/components/CardList.cjs +30 -40
  88. package/dist/react/components/CardList.cjs.map +1 -1
  89. package/dist/react/components/CardList.js +31 -41
  90. package/dist/react/components/CardList.js.map +1 -1
  91. package/dist/react/components/DataTable.cjs +146 -169
  92. package/dist/react/components/DataTable.cjs.map +1 -1
  93. package/dist/react/components/DataTable.js +147 -170
  94. package/dist/react/components/DataTable.js.map +1 -1
  95. package/dist/react/components/InfiniteScroll.cjs +51 -42
  96. package/dist/react/components/InfiniteScroll.cjs.map +1 -1
  97. package/dist/react/components/InfiniteScroll.js +52 -43
  98. package/dist/react/components/InfiniteScroll.js.map +1 -1
  99. package/dist/react/components/types.cjs +10 -6
  100. package/dist/react/components/types.cjs.map +1 -1
  101. package/dist/react/components/types.js +11 -9
  102. package/dist/react/components/types.js.map +1 -1
  103. package/dist/react/guards.cjs +10 -6
  104. package/dist/react/guards.cjs.map +1 -1
  105. package/dist/react/guards.js +11 -9
  106. package/dist/react/guards.js.map +1 -1
  107. package/dist/react/provider.cjs +23 -20
  108. package/dist/react/provider.cjs.map +1 -1
  109. package/dist/react/provider.js +23 -21
  110. package/dist/react/provider.js.map +1 -1
  111. package/dist/react/use-event-bus.cjs +24 -20
  112. package/dist/react/use-event-bus.cjs.map +1 -1
  113. package/dist/react/use-event-bus.js +24 -21
  114. package/dist/react/use-event-bus.js.map +1 -1
  115. package/dist/react/use-instance.cjs +43 -36
  116. package/dist/react/use-instance.cjs.map +1 -1
  117. package/dist/react/use-instance.js +43 -36
  118. package/dist/react/use-instance.js.map +1 -1
  119. package/dist/react/use-local.cjs +48 -64
  120. package/dist/react/use-local.cjs.map +1 -1
  121. package/dist/react/use-local.js +47 -63
  122. package/dist/react/use-local.js.map +1 -1
  123. package/dist/react/use-model.cjs +84 -98
  124. package/dist/react/use-model.cjs.map +1 -1
  125. package/dist/react/use-model.js +84 -100
  126. package/dist/react/use-model.js.map +1 -1
  127. package/dist/react/use-singleton.cjs +19 -23
  128. package/dist/react/use-singleton.cjs.map +1 -1
  129. package/dist/react/use-singleton.js +16 -20
  130. package/dist/react/use-singleton.js.map +1 -1
  131. package/dist/react/use-subscribe-only.cjs +28 -22
  132. package/dist/react/use-subscribe-only.cjs.map +1 -1
  133. package/dist/react/use-subscribe-only.js +28 -22
  134. package/dist/react/use-subscribe-only.js.map +1 -1
  135. package/dist/react/use-teardown.cjs +20 -19
  136. package/dist/react/use-teardown.cjs.map +1 -1
  137. package/dist/react/use-teardown.js +20 -19
  138. package/dist/react/use-teardown.js.map +1 -1
  139. package/dist/react-native/NativeCollection.cjs +98 -78
  140. package/dist/react-native/NativeCollection.cjs.map +1 -1
  141. package/dist/react-native/NativeCollection.js +97 -77
  142. package/dist/react-native/NativeCollection.js.map +1 -1
  143. package/dist/react-native.cjs +2 -4
  144. package/dist/react-native.js +1 -4
  145. package/dist/react.cjs +24 -26
  146. package/dist/react.js +1 -17
  147. package/dist/singleton.cjs +28 -22
  148. package/dist/singleton.cjs.map +1 -1
  149. package/dist/singleton.js +29 -26
  150. package/dist/singleton.js.map +1 -1
  151. package/dist/walkPrototypeChain.cjs +20 -12
  152. package/dist/walkPrototypeChain.cjs.map +1 -1
  153. package/dist/walkPrototypeChain.js +21 -13
  154. package/dist/walkPrototypeChain.js.map +1 -1
  155. package/dist/web/IndexedDBCollection.cjs +53 -36
  156. package/dist/web/IndexedDBCollection.cjs.map +1 -1
  157. package/dist/web/IndexedDBCollection.js +52 -35
  158. package/dist/web/IndexedDBCollection.js.map +1 -1
  159. package/dist/web/WebStorageCollection.cjs +82 -84
  160. package/dist/web/WebStorageCollection.cjs.map +1 -1
  161. package/dist/web/WebStorageCollection.js +81 -83
  162. package/dist/web/WebStorageCollection.js.map +1 -1
  163. package/dist/web/idb.cjs +107 -99
  164. package/dist/web/idb.cjs.map +1 -1
  165. package/dist/web/idb.js +108 -105
  166. package/dist/web/idb.js.map +1 -1
  167. package/dist/web.cjs +4 -6
  168. package/dist/web.js +1 -5
  169. package/dist/wrapAsyncMethods.cjs +141 -168
  170. package/dist/wrapAsyncMethods.cjs.map +1 -1
  171. package/dist/wrapAsyncMethods.js +141 -168
  172. package/dist/wrapAsyncMethods.js.map +1 -1
  173. package/examples/primitive/channel.ts +109 -0
  174. package/examples/primitive/collection.ts +118 -0
  175. package/examples/primitive/controller.ts +118 -0
  176. package/examples/primitive/counter.ts +108 -0
  177. package/examples/primitive/env.d.ts +1 -0
  178. package/examples/primitive/eventbus.ts +77 -0
  179. package/examples/primitive/feed.ts +162 -0
  180. package/examples/primitive/model.ts +82 -0
  181. package/examples/primitive/pagination.ts +91 -0
  182. package/examples/primitive/pending.ts +189 -0
  183. package/examples/primitive/persistent-collection.ts +116 -0
  184. package/examples/primitive/resource.ts +114 -0
  185. package/examples/primitive/selection.ts +96 -0
  186. package/examples/primitive/sorting.ts +112 -0
  187. package/examples/primitive/timer.ts +58 -0
  188. package/examples/primitive/trackable.ts +225 -0
  189. package/examples/primitive/tsconfig.json +20 -0
  190. package/examples/primitive/viewmodel-service.ts +161 -0
  191. package/examples/react/AuthExample/index.html +12 -0
  192. package/examples/react/AuthExample/src/App.tsx +29 -0
  193. package/examples/react/AuthExample/src/components/AdminPage.tsx +51 -0
  194. package/examples/react/AuthExample/src/components/AppHeader.tsx +32 -0
  195. package/examples/react/AuthExample/src/components/AuthGuard.tsx +50 -0
  196. package/examples/react/AuthExample/src/components/AuthScreen.tsx +181 -0
  197. package/examples/react/AuthExample/src/components/DashboardPage.tsx +41 -0
  198. package/examples/react/AuthExample/src/components/ProfilePage.tsx +44 -0
  199. package/examples/react/AuthExample/src/components/Toast.tsx +41 -0
  200. package/examples/react/AuthExample/src/env.d.ts +10 -0
  201. package/examples/react/AuthExample/src/events/AppEventBus.ts +7 -0
  202. package/examples/react/AuthExample/src/main.tsx +10 -0
  203. package/examples/react/AuthExample/src/mock/api.ts +78 -0
  204. package/examples/react/AuthExample/src/models/LoginFormModel.ts +19 -0
  205. package/examples/react/AuthExample/src/models/RegisterFormModel.ts +25 -0
  206. package/examples/react/AuthExample/src/services/AuthService.ts +21 -0
  207. package/examples/react/AuthExample/src/styles.css +445 -0
  208. package/examples/react/AuthExample/src/types/auth.ts +12 -0
  209. package/examples/react/AuthExample/src/viewmodels/AuthViewModel.ts +111 -0
  210. package/examples/react/AuthExample/tsconfig.json +22 -0
  211. package/examples/react/AuthExample/vite.config.ts +18 -0
  212. package/examples/react/ComplexApp/index.html +12 -0
  213. package/examples/react/ComplexApp/src/App.tsx +17 -0
  214. package/examples/react/ComplexApp/src/channels/ActivityChannel.ts +24 -0
  215. package/examples/react/ComplexApp/src/channels/DashboardChannel.ts +26 -0
  216. package/examples/react/ComplexApp/src/channels/ErrorsChannel.ts +5 -0
  217. package/examples/react/ComplexApp/src/channels/LatencyChannel.ts +5 -0
  218. package/examples/react/ComplexApp/src/channels/OrdersChannel.ts +5 -0
  219. package/examples/react/ComplexApp/src/channels/RevenueChannel.ts +5 -0
  220. package/examples/react/ComplexApp/src/channels/TrafficChannel.ts +5 -0
  221. package/examples/react/ComplexApp/src/channels/UsersMetricChannel.ts +5 -0
  222. package/examples/react/ComplexApp/src/collections/DashboardCollection.ts +6 -0
  223. package/examples/react/ComplexApp/src/collections/ErrorsCollection.ts +3 -0
  224. package/examples/react/ComplexApp/src/collections/LatencyCollection.ts +3 -0
  225. package/examples/react/ComplexApp/src/collections/OrdersCollection.ts +3 -0
  226. package/examples/react/ComplexApp/src/collections/RevenueCollection.ts +3 -0
  227. package/examples/react/ComplexApp/src/collections/TrafficCollection.ts +3 -0
  228. package/examples/react/ComplexApp/src/collections/UsersMetricCollection.ts +3 -0
  229. package/examples/react/ComplexApp/src/components/activity/ActivityFeed.tsx +31 -0
  230. package/examples/react/ComplexApp/src/components/activity/ActivityItemRow.tsx +35 -0
  231. package/examples/react/ComplexApp/src/components/dashboard/DashboardCard.tsx +37 -0
  232. package/examples/react/ComplexApp/src/components/dashboard/DashboardPage.tsx +34 -0
  233. package/examples/react/ComplexApp/src/components/layout/Navbar.tsx +32 -0
  234. package/examples/react/ComplexApp/src/components/layout/SocialFeedPanel.tsx +57 -0
  235. package/examples/react/ComplexApp/src/components/shared/Spinner.tsx +3 -0
  236. package/examples/react/ComplexApp/src/components/shared/StatusIndicator.tsx +13 -0
  237. package/examples/react/ComplexApp/src/components/shared/Toast.tsx +40 -0
  238. package/examples/react/ComplexApp/src/env.d.ts +10 -0
  239. package/examples/react/ComplexApp/src/events/AppEventBus.ts +7 -0
  240. package/examples/react/ComplexApp/src/main.tsx +10 -0
  241. package/examples/react/ComplexApp/src/mock-remote/MockWebSocket.ts +38 -0
  242. package/examples/react/ComplexApp/src/mock-remote/activity-api.ts +48 -0
  243. package/examples/react/ComplexApp/src/mock-remote/dashboard-generators.ts +45 -0
  244. package/examples/react/ComplexApp/src/mock-remote/delay.ts +18 -0
  245. package/examples/react/ComplexApp/src/mock-remote/social-api.ts +55 -0
  246. package/examples/react/ComplexApp/src/resources/ActivityResource.ts +12 -0
  247. package/examples/react/ComplexApp/src/resources/SocialFeedResource.ts +17 -0
  248. package/examples/react/ComplexApp/src/styles.css +463 -0
  249. package/examples/react/ComplexApp/src/types/activity.ts +8 -0
  250. package/examples/react/ComplexApp/src/types/dashboard.ts +5 -0
  251. package/examples/react/ComplexApp/src/types/social.ts +8 -0
  252. package/examples/react/ComplexApp/src/types/users.ts +6 -0
  253. package/examples/react/ComplexApp/src/viewmodels/ActivityFeedViewModel.ts +68 -0
  254. package/examples/react/ComplexApp/src/viewmodels/AppStateViewModel.ts +26 -0
  255. package/examples/react/ComplexApp/src/viewmodels/DashboardCardViewModel.ts +69 -0
  256. package/examples/react/ComplexApp/src/viewmodels/ErrorsCardViewModel.ts +9 -0
  257. package/examples/react/ComplexApp/src/viewmodels/LatencyCardViewModel.ts +9 -0
  258. package/examples/react/ComplexApp/src/viewmodels/OrdersCardViewModel.ts +9 -0
  259. package/examples/react/ComplexApp/src/viewmodels/RevenueCardViewModel.ts +9 -0
  260. package/examples/react/ComplexApp/src/viewmodels/SocialFeedViewModel.ts +39 -0
  261. package/examples/react/ComplexApp/src/viewmodels/TrafficCardViewModel.ts +9 -0
  262. package/examples/react/ComplexApp/src/viewmodels/UsersMetricCardViewModel.ts +9 -0
  263. package/examples/react/ComplexApp/tsconfig.json +22 -0
  264. package/examples/react/ComplexApp/vite.config.ts +18 -0
  265. package/examples/react/FullApp/index.html +12 -0
  266. package/examples/react/FullApp/src/App.tsx +28 -0
  267. package/examples/react/FullApp/src/collections/ConversationsCollection.ts +4 -0
  268. package/examples/react/FullApp/src/collections/LocationsCollection.ts +4 -0
  269. package/examples/react/FullApp/src/components/auth/LoginPage.tsx +80 -0
  270. package/examples/react/FullApp/src/components/dashboard/DashboardPage.tsx +29 -0
  271. package/examples/react/FullApp/src/components/dashboard/RecentActivityCard.tsx +35 -0
  272. package/examples/react/FullApp/src/components/dashboard/StatsCard.tsx +19 -0
  273. package/examples/react/FullApp/src/components/layout/AppShell.tsx +31 -0
  274. package/examples/react/FullApp/src/components/layout/Header.tsx +25 -0
  275. package/examples/react/FullApp/src/components/layout/Sidebar.tsx +29 -0
  276. package/examples/react/FullApp/src/components/locations/LocationFilters.tsx +60 -0
  277. package/examples/react/FullApp/src/components/locations/LocationForm.tsx +112 -0
  278. package/examples/react/FullApp/src/components/locations/LocationProfilePage.tsx +81 -0
  279. package/examples/react/FullApp/src/components/locations/LocationsPage.tsx +127 -0
  280. package/examples/react/FullApp/src/components/messaging/ConversationList.tsx +59 -0
  281. package/examples/react/FullApp/src/components/messaging/MessageBubble.tsx +22 -0
  282. package/examples/react/FullApp/src/components/messaging/MessageThread.tsx +100 -0
  283. package/examples/react/FullApp/src/components/messaging/MessagingPage.tsx +52 -0
  284. package/examples/react/FullApp/src/components/shared/ErrorBanner.tsx +3 -0
  285. package/examples/react/FullApp/src/components/shared/Spinner.tsx +7 -0
  286. package/examples/react/FullApp/src/components/shared/Toast.tsx +41 -0
  287. package/examples/react/FullApp/src/components/users/UserFilters.tsx +59 -0
  288. package/examples/react/FullApp/src/components/users/UsersPage.tsx +80 -0
  289. package/examples/react/FullApp/src/components/users/UsersTable.tsx +52 -0
  290. package/examples/react/FullApp/src/env.d.ts +10 -0
  291. package/examples/react/FullApp/src/events/AppEventBus.ts +7 -0
  292. package/examples/react/FullApp/src/main.tsx +10 -0
  293. package/examples/react/FullApp/src/mock/delay.ts +21 -0
  294. package/examples/react/FullApp/src/mock/locations.ts +76 -0
  295. package/examples/react/FullApp/src/mock/messages.ts +237 -0
  296. package/examples/react/FullApp/src/mock/users.ts +84 -0
  297. package/examples/react/FullApp/src/models/LocationFormModel.ts +31 -0
  298. package/examples/react/FullApp/src/models/LoginFormModel.ts +19 -0
  299. package/examples/react/FullApp/src/resources/UsersResource.ts +12 -0
  300. package/examples/react/FullApp/src/services/AuthService.ts +18 -0
  301. package/examples/react/FullApp/src/services/LocationService.ts +23 -0
  302. package/examples/react/FullApp/src/services/MessageService.ts +65 -0
  303. package/examples/react/FullApp/src/services/UserService.ts +23 -0
  304. package/examples/react/FullApp/src/styles.css +767 -0
  305. package/examples/react/FullApp/src/types/conversation.ts +7 -0
  306. package/examples/react/FullApp/src/types/location.ts +12 -0
  307. package/examples/react/FullApp/src/types/message.ts +7 -0
  308. package/examples/react/FullApp/src/types/user.ts +10 -0
  309. package/examples/react/FullApp/src/viewmodels/AuthViewModel.ts +51 -0
  310. package/examples/react/FullApp/src/viewmodels/ConversationsViewModel.ts +89 -0
  311. package/examples/react/FullApp/src/viewmodels/DashboardViewModel.ts +56 -0
  312. package/examples/react/FullApp/src/viewmodels/LocationProfileViewModel.ts +81 -0
  313. package/examples/react/FullApp/src/viewmodels/LocationsViewModel.ts +113 -0
  314. package/examples/react/FullApp/src/viewmodels/MessageThreadViewModel.ts +83 -0
  315. package/examples/react/FullApp/src/viewmodels/UsersViewModel.ts +88 -0
  316. package/examples/react/FullApp/tsconfig.json +22 -0
  317. package/examples/react/FullApp/vite.config.ts +18 -0
  318. package/examples/react/WorkerApp/index.html +12 -0
  319. package/examples/react/WorkerApp/src/App.tsx +24 -0
  320. package/examples/react/WorkerApp/src/channels/MessagingChannel.ts +46 -0
  321. package/examples/react/WorkerApp/src/channels/WorkerStatusChannel.ts +35 -0
  322. package/examples/react/WorkerApp/src/components/auth/LoginPage.tsx +60 -0
  323. package/examples/react/WorkerApp/src/components/layout/AppShell.tsx +31 -0
  324. package/examples/react/WorkerApp/src/components/layout/Header.tsx +23 -0
  325. package/examples/react/WorkerApp/src/components/layout/Sidebar.tsx +28 -0
  326. package/examples/react/WorkerApp/src/components/messaging/ComposeBar.tsx +33 -0
  327. package/examples/react/WorkerApp/src/components/messaging/ConversationList.tsx +59 -0
  328. package/examples/react/WorkerApp/src/components/messaging/MessageBubble.tsx +45 -0
  329. package/examples/react/WorkerApp/src/components/messaging/MessageThread.tsx +93 -0
  330. package/examples/react/WorkerApp/src/components/messaging/MessagingPage.tsx +53 -0
  331. package/examples/react/WorkerApp/src/components/shared/ErrorBanner.tsx +3 -0
  332. package/examples/react/WorkerApp/src/components/shared/PendingBanner.tsx +37 -0
  333. package/examples/react/WorkerApp/src/components/shared/Spinner.tsx +7 -0
  334. package/examples/react/WorkerApp/src/components/shared/Toast.tsx +41 -0
  335. package/examples/react/WorkerApp/src/components/shift/ShiftPage.tsx +98 -0
  336. package/examples/react/WorkerApp/src/components/shift/ShiftTimer.tsx +24 -0
  337. package/examples/react/WorkerApp/src/components/shift/SiteSelector.tsx +27 -0
  338. package/examples/react/WorkerApp/src/components/sites/SiteFilters.tsx +61 -0
  339. package/examples/react/WorkerApp/src/components/sites/SitesPage.tsx +102 -0
  340. package/examples/react/WorkerApp/src/env.d.ts +10 -0
  341. package/examples/react/WorkerApp/src/events/AppEventBus.ts +7 -0
  342. package/examples/react/WorkerApp/src/main.tsx +10 -0
  343. package/examples/react/WorkerApp/src/mock/MockWebSocket.ts +38 -0
  344. package/examples/react/WorkerApp/src/mock/delay.ts +31 -0
  345. package/examples/react/WorkerApp/src/mock/messages.ts +120 -0
  346. package/examples/react/WorkerApp/src/mock/shifts.ts +57 -0
  347. package/examples/react/WorkerApp/src/mock/sites.ts +14 -0
  348. package/examples/react/WorkerApp/src/mock/workers.ts +12 -0
  349. package/examples/react/WorkerApp/src/models/ComposeMessageModel.ts +17 -0
  350. package/examples/react/WorkerApp/src/resources/ConversationsResource.ts +10 -0
  351. package/examples/react/WorkerApp/src/resources/MessagesResource.ts +32 -0
  352. package/examples/react/WorkerApp/src/resources/ShiftResource.ts +73 -0
  353. package/examples/react/WorkerApp/src/resources/SitesResource.ts +11 -0
  354. package/examples/react/WorkerApp/src/resources/WorkersResource.ts +11 -0
  355. package/examples/react/WorkerApp/src/styles.css +756 -0
  356. package/examples/react/WorkerApp/src/types/conversation.ts +7 -0
  357. package/examples/react/WorkerApp/src/types/message.ts +7 -0
  358. package/examples/react/WorkerApp/src/types/shift.ts +13 -0
  359. package/examples/react/WorkerApp/src/types/site.ts +8 -0
  360. package/examples/react/WorkerApp/src/types/worker.ts +8 -0
  361. package/examples/react/WorkerApp/src/viewmodels/AuthViewModel.ts +41 -0
  362. package/examples/react/WorkerApp/src/viewmodels/ConversationsViewModel.ts +83 -0
  363. package/examples/react/WorkerApp/src/viewmodels/MessageThreadViewModel.ts +113 -0
  364. package/examples/react/WorkerApp/src/viewmodels/ShiftViewModel.ts +147 -0
  365. package/examples/react/WorkerApp/src/viewmodels/SitesViewModel.ts +82 -0
  366. package/examples/react/WorkerApp/tsconfig.json +22 -0
  367. package/examples/react/WorkerApp/vite.config.ts +18 -0
  368. package/package.json +11 -9
  369. package/src/Pending.test.ts +1 -2
  370. package/src/Sorting.test.ts +1 -1
  371. package/src/produceDraft.test.ts +3 -3
  372. package/src/react/components/CardList.test.tsx +1 -1
  373. package/src/react/components/DataTable.test.tsx +1 -1
  374. package/src/react/components/InfiniteScroll.test.tsx +5 -5
  375. package/dist/mvc-kit.cjs.map +0 -1
  376. package/dist/mvc-kit.js.map +0 -1
  377. package/dist/react-native.cjs.map +0 -1
  378. package/dist/react-native.js.map +0 -1
  379. package/dist/react.cjs.map +0 -1
  380. package/dist/react.js.map +0 -1
  381. package/dist/web.cjs.map +0 -1
  382. package/dist/web.js.map +0 -1
@@ -1,179 +1,156 @@
1
- import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
- import { isSelectionHelper, isSortingHelper, isPaginationHelper } from "./types.js";
1
+ import { isPaginationHelper, isSelectionHelper, isSortingHelper } from "./types.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/react/components/DataTable.tsx
3
4
  function resolveSelectionProp(selection) {
4
- if (isSelectionHelper(selection)) {
5
- return {
6
- selected: selection.selected,
7
- onToggle: (key) => selection.toggle(key),
8
- onToggleAll: (allKeys) => selection.toggleAll(allKeys)
9
- };
10
- }
11
- return {
12
- selected: selection.selected,
13
- onToggle: selection.onToggle,
14
- onToggleAll: (allKeys) => selection.onToggleAll(allKeys)
15
- };
5
+ if (isSelectionHelper(selection)) return {
6
+ selected: selection.selected,
7
+ onToggle: (key) => selection.toggle(key),
8
+ onToggleAll: (allKeys) => selection.toggleAll(allKeys)
9
+ };
10
+ return {
11
+ selected: selection.selected,
12
+ onToggle: selection.onToggle,
13
+ onToggleAll: (allKeys) => selection.onToggleAll(allKeys)
14
+ };
16
15
  }
17
16
  function resolveSortProp(sort, onSort) {
18
- if (isSortingHelper(sort)) {
19
- return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };
20
- }
21
- return { sorts: sort, onSort };
17
+ if (isSortingHelper(sort)) return {
18
+ sorts: sort.sorts,
19
+ onSort: (key) => sort.toggle(key)
20
+ };
21
+ return {
22
+ sorts: sort,
23
+ onSort
24
+ };
22
25
  }
23
26
  function resolvePaginationProp(pagination, pageSize, paginationTotal) {
24
- if (isPaginationHelper(pagination)) {
25
- const total2 = paginationTotal ?? 0;
26
- const ps2 = pagination.pageSize;
27
- const pageCount2 = Math.max(1, Math.ceil(total2 / ps2));
28
- const page2 = pagination.page;
29
- return {
30
- page: page2,
31
- pageCount: pageCount2,
32
- total: total2,
33
- pageSize: ps2,
34
- hasPrev: page2 > 1,
35
- hasNext: page2 < pageCount2,
36
- goToPage: (p) => pagination.setPage(p),
37
- goPrev: () => pagination.setPage(page2 - 1),
38
- goNext: () => pagination.setPage(page2 + 1)
39
- };
40
- }
41
- const total = pagination.total;
42
- const ps = pageSize ?? 10;
43
- const pageCount = Math.max(1, Math.ceil(total / ps));
44
- const page = pagination.page;
45
- return {
46
- page,
47
- pageCount,
48
- total,
49
- pageSize: ps,
50
- hasPrev: page > 1,
51
- hasNext: page < pageCount,
52
- goToPage: pagination.onPageChange,
53
- goPrev: () => pagination.onPageChange(page - 1),
54
- goNext: () => pagination.onPageChange(page + 1)
55
- };
27
+ if (isPaginationHelper(pagination)) {
28
+ const total = paginationTotal ?? 0;
29
+ const ps = pagination.pageSize;
30
+ const pageCount = Math.max(1, Math.ceil(total / ps));
31
+ const page = pagination.page;
32
+ return {
33
+ page,
34
+ pageCount,
35
+ total,
36
+ pageSize: ps,
37
+ hasPrev: page > 1,
38
+ hasNext: page < pageCount,
39
+ goToPage: (p) => pagination.setPage(p),
40
+ goPrev: () => pagination.setPage(page - 1),
41
+ goNext: () => pagination.setPage(page + 1)
42
+ };
43
+ }
44
+ const total = pagination.total;
45
+ const ps = pageSize ?? 10;
46
+ const pageCount = Math.max(1, Math.ceil(total / ps));
47
+ const page = pagination.page;
48
+ return {
49
+ page,
50
+ pageCount,
51
+ total,
52
+ pageSize: ps,
53
+ hasPrev: page > 1,
54
+ hasNext: page < pageCount,
55
+ goToPage: pagination.onPageChange,
56
+ goPrev: () => pagination.onPageChange(page - 1),
57
+ goNext: () => pagination.onPageChange(page + 1)
58
+ };
56
59
  }
57
- const defaultKeyOf = (item) => item.id;
60
+ var defaultKeyOf = (item) => item.id;
58
61
  function getAriaSortValue(key, sorts) {
59
- if (!sorts) return "none";
60
- const desc = sorts.find((s) => s.key === key);
61
- if (!desc) return "none";
62
- return desc.direction === "asc" ? "ascending" : "descending";
62
+ if (!sorts) return "none";
63
+ const desc = sorts.find((s) => s.key === key);
64
+ if (!desc) return "none";
65
+ return desc.direction === "asc" ? "ascending" : "descending";
63
66
  }
64
- function DataTable({
65
- items,
66
- columns,
67
- keyOf = defaultKeyOf,
68
- pageSize,
69
- sort,
70
- onSort,
71
- selection,
72
- loading,
73
- error,
74
- pagination,
75
- paginationTotal,
76
- renderEmpty,
77
- renderLoading,
78
- renderError,
79
- renderSortIndicator,
80
- renderRow,
81
- renderPagination,
82
- className,
83
- "aria-label": ariaLabel
84
- }) {
85
- if (loading && renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
86
- if (error && renderError) return /* @__PURE__ */ jsx(Fragment, { children: renderError(error) });
87
- if (items.length === 0 && renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
88
- const resolvedSelection = selection ? resolveSelectionProp(selection) : void 0;
89
- let resolvedSorts;
90
- let resolvedOnSort;
91
- if (sort) {
92
- const resolved = resolveSortProp(sort, onSort);
93
- resolvedSorts = resolved.sorts;
94
- resolvedOnSort = resolved.onSort;
95
- }
96
- let displayItems = items;
97
- let paginationInfo;
98
- if (pagination) {
99
- paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);
100
- } else if (pageSize && !pagination) {
101
- displayItems = items.slice(0, pageSize);
102
- }
103
- const allKeys = resolvedSelection ? displayItems.map((item) => keyOf(item)) : [];
104
- const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every((k) => resolvedSelection.selected.has(k));
105
- const someSelected = resolvedSelection && allKeys.some((k) => resolvedSelection.selected.has(k));
106
- return /* @__PURE__ */ jsxs("div", { "data-component": "data-table", className, children: [
107
- /* @__PURE__ */ jsxs("table", { role: "grid", "aria-label": ariaLabel, children: [
108
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
109
- resolvedSelection && /* @__PURE__ */ jsx("th", { "data-column": "select", children: /* @__PURE__ */ jsx(
110
- "input",
111
- {
112
- type: "checkbox",
113
- checked: !!allSelected,
114
- ref: (el) => {
115
- if (el) el.indeterminate = !!someSelected && !allSelected;
116
- },
117
- onChange: () => resolvedSelection.onToggleAll(allKeys),
118
- "aria-label": "Select all"
119
- }
120
- ) }),
121
- columns.map((col) => {
122
- const isSortable = col.sortable && resolvedOnSort;
123
- const sortDesc = resolvedSorts?.find((s) => s.key === col.key);
124
- const isActive = !!sortDesc;
125
- const sortIndex = resolvedSorts ? resolvedSorts.findIndex((s) => s.key === col.key) : -1;
126
- return /* @__PURE__ */ jsx(
127
- "th",
128
- {
129
- "data-sortable": isSortable ? "" : void 0,
130
- "data-sorted": isActive ? "" : void 0,
131
- "data-align": col.align,
132
- style: col.width ? { width: col.width } : void 0,
133
- "aria-sort": isSortable ? getAriaSortValue(col.key, resolvedSorts) : void 0,
134
- children: isSortable ? /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => resolvedOnSort(col.key), children: [
135
- col.header,
136
- renderSortIndicator?.({
137
- active: isActive,
138
- direction: sortDesc?.direction ?? "asc",
139
- index: sortIndex,
140
- onToggle: () => resolvedOnSort(col.key)
141
- })
142
- ] }) : col.header
143
- },
144
- col.key
145
- );
146
- })
147
- ] }) }),
148
- /* @__PURE__ */ jsx("tbody", { children: displayItems.map((item, index) => {
149
- const key = keyOf(item);
150
- const isSelected = resolvedSelection?.selected.has(key);
151
- const cells = /* @__PURE__ */ jsxs(Fragment, { children: [
152
- resolvedSelection && /* @__PURE__ */ jsx("td", { "data-column": "select", children: /* @__PURE__ */ jsx(
153
- "input",
154
- {
155
- type: "checkbox",
156
- checked: !!isSelected,
157
- onChange: () => resolvedSelection.onToggle(key),
158
- "aria-label": `Select row ${key}`
159
- }
160
- ) }),
161
- columns.map((col) => /* @__PURE__ */ jsx(
162
- "td",
163
- {
164
- "data-align": col.align,
165
- children: col.render(item, index)
166
- },
167
- col.key
168
- ))
169
- ] });
170
- return /* @__PURE__ */ jsx("tr", { "data-selected": isSelected ? "" : void 0, children: renderRow ? renderRow(item, index, cells) : cells }, key);
171
- }) })
172
- ] }),
173
- paginationInfo && renderPagination?.(paginationInfo)
174
- ] });
67
+ /**
68
+ * Headless data table with sort headers, selection checkboxes, and pagination slots.
69
+ * Renders semantic HTML (`<table>`) with data attributes for styling.
70
+ * Accepts Sorting/Selection/Pagination helpers directly via duck-typing.
71
+ */
72
+ function DataTable({ items, columns, keyOf = defaultKeyOf, pageSize, sort, onSort, selection, loading, error, pagination, paginationTotal, renderEmpty, renderLoading, renderError, renderSortIndicator, renderRow, renderPagination, className, "aria-label": ariaLabel }) {
73
+ if (loading && renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
74
+ if (error && renderError) return /* @__PURE__ */ jsx(Fragment, { children: renderError(error) });
75
+ if (items.length === 0 && renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
76
+ const resolvedSelection = selection ? resolveSelectionProp(selection) : void 0;
77
+ let resolvedSorts;
78
+ let resolvedOnSort;
79
+ if (sort) {
80
+ const resolved = resolveSortProp(sort, onSort);
81
+ resolvedSorts = resolved.sorts;
82
+ resolvedOnSort = resolved.onSort;
83
+ }
84
+ let displayItems = items;
85
+ let paginationInfo;
86
+ if (pagination) paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);
87
+ else if (pageSize && !pagination) displayItems = items.slice(0, pageSize);
88
+ const allKeys = resolvedSelection ? displayItems.map((item) => keyOf(item)) : [];
89
+ const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every((k) => resolvedSelection.selected.has(k));
90
+ const someSelected = resolvedSelection && allKeys.some((k) => resolvedSelection.selected.has(k));
91
+ return /* @__PURE__ */ jsxs("div", {
92
+ "data-component": "data-table",
93
+ className,
94
+ children: [/* @__PURE__ */ jsxs("table", {
95
+ role: "grid",
96
+ "aria-label": ariaLabel,
97
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [resolvedSelection && /* @__PURE__ */ jsx("th", {
98
+ "data-column": "select",
99
+ children: /* @__PURE__ */ jsx("input", {
100
+ type: "checkbox",
101
+ checked: !!allSelected,
102
+ ref: (el) => {
103
+ if (el) el.indeterminate = !!someSelected && !allSelected;
104
+ },
105
+ onChange: () => resolvedSelection.onToggleAll(allKeys),
106
+ "aria-label": "Select all"
107
+ })
108
+ }), columns.map((col) => {
109
+ const isSortable = col.sortable && resolvedOnSort;
110
+ const sortDesc = resolvedSorts?.find((s) => s.key === col.key);
111
+ const isActive = !!sortDesc;
112
+ const sortIndex = resolvedSorts ? resolvedSorts.findIndex((s) => s.key === col.key) : -1;
113
+ return /* @__PURE__ */ jsx("th", {
114
+ "data-sortable": isSortable ? "" : void 0,
115
+ "data-sorted": isActive ? "" : void 0,
116
+ "data-align": col.align,
117
+ style: col.width ? { width: col.width } : void 0,
118
+ "aria-sort": isSortable ? getAriaSortValue(col.key, resolvedSorts) : void 0,
119
+ children: isSortable ? /* @__PURE__ */ jsxs("button", {
120
+ type: "button",
121
+ onClick: () => resolvedOnSort(col.key),
122
+ children: [col.header, renderSortIndicator?.({
123
+ active: isActive,
124
+ direction: sortDesc?.direction ?? "asc",
125
+ index: sortIndex,
126
+ onToggle: () => resolvedOnSort(col.key)
127
+ })]
128
+ }) : col.header
129
+ }, col.key);
130
+ })] }) }), /* @__PURE__ */ jsx("tbody", { children: displayItems.map((item, index) => {
131
+ const key = keyOf(item);
132
+ const isSelected = resolvedSelection?.selected.has(key);
133
+ const cells = /* @__PURE__ */ jsxs(Fragment, { children: [resolvedSelection && /* @__PURE__ */ jsx("td", {
134
+ "data-column": "select",
135
+ children: /* @__PURE__ */ jsx("input", {
136
+ type: "checkbox",
137
+ checked: !!isSelected,
138
+ onChange: () => resolvedSelection.onToggle(key),
139
+ "aria-label": `Select row ${key}`
140
+ })
141
+ }), columns.map((col) => /* @__PURE__ */ jsx("td", {
142
+ "data-align": col.align,
143
+ children: col.render(item, index)
144
+ }, col.key))] });
145
+ return /* @__PURE__ */ jsx("tr", {
146
+ "data-selected": isSelected ? "" : void 0,
147
+ children: renderRow ? renderRow(item, index, cells) : cells
148
+ }, key);
149
+ }) })]
150
+ }), paginationInfo && renderPagination?.(paginationInfo)]
151
+ });
175
152
  }
176
- export {
177
- DataTable
178
- };
179
- //# sourceMappingURL=DataTable.js.map
153
+ //#endregion
154
+ export { DataTable };
155
+
156
+ //# sourceMappingURL=DataTable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","sources":["../../../src/react/components/DataTable.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n Column,\n SortHeaderProps,\n SelectionState,\n SelectionHelper,\n PaginationState,\n PaginationHelper,\n PaginationInfo,\n SortingHelper,\n AsyncStateProps,\n} from './types';\nimport { isSelectionHelper, isPaginationHelper, isSortingHelper } from './types';\nimport type { SortDescriptor } from '../../Sorting';\n\n// ── Prop resolution helpers ──\n\ninterface ResolvedSelection {\n selected: ReadonlySet<any>;\n onToggle: (key: any) => void;\n onToggleAll: (allKeys: any[]) => void;\n}\n\nfunction resolveSelectionProp(selection: SelectionState | SelectionHelper): ResolvedSelection {\n if (isSelectionHelper(selection)) {\n return {\n selected: selection.selected,\n onToggle: (key) => selection.toggle(key),\n onToggleAll: (allKeys) => selection.toggleAll(allKeys),\n };\n }\n return {\n selected: selection.selected,\n onToggle: selection.onToggle,\n onToggleAll: (allKeys) => selection.onToggleAll(allKeys),\n };\n}\n\nfunction resolveSortProp(\n sort: readonly SortDescriptor[] | SortingHelper,\n onSort?: (key: string) => void,\n): { sorts: readonly SortDescriptor[]; onSort: ((key: string) => void) | undefined } {\n if (isSortingHelper(sort)) {\n return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };\n }\n return { sorts: sort, onSort };\n}\n\nfunction resolvePaginationProp(\n pagination: PaginationState | PaginationHelper,\n pageSize?: number,\n paginationTotal?: number,\n): PaginationInfo {\n if (isPaginationHelper(pagination)) {\n const total = paginationTotal ?? 0;\n const ps = pagination.pageSize;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: (p) => pagination.setPage(p),\n goPrev: () => pagination.setPage(page - 1),\n goNext: () => pagination.setPage(page + 1),\n };\n }\n const total = pagination.total;\n const ps = pageSize ?? 10;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: pagination.onPageChange,\n goPrev: () => pagination.onPageChange(page - 1),\n goNext: () => pagination.onPageChange(page + 1),\n };\n}\n\n/** Props for the DataTable headless component. */\nexport interface DataTableProps<T> extends AsyncStateProps {\n items: T[];\n columns: Column<T>[];\n keyOf?: (item: T) => string | number;\n pageSize?: number;\n\n // Controlled state — accepts object-literal OR helper instance\n sort?: readonly SortDescriptor[] | SortingHelper;\n onSort?: (key: string) => void;\n selection?: SelectionState | SelectionHelper;\n pagination?: PaginationState | PaginationHelper;\n paginationTotal?: number;\n\n // Render slots\n renderEmpty?: () => ReactNode;\n renderLoading?: () => ReactNode;\n renderError?: (error: string) => ReactNode;\n renderSortIndicator?: (props: SortHeaderProps) => ReactNode;\n renderRow?: (item: T, index: number, defaultCells: ReactNode) => ReactNode;\n renderPagination?: (info: PaginationInfo) => ReactNode;\n\n className?: string;\n 'aria-label'?: string;\n}\n\nconst defaultKeyOf = (item: any) => item.id;\n\nfunction getAriaSortValue(key: string, sorts: readonly SortDescriptor[] | undefined): 'ascending' | 'descending' | 'none' {\n if (!sorts) return 'none';\n const desc = sorts.find(s => s.key === key);\n if (!desc) return 'none';\n return desc.direction === 'asc' ? 'ascending' : 'descending';\n}\n\n/**\n * Headless data table with sort headers, selection checkboxes, and pagination slots.\n * Renders semantic HTML (`<table>`) with data attributes for styling.\n * Accepts Sorting/Selection/Pagination helpers directly via duck-typing.\n */\nexport function DataTable<T>({\n items,\n columns,\n keyOf = defaultKeyOf,\n pageSize,\n sort,\n onSort,\n selection,\n loading,\n error,\n pagination,\n paginationTotal,\n renderEmpty,\n renderLoading,\n renderError,\n renderSortIndicator,\n renderRow,\n renderPagination,\n className,\n 'aria-label': ariaLabel,\n}: DataTableProps<T>) {\n if (loading && renderLoading) return <>{renderLoading()}</>;\n if (error && renderError) return <>{renderError(error)}</>;\n if (items.length === 0 && renderEmpty) return <>{renderEmpty()}</>;\n\n // ── Resolve props ──\n const resolvedSelection = selection ? resolveSelectionProp(selection) : undefined;\n\n let resolvedSorts: readonly SortDescriptor[] | undefined;\n let resolvedOnSort: ((key: string) => void) | undefined;\n if (sort) {\n const resolved = resolveSortProp(sort, onSort);\n resolvedSorts = resolved.sorts;\n resolvedOnSort = resolved.onSort;\n }\n\n let displayItems = items;\n let paginationInfo: PaginationInfo | undefined;\n if (pagination) {\n paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);\n } else if (pageSize && !pagination) {\n displayItems = items.slice(0, pageSize);\n }\n\n // Selection: check indeterminate state\n const allKeys = resolvedSelection ? displayItems.map(item => keyOf(item)) : [];\n const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every(k => resolvedSelection!.selected.has(k));\n const someSelected = resolvedSelection && allKeys.some(k => resolvedSelection!.selected.has(k));\n\n return (\n <div data-component=\"data-table\" className={className}>\n <table role=\"grid\" aria-label={ariaLabel}>\n <thead>\n <tr>\n {resolvedSelection && (\n <th data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!allSelected}\n ref={(el) => {\n if (el) el.indeterminate = !!someSelected && !allSelected;\n }}\n onChange={() => resolvedSelection!.onToggleAll(allKeys)}\n aria-label=\"Select all\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSortable = col.sortable && resolvedOnSort;\n const sortDesc = resolvedSorts?.find(s => s.key === col.key);\n const isActive = !!sortDesc;\n const sortIndex = resolvedSorts ? resolvedSorts.findIndex(s => s.key === col.key) : -1;\n\n return (\n <th\n key={col.key}\n data-sortable={isSortable ? '' : undefined}\n data-sorted={isActive ? '' : undefined}\n data-align={col.align}\n style={col.width ? { width: col.width } : undefined}\n aria-sort={isSortable ? getAriaSortValue(col.key, resolvedSorts) : undefined}\n >\n {isSortable ? (\n <button type=\"button\" onClick={() => resolvedOnSort!(col.key)}>\n {col.header}\n {renderSortIndicator?.({\n active: isActive,\n direction: sortDesc?.direction ?? 'asc',\n index: sortIndex,\n onToggle: () => resolvedOnSort!(col.key),\n })}\n </button>\n ) : (\n col.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {displayItems.map((item, index) => {\n const key = keyOf(item);\n const isSelected = resolvedSelection?.selected.has(key);\n\n const cells = (\n <>\n {resolvedSelection && (\n <td data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!isSelected}\n onChange={() => resolvedSelection!.onToggle(key)}\n aria-label={`Select row ${key}`}\n />\n </td>\n )}\n {columns.map((col) => (\n <td\n key={col.key}\n data-align={col.align}\n >\n {col.render(item, index)}\n </td>\n ))}\n </>\n );\n\n return (\n <tr key={key} data-selected={isSelected ? '' : undefined}>\n {renderRow ? renderRow(item, index, cells) : cells}\n </tr>\n );\n })}\n </tbody>\n </table>\n {paginationInfo && renderPagination?.(paginationInfo)}\n </div>\n );\n}\n"],"names":["total","ps","pageCount","page"],"mappings":"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"}
1
+ {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/react/components/DataTable.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n Column,\n SortHeaderProps,\n SelectionState,\n SelectionHelper,\n PaginationState,\n PaginationHelper,\n PaginationInfo,\n SortingHelper,\n AsyncStateProps,\n} from './types';\nimport { isSelectionHelper, isPaginationHelper, isSortingHelper } from './types';\nimport type { SortDescriptor } from '../../Sorting';\n\n// ── Prop resolution helpers ──\n\ninterface ResolvedSelection {\n selected: ReadonlySet<any>;\n onToggle: (key: any) => void;\n onToggleAll: (allKeys: any[]) => void;\n}\n\nfunction resolveSelectionProp(selection: SelectionState | SelectionHelper): ResolvedSelection {\n if (isSelectionHelper(selection)) {\n return {\n selected: selection.selected,\n onToggle: (key) => selection.toggle(key),\n onToggleAll: (allKeys) => selection.toggleAll(allKeys),\n };\n }\n return {\n selected: selection.selected,\n onToggle: selection.onToggle,\n onToggleAll: (allKeys) => selection.onToggleAll(allKeys),\n };\n}\n\nfunction resolveSortProp(\n sort: readonly SortDescriptor[] | SortingHelper,\n onSort?: (key: string) => void,\n): { sorts: readonly SortDescriptor[]; onSort: ((key: string) => void) | undefined } {\n if (isSortingHelper(sort)) {\n return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };\n }\n return { sorts: sort, onSort };\n}\n\nfunction resolvePaginationProp(\n pagination: PaginationState | PaginationHelper,\n pageSize?: number,\n paginationTotal?: number,\n): PaginationInfo {\n if (isPaginationHelper(pagination)) {\n const total = paginationTotal ?? 0;\n const ps = pagination.pageSize;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: (p) => pagination.setPage(p),\n goPrev: () => pagination.setPage(page - 1),\n goNext: () => pagination.setPage(page + 1),\n };\n }\n const total = pagination.total;\n const ps = pageSize ?? 10;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: pagination.onPageChange,\n goPrev: () => pagination.onPageChange(page - 1),\n goNext: () => pagination.onPageChange(page + 1),\n };\n}\n\n/** Props for the DataTable headless component. */\nexport interface DataTableProps<T> extends AsyncStateProps {\n items: T[];\n columns: Column<T>[];\n keyOf?: (item: T) => string | number;\n pageSize?: number;\n\n // Controlled state — accepts object-literal OR helper instance\n sort?: readonly SortDescriptor[] | SortingHelper;\n onSort?: (key: string) => void;\n selection?: SelectionState | SelectionHelper;\n pagination?: PaginationState | PaginationHelper;\n paginationTotal?: number;\n\n // Render slots\n renderEmpty?: () => ReactNode;\n renderLoading?: () => ReactNode;\n renderError?: (error: string) => ReactNode;\n renderSortIndicator?: (props: SortHeaderProps) => ReactNode;\n renderRow?: (item: T, index: number, defaultCells: ReactNode) => ReactNode;\n renderPagination?: (info: PaginationInfo) => ReactNode;\n\n className?: string;\n 'aria-label'?: string;\n}\n\nconst defaultKeyOf = (item: any) => item.id;\n\nfunction getAriaSortValue(key: string, sorts: readonly SortDescriptor[] | undefined): 'ascending' | 'descending' | 'none' {\n if (!sorts) return 'none';\n const desc = sorts.find(s => s.key === key);\n if (!desc) return 'none';\n return desc.direction === 'asc' ? 'ascending' : 'descending';\n}\n\n/**\n * Headless data table with sort headers, selection checkboxes, and pagination slots.\n * Renders semantic HTML (`<table>`) with data attributes for styling.\n * Accepts Sorting/Selection/Pagination helpers directly via duck-typing.\n */\nexport function DataTable<T>({\n items,\n columns,\n keyOf = defaultKeyOf,\n pageSize,\n sort,\n onSort,\n selection,\n loading,\n error,\n pagination,\n paginationTotal,\n renderEmpty,\n renderLoading,\n renderError,\n renderSortIndicator,\n renderRow,\n renderPagination,\n className,\n 'aria-label': ariaLabel,\n}: DataTableProps<T>) {\n if (loading && renderLoading) return <>{renderLoading()}</>;\n if (error && renderError) return <>{renderError(error)}</>;\n if (items.length === 0 && renderEmpty) return <>{renderEmpty()}</>;\n\n // ── Resolve props ──\n const resolvedSelection = selection ? resolveSelectionProp(selection) : undefined;\n\n let resolvedSorts: readonly SortDescriptor[] | undefined;\n let resolvedOnSort: ((key: string) => void) | undefined;\n if (sort) {\n const resolved = resolveSortProp(sort, onSort);\n resolvedSorts = resolved.sorts;\n resolvedOnSort = resolved.onSort;\n }\n\n let displayItems = items;\n let paginationInfo: PaginationInfo | undefined;\n if (pagination) {\n paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);\n } else if (pageSize && !pagination) {\n displayItems = items.slice(0, pageSize);\n }\n\n // Selection: check indeterminate state\n const allKeys = resolvedSelection ? displayItems.map(item => keyOf(item)) : [];\n const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every(k => resolvedSelection!.selected.has(k));\n const someSelected = resolvedSelection && allKeys.some(k => resolvedSelection!.selected.has(k));\n\n return (\n <div data-component=\"data-table\" className={className}>\n <table role=\"grid\" aria-label={ariaLabel}>\n <thead>\n <tr>\n {resolvedSelection && (\n <th data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!allSelected}\n ref={(el) => {\n if (el) el.indeterminate = !!someSelected && !allSelected;\n }}\n onChange={() => resolvedSelection!.onToggleAll(allKeys)}\n aria-label=\"Select all\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSortable = col.sortable && resolvedOnSort;\n const sortDesc = resolvedSorts?.find(s => s.key === col.key);\n const isActive = !!sortDesc;\n const sortIndex = resolvedSorts ? resolvedSorts.findIndex(s => s.key === col.key) : -1;\n\n return (\n <th\n key={col.key}\n data-sortable={isSortable ? '' : undefined}\n data-sorted={isActive ? '' : undefined}\n data-align={col.align}\n style={col.width ? { width: col.width } : undefined}\n aria-sort={isSortable ? getAriaSortValue(col.key, resolvedSorts) : undefined}\n >\n {isSortable ? (\n <button type=\"button\" onClick={() => resolvedOnSort!(col.key)}>\n {col.header}\n {renderSortIndicator?.({\n active: isActive,\n direction: sortDesc?.direction ?? 'asc',\n index: sortIndex,\n onToggle: () => resolvedOnSort!(col.key),\n })}\n </button>\n ) : (\n col.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {displayItems.map((item, index) => {\n const key = keyOf(item);\n const isSelected = resolvedSelection?.selected.has(key);\n\n const cells = (\n <>\n {resolvedSelection && (\n <td data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!isSelected}\n onChange={() => resolvedSelection!.onToggle(key)}\n aria-label={`Select row ${key}`}\n />\n </td>\n )}\n {columns.map((col) => (\n <td\n key={col.key}\n data-align={col.align}\n >\n {col.render(item, index)}\n </td>\n ))}\n </>\n );\n\n return (\n <tr key={key} data-selected={isSelected ? '' : undefined}>\n {renderRow ? renderRow(item, index, cells) : cells}\n </tr>\n );\n })}\n </tbody>\n </table>\n {paginationInfo && renderPagination?.(paginationInfo)}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,44 +1,53 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const react = require("react");
5
- function InfiniteScroll({
6
- hasMore,
7
- loading = false,
8
- onLoadMore,
9
- threshold = 0.1,
10
- rootMargin = "0px",
11
- direction = "down",
12
- children,
13
- renderLoading,
14
- renderEnd,
15
- className
16
- }) {
17
- const sentinelRef = react.useRef(null);
18
- const onLoadMoreRef = react.useRef(onLoadMore);
19
- onLoadMoreRef.current = onLoadMore;
20
- react.useEffect(() => {
21
- if (typeof IntersectionObserver === "undefined") return;
22
- const sentinel = sentinelRef.current;
23
- if (!sentinel) return;
24
- const observer = new IntersectionObserver(
25
- (entries) => {
26
- if (entries[0]?.isIntersecting) {
27
- onLoadMoreRef.current();
28
- }
29
- },
30
- { threshold, rootMargin }
31
- );
32
- observer.observe(sentinel);
33
- return () => observer.disconnect();
34
- }, [threshold, rootMargin, hasMore, loading]);
35
- const style = direction === "up" ? { display: "flex", flexDirection: "column-reverse" } : void 0;
36
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-component": "infinite-scroll", className, style, children: [
37
- children,
38
- hasMore && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: sentinelRef, "aria-hidden": "true", "data-sentinel": true }),
39
- loading && renderLoading?.(),
40
- !hasMore && renderEnd?.()
41
- ] });
1
+ let react = require("react");
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ //#region src/react/components/InfiniteScroll.tsx
4
+ /**
5
+ * Headless infinite scroll wrapper using IntersectionObserver.
6
+ * Renders a sentinel element that triggers `onLoadMore` when visible.
7
+ * Use `direction="up"` for reverse-scroll chat UIs.
8
+ */
9
+ function InfiniteScroll({ hasMore, loading = false, onLoadMore, threshold = .1, rootMargin = "0px", direction = "down", children, renderLoading, renderEnd, className }) {
10
+ const sentinelRef = (0, react.useRef)(null);
11
+ const onLoadMoreRef = (0, react.useRef)(onLoadMore);
12
+ onLoadMoreRef.current = onLoadMore;
13
+ (0, react.useEffect)(() => {
14
+ if (typeof IntersectionObserver === "undefined") return;
15
+ const sentinel = sentinelRef.current;
16
+ if (!sentinel) return;
17
+ const observer = new IntersectionObserver((entries) => {
18
+ if (entries[0]?.isIntersecting) onLoadMoreRef.current();
19
+ }, {
20
+ threshold,
21
+ rootMargin
22
+ });
23
+ observer.observe(sentinel);
24
+ return () => observer.disconnect();
25
+ }, [
26
+ threshold,
27
+ rootMargin,
28
+ hasMore,
29
+ loading
30
+ ]);
31
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32
+ "data-component": "infinite-scroll",
33
+ className,
34
+ style: direction === "up" ? {
35
+ display: "flex",
36
+ flexDirection: "column-reverse"
37
+ } : void 0,
38
+ children: [
39
+ children,
40
+ hasMore && !loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
41
+ ref: sentinelRef,
42
+ "aria-hidden": "true",
43
+ "data-sentinel": true
44
+ }),
45
+ loading && renderLoading?.(),
46
+ !hasMore && renderEnd?.()
47
+ ]
48
+ });
42
49
  }
50
+ //#endregion
43
51
  exports.InfiniteScroll = InfiniteScroll;
44
- //# sourceMappingURL=InfiniteScroll.cjs.map
52
+
53
+ //# sourceMappingURL=InfiniteScroll.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"InfiniteScroll.cjs","sources":["../../../src/react/components/InfiniteScroll.tsx"],"sourcesContent":["import { useRef, useEffect, type ReactNode } from 'react';\n\n/** Props for the InfiniteScroll headless component. */\nexport interface InfiniteScrollProps {\n hasMore: boolean;\n loading?: boolean;\n onLoadMore: () => void;\n threshold?: number;\n rootMargin?: string;\n direction?: 'down' | 'up';\n children: ReactNode;\n renderLoading?: () => ReactNode;\n renderEnd?: () => ReactNode;\n className?: string;\n}\n\n/**\n * Headless infinite scroll wrapper using IntersectionObserver.\n * Renders a sentinel element that triggers `onLoadMore` when visible.\n * Use `direction=\"up\"` for reverse-scroll chat UIs.\n */\nexport function InfiniteScroll({\n hasMore,\n loading = false,\n onLoadMore,\n threshold = 0.1,\n rootMargin = '0px',\n direction = 'down',\n children,\n renderLoading,\n renderEnd,\n className,\n}: InfiniteScrollProps) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n\n useEffect(() => {\n if (typeof IntersectionObserver === 'undefined') return;\n const sentinel = sentinelRef.current;\n if (!sentinel) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n onLoadMoreRef.current();\n }\n },\n { threshold, rootMargin },\n );\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [threshold, rootMargin, hasMore, loading]);\n\n const style = direction === 'up'\n ? { display: 'flex', flexDirection: 'column-reverse' as const }\n : undefined;\n\n return (\n <div data-component=\"infinite-scroll\" className={className} style={style}>\n {children}\n {hasMore && !loading && (\n <div ref={sentinelRef} aria-hidden=\"true\" data-sentinel />\n )}\n {loading && renderLoading?.()}\n {!hasMore && renderEnd?.()}\n </div>\n );\n}\n"],"names":["useRef","useEffect","jsxs","jsx"],"mappings":";;;;AAqBO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,cAAcA,MAAAA,OAAuB,IAAI;AAC/C,QAAM,gBAAgBA,MAAAA,OAAO,UAAU;AACvC,gBAAc,UAAU;AAExBC,QAAAA,UAAU,MAAM;AACd,QAAI,OAAO,yBAAyB,YAAa;AACjD,UAAM,WAAW,YAAY;AAC7B,QAAI,CAAC,SAAU;AAEf,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,YAAY;AACX,YAAI,QAAQ,CAAC,GAAG,gBAAgB;AAC9B,wBAAc,QAAA;AAAA,QAChB;AAAA,MACF;AAAA,MACA,EAAE,WAAW,WAAA;AAAA,IAAW;AAG1B,aAAS,QAAQ,QAAQ;AACzB,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,WAAW,YAAY,SAAS,OAAO,CAAC;AAE5C,QAAM,QAAQ,cAAc,OACxB,EAAE,SAAS,QAAQ,eAAe,qBAClC;AAEJ,SACEC,2BAAAA,KAAC,OAAA,EAAI,kBAAe,mBAAkB,WAAsB,OACzD,UAAA;AAAA,IAAA;AAAA,IACA,WAAW,CAAC,WACXC,2BAAAA,IAAC,OAAA,EAAI,KAAK,aAAa,eAAY,QAAO,iBAAa,KAAA,CAAC;AAAA,IAEzD,WAAW,gBAAA;AAAA,IACX,CAAC,WAAW,YAAA;AAAA,EAAY,GAC3B;AAEJ;;"}
1
+ {"version":3,"file":"InfiniteScroll.cjs","names":[],"sources":["../../../src/react/components/InfiniteScroll.tsx"],"sourcesContent":["import { useRef, useEffect, type ReactNode } from 'react';\n\n/** Props for the InfiniteScroll headless component. */\nexport interface InfiniteScrollProps {\n hasMore: boolean;\n loading?: boolean;\n onLoadMore: () => void;\n threshold?: number;\n rootMargin?: string;\n direction?: 'down' | 'up';\n children: ReactNode;\n renderLoading?: () => ReactNode;\n renderEnd?: () => ReactNode;\n className?: string;\n}\n\n/**\n * Headless infinite scroll wrapper using IntersectionObserver.\n * Renders a sentinel element that triggers `onLoadMore` when visible.\n * Use `direction=\"up\"` for reverse-scroll chat UIs.\n */\nexport function InfiniteScroll({\n hasMore,\n loading = false,\n onLoadMore,\n threshold = 0.1,\n rootMargin = '0px',\n direction = 'down',\n children,\n renderLoading,\n renderEnd,\n className,\n}: InfiniteScrollProps) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n\n useEffect(() => {\n if (typeof IntersectionObserver === 'undefined') return;\n const sentinel = sentinelRef.current;\n if (!sentinel) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n onLoadMoreRef.current();\n }\n },\n { threshold, rootMargin },\n );\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [threshold, rootMargin, hasMore, loading]);\n\n const style = direction === 'up'\n ? { display: 'flex', flexDirection: 'column-reverse' as const }\n : undefined;\n\n return (\n <div data-component=\"infinite-scroll\" className={className} style={style}>\n {children}\n {hasMore && !loading && (\n <div ref={sentinelRef} aria-hidden=\"true\" data-sentinel />\n )}\n {loading && renderLoading?.()}\n {!hasMore && renderEnd?.()}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAqBA,SAAgB,eAAe,EAC7B,SACA,UAAU,OACV,YACA,YAAY,IACZ,aAAa,OACb,YAAY,QACZ,UACA,eACA,WACA,aACsB;CACtB,MAAM,eAAA,GAAA,MAAA,QAAqC,KAAK;CAChD,MAAM,iBAAA,GAAA,MAAA,QAAuB,WAAW;AACxC,eAAc,UAAU;AAExB,EAAA,GAAA,MAAA,iBAAgB;AACd,MAAI,OAAO,yBAAyB,YAAa;EACjD,MAAM,WAAW,YAAY;AAC7B,MAAI,CAAC,SAAU;EAEf,MAAM,WAAW,IAAI,sBAClB,YAAY;AACX,OAAI,QAAQ,IAAI,eACd,eAAc,SAAS;KAG3B;GAAE;GAAW;GAAY,CAC1B;AAED,WAAS,QAAQ,SAAS;AAC1B,eAAa,SAAS,YAAY;IACjC;EAAC;EAAW;EAAY;EAAS;EAAQ,CAAC;AAM7C,QACE,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,kBAAe;EAA6B;EAAkB,OALvD,cAAc,OACxB;GAAE,SAAS;GAAQ,eAAe;GAA2B,GAC7D,KAAA;YAGF;GACG;GACA,WAAW,CAAC,WACX,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,KAAK;IAAa,eAAY;IAAO,iBAAA;IAAgB,CAAA;GAE3D,WAAW,iBAAiB;GAC5B,CAAC,WAAW,aAAa;GACtB"}
@@ -1,44 +1,53 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
3
- function InfiniteScroll({
4
- hasMore,
5
- loading = false,
6
- onLoadMore,
7
- threshold = 0.1,
8
- rootMargin = "0px",
9
- direction = "down",
10
- children,
11
- renderLoading,
12
- renderEnd,
13
- className
14
- }) {
15
- const sentinelRef = useRef(null);
16
- const onLoadMoreRef = useRef(onLoadMore);
17
- onLoadMoreRef.current = onLoadMore;
18
- useEffect(() => {
19
- if (typeof IntersectionObserver === "undefined") return;
20
- const sentinel = sentinelRef.current;
21
- if (!sentinel) return;
22
- const observer = new IntersectionObserver(
23
- (entries) => {
24
- if (entries[0]?.isIntersecting) {
25
- onLoadMoreRef.current();
26
- }
27
- },
28
- { threshold, rootMargin }
29
- );
30
- observer.observe(sentinel);
31
- return () => observer.disconnect();
32
- }, [threshold, rootMargin, hasMore, loading]);
33
- const style = direction === "up" ? { display: "flex", flexDirection: "column-reverse" } : void 0;
34
- return /* @__PURE__ */ jsxs("div", { "data-component": "infinite-scroll", className, style, children: [
35
- children,
36
- hasMore && !loading && /* @__PURE__ */ jsx("div", { ref: sentinelRef, "aria-hidden": "true", "data-sentinel": true }),
37
- loading && renderLoading?.(),
38
- !hasMore && renderEnd?.()
39
- ] });
1
+ import { useEffect, useRef } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/react/components/InfiniteScroll.tsx
4
+ /**
5
+ * Headless infinite scroll wrapper using IntersectionObserver.
6
+ * Renders a sentinel element that triggers `onLoadMore` when visible.
7
+ * Use `direction="up"` for reverse-scroll chat UIs.
8
+ */
9
+ function InfiniteScroll({ hasMore, loading = false, onLoadMore, threshold = .1, rootMargin = "0px", direction = "down", children, renderLoading, renderEnd, className }) {
10
+ const sentinelRef = useRef(null);
11
+ const onLoadMoreRef = useRef(onLoadMore);
12
+ onLoadMoreRef.current = onLoadMore;
13
+ useEffect(() => {
14
+ if (typeof IntersectionObserver === "undefined") return;
15
+ const sentinel = sentinelRef.current;
16
+ if (!sentinel) return;
17
+ const observer = new IntersectionObserver((entries) => {
18
+ if (entries[0]?.isIntersecting) onLoadMoreRef.current();
19
+ }, {
20
+ threshold,
21
+ rootMargin
22
+ });
23
+ observer.observe(sentinel);
24
+ return () => observer.disconnect();
25
+ }, [
26
+ threshold,
27
+ rootMargin,
28
+ hasMore,
29
+ loading
30
+ ]);
31
+ return /* @__PURE__ */ jsxs("div", {
32
+ "data-component": "infinite-scroll",
33
+ className,
34
+ style: direction === "up" ? {
35
+ display: "flex",
36
+ flexDirection: "column-reverse"
37
+ } : void 0,
38
+ children: [
39
+ children,
40
+ hasMore && !loading && /* @__PURE__ */ jsx("div", {
41
+ ref: sentinelRef,
42
+ "aria-hidden": "true",
43
+ "data-sentinel": true
44
+ }),
45
+ loading && renderLoading?.(),
46
+ !hasMore && renderEnd?.()
47
+ ]
48
+ });
40
49
  }
41
- export {
42
- InfiniteScroll
43
- };
44
- //# sourceMappingURL=InfiniteScroll.js.map
50
+ //#endregion
51
+ export { InfiniteScroll };
52
+
53
+ //# sourceMappingURL=InfiniteScroll.js.map