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
package/dist/Sorting.cjs CHANGED
@@ -1,104 +1,105 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const Trackable = require("./Trackable.cjs");
4
- class Sorting extends Trackable.Trackable {
5
- _sorts;
6
- constructor(options) {
7
- super();
8
- this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
9
- }
10
- // ── Readable state ──
11
- /** Current list of active sort descriptors, in priority order. */
12
- get sorts() {
13
- return this._sorts;
14
- }
15
- /** Primary sort key (first descriptor), or null when empty. */
16
- get key() {
17
- return this._sorts.length > 0 ? this._sorts[0].key : null;
18
- }
19
- /** Primary sort direction. Defaults to 'asc' when empty. */
20
- get direction() {
21
- return this._sorts.length > 0 ? this._sorts[0].direction : "asc";
22
- }
23
- // ── Query ──
24
- /** Whether the given key is currently sorted. */
25
- isSorted(key) {
26
- return this._sorts.some((s) => s.key === key);
27
- }
28
- /** Returns the sort direction for a key, or null if not sorted. */
29
- directionOf(key) {
30
- const found = this._sorts.find((s) => s.key === key);
31
- return found ? found.direction : null;
32
- }
33
- /** Returns the priority index of a sorted key, or -1 if not sorted. */
34
- indexOf(key) {
35
- return this._sorts.findIndex((s) => s.key === key);
36
- }
37
- // ── Actions ──
38
- /** 3-click cycle: not sorted → asc → desc → removed. */
39
- toggle(key) {
40
- const idx = this.indexOf(key);
41
- if (idx === -1) {
42
- this._sorts = Object.freeze([...this._sorts, { key, direction: "asc" }]);
43
- } else if (this._sorts[idx].direction === "asc") {
44
- const next = this._sorts.map(
45
- (s, i) => i === idx ? { key: s.key, direction: "desc" } : s
46
- );
47
- this._sorts = Object.freeze(next);
48
- } else {
49
- this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));
50
- }
51
- this.notify();
52
- }
53
- /** Replace all with a single sort. */
54
- setSort(key, direction) {
55
- this._sorts = Object.freeze([{ key, direction }]);
56
- this.notify();
57
- }
58
- /** Replace all sorts. */
59
- setSorts(sorts) {
60
- this._sorts = Object.freeze(sorts.map((s) => ({ ...s })));
61
- this.notify();
62
- }
63
- /** Clear all sort descriptors. */
64
- reset() {
65
- this._sorts = Object.freeze([]);
66
- this.notify();
67
- }
68
- // ── Pipeline ──
69
- /** Sort an array using the current descriptors. Returns a new sorted array. */
70
- apply(items, compareFn) {
71
- if (this._sorts.length === 0) return items;
72
- const sorted = items.slice();
73
- const sorts = this._sorts;
74
- sorted.sort((a, b) => {
75
- for (const { key, direction } of sorts) {
76
- let cmp;
77
- if (compareFn) {
78
- cmp = compareFn(a, b, key, direction);
79
- } else {
80
- cmp = defaultCompare(a, b, key);
81
- }
82
- if (direction === "desc") cmp = -cmp;
83
- if (cmp !== 0) return cmp;
84
- }
85
- return 0;
86
- });
87
- return sorted;
88
- }
89
- }
1
+ const require_Trackable = require("./Trackable.cjs");
2
+ //#region src/Sorting.ts
3
+ /**
4
+ * Multi-column sort state manager with a comparator pipeline.
5
+ * Maintains an ordered list of sort descriptors and applies them to arrays.
6
+ * Subscribable — auto-tracked when used as a ViewModel property.
7
+ */
8
+ var Sorting = class extends require_Trackable.Trackable {
9
+ _sorts;
10
+ constructor(options) {
11
+ super();
12
+ this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
13
+ }
14
+ /** Current list of active sort descriptors, in priority order. */
15
+ get sorts() {
16
+ return this._sorts;
17
+ }
18
+ /** Primary sort key (first descriptor), or null when empty. */
19
+ get key() {
20
+ return this._sorts.length > 0 ? this._sorts[0].key : null;
21
+ }
22
+ /** Primary sort direction. Defaults to 'asc' when empty. */
23
+ get direction() {
24
+ return this._sorts.length > 0 ? this._sorts[0].direction : "asc";
25
+ }
26
+ /** Whether the given key is currently sorted. */
27
+ isSorted(key) {
28
+ return this._sorts.some((s) => s.key === key);
29
+ }
30
+ /** Returns the sort direction for a key, or null if not sorted. */
31
+ directionOf(key) {
32
+ const found = this._sorts.find((s) => s.key === key);
33
+ return found ? found.direction : null;
34
+ }
35
+ /** Returns the priority index of a sorted key, or -1 if not sorted. */
36
+ indexOf(key) {
37
+ return this._sorts.findIndex((s) => s.key === key);
38
+ }
39
+ /** 3-click cycle: not sorted → asc → desc → removed. */
40
+ toggle(key) {
41
+ const idx = this.indexOf(key);
42
+ if (idx === -1) this._sorts = Object.freeze([...this._sorts, {
43
+ key,
44
+ direction: "asc"
45
+ }]);
46
+ else if (this._sorts[idx].direction === "asc") {
47
+ const next = this._sorts.map((s, i) => i === idx ? {
48
+ key: s.key,
49
+ direction: "desc"
50
+ } : s);
51
+ this._sorts = Object.freeze(next);
52
+ } else this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));
53
+ this.notify();
54
+ }
55
+ /** Replace all with a single sort. */
56
+ setSort(key, direction) {
57
+ this._sorts = Object.freeze([{
58
+ key,
59
+ direction
60
+ }]);
61
+ this.notify();
62
+ }
63
+ /** Replace all sorts. */
64
+ setSorts(sorts) {
65
+ this._sorts = Object.freeze(sorts.map((s) => ({ ...s })));
66
+ this.notify();
67
+ }
68
+ /** Clear all sort descriptors. */
69
+ reset() {
70
+ this._sorts = Object.freeze([]);
71
+ this.notify();
72
+ }
73
+ /** Sort an array using the current descriptors. Returns a new sorted array. */
74
+ apply(items, compareFn) {
75
+ if (this._sorts.length === 0) return items;
76
+ const sorted = items.slice();
77
+ const sorts = this._sorts;
78
+ sorted.sort((a, b) => {
79
+ for (const { key, direction } of sorts) {
80
+ let cmp;
81
+ if (compareFn) cmp = compareFn(a, b, key, direction);
82
+ else cmp = defaultCompare(a, b, key);
83
+ if (direction === "desc") cmp = -cmp;
84
+ if (cmp !== 0) return cmp;
85
+ }
86
+ return 0;
87
+ });
88
+ return sorted;
89
+ }
90
+ };
90
91
  function defaultCompare(a, b, key) {
91
- const aVal = a[key];
92
- const bVal = b[key];
93
- if (aVal == null && bVal == null) return 0;
94
- if (aVal == null) return -1;
95
- if (bVal == null) return 1;
96
- if (typeof aVal === "string" && typeof bVal === "string") {
97
- return aVal.localeCompare(bVal);
98
- }
99
- if (aVal < bVal) return -1;
100
- if (aVal > bVal) return 1;
101
- return 0;
92
+ const aVal = a[key];
93
+ const bVal = b[key];
94
+ if (aVal == null && bVal == null) return 0;
95
+ if (aVal == null) return -1;
96
+ if (bVal == null) return 1;
97
+ if (typeof aVal === "string" && typeof bVal === "string") return aVal.localeCompare(bVal);
98
+ if (aVal < bVal) return -1;
99
+ if (aVal > bVal) return 1;
100
+ return 0;
102
101
  }
102
+ //#endregion
103
103
  exports.Sorting = Sorting;
104
- //# sourceMappingURL=Sorting.cjs.map
104
+
105
+ //# sourceMappingURL=Sorting.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sorting.cjs","sources":["../src/Sorting.ts"],"sourcesContent":["import { Trackable } from './Trackable';\n\n/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> extends Trackable {\n private _sorts: readonly SortDescriptor[];\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n super();\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this.notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this.notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this.notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this.notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":["Trackable"],"mappings":"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"}
1
+ {"version":3,"file":"Sorting.cjs","names":[],"sources":["../src/Sorting.ts"],"sourcesContent":["import { Trackable } from './Trackable';\n\n/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> extends Trackable {\n private _sorts: readonly SortDescriptor[];\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n super();\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this.notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this.notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this.notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this.notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"mappings":";;;;;;;AAaA,IAAa,UAAb,cAAsC,kBAAA,UAAU;CAC9C;CAEA,YAAY,SAAwC;AAClD,SAAO;AACP,OAAK,SAAS,OAAO,OAAO,SAAS,OAAO,KAAI,OAAM,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;;;CAMzE,IAAI,QAAmC;AACrC,SAAO,KAAK;;;CAId,IAAI,MAAqB;AACvB,SAAO,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,GAAG,MAAM;;;CAIvD,IAAI,YAA4B;AAC9B,SAAO,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,GAAG,YAAY;;;CAM7D,SAAS,KAAsB;AAC7B,SAAO,KAAK,OAAO,MAAK,MAAK,EAAE,QAAQ,IAAI;;;CAI7C,YAAY,KAAoC;EAC9C,MAAM,QAAQ,KAAK,OAAO,MAAK,MAAK,EAAE,QAAQ,IAAI;AAClD,SAAO,QAAQ,MAAM,YAAY;;;CAInC,QAAQ,KAAqB;AAC3B,SAAO,KAAK,OAAO,WAAU,MAAK,EAAE,QAAQ,IAAI;;;CAMlD,OAAO,KAAmB;EACxB,MAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,MAAI,QAAQ,GAEV,MAAK,SAAS,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;GAAE;GAAK,WAAW;GAAgB,CAAC,CAAC;WACxE,KAAK,OAAO,KAAK,cAAc,OAAO;GAE/C,MAAM,OAAO,KAAK,OAAO,KAAK,GAAG,MAC/B,MAAM,MAAM;IAAE,KAAK,EAAE;IAAK,WAAW;IAAiB,GAAG,EAC1D;AACD,QAAK,SAAS,OAAO,OAAO,KAAK;QAGjC,MAAK,SAAS,OAAO,OAAO,KAAK,OAAO,QAAQ,GAAG,MAAM,MAAM,IAAI,CAAC;AAEtE,OAAK,QAAQ;;;CAIf,QAAQ,KAAa,WAAiC;AACpD,OAAK,SAAS,OAAO,OAAO,CAAC;GAAE;GAAK;GAAW,CAAC,CAAC;AACjD,OAAK,QAAQ;;;CAIf,SAAS,OAA+B;AACtC,OAAK,SAAS,OAAO,OAAO,MAAM,KAAI,OAAM,EAAE,GAAG,GAAG,EAAE,CAAC;AACvD,OAAK,QAAQ;;;CAIf,QAAc;AACZ,OAAK,SAAS,OAAO,OAAO,EAAE,CAAC;AAC/B,OAAK,QAAQ;;;CAMf,MACE,OACA,WACK;AACL,MAAI,KAAK,OAAO,WAAW,EAAG,QAAO;EACrC,MAAM,SAAS,MAAM,OAAO;EAC5B,MAAM,QAAQ,KAAK;AACnB,SAAO,MAAM,GAAG,MAAM;AACpB,QAAK,MAAM,EAAE,KAAK,eAAe,OAAO;IACtC,IAAI;AACJ,QAAI,UACF,OAAM,UAAU,GAAG,GAAG,KAAK,UAAU;QAErC,OAAM,eAAe,GAAG,GAAG,IAAI;AAEjC,QAAI,cAAc,OAAQ,OAAM,CAAC;AACjC,QAAI,QAAQ,EAAG,QAAO;;AAExB,UAAO;IACP;AACF,SAAO;;;AAIX,SAAS,eAAe,GAAQ,GAAQ,KAAqB;CAC3D,MAAM,OAAO,EAAE;CACf,MAAM,OAAO,EAAE;AACf,KAAI,QAAQ,QAAQ,QAAQ,KAAM,QAAO;AACzC,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI,OAAO,SAAS,YAAY,OAAO,SAAS,SAC9C,QAAO,KAAK,cAAc,KAAK;AAEjC,KAAI,OAAO,KAAM,QAAO;AACxB,KAAI,OAAO,KAAM,QAAO;AACxB,QAAO"}
package/dist/Sorting.js CHANGED
@@ -1,104 +1,105 @@
1
1
  import { Trackable } from "./Trackable.js";
2
- class Sorting extends Trackable {
3
- _sorts;
4
- constructor(options) {
5
- super();
6
- this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
7
- }
8
- // ── Readable state ──
9
- /** Current list of active sort descriptors, in priority order. */
10
- get sorts() {
11
- return this._sorts;
12
- }
13
- /** Primary sort key (first descriptor), or null when empty. */
14
- get key() {
15
- return this._sorts.length > 0 ? this._sorts[0].key : null;
16
- }
17
- /** Primary sort direction. Defaults to 'asc' when empty. */
18
- get direction() {
19
- return this._sorts.length > 0 ? this._sorts[0].direction : "asc";
20
- }
21
- // ── Query ──
22
- /** Whether the given key is currently sorted. */
23
- isSorted(key) {
24
- return this._sorts.some((s) => s.key === key);
25
- }
26
- /** Returns the sort direction for a key, or null if not sorted. */
27
- directionOf(key) {
28
- const found = this._sorts.find((s) => s.key === key);
29
- return found ? found.direction : null;
30
- }
31
- /** Returns the priority index of a sorted key, or -1 if not sorted. */
32
- indexOf(key) {
33
- return this._sorts.findIndex((s) => s.key === key);
34
- }
35
- // ── Actions ──
36
- /** 3-click cycle: not sorted → asc → desc → removed. */
37
- toggle(key) {
38
- const idx = this.indexOf(key);
39
- if (idx === -1) {
40
- this._sorts = Object.freeze([...this._sorts, { key, direction: "asc" }]);
41
- } else if (this._sorts[idx].direction === "asc") {
42
- const next = this._sorts.map(
43
- (s, i) => i === idx ? { key: s.key, direction: "desc" } : s
44
- );
45
- this._sorts = Object.freeze(next);
46
- } else {
47
- this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));
48
- }
49
- this.notify();
50
- }
51
- /** Replace all with a single sort. */
52
- setSort(key, direction) {
53
- this._sorts = Object.freeze([{ key, direction }]);
54
- this.notify();
55
- }
56
- /** Replace all sorts. */
57
- setSorts(sorts) {
58
- this._sorts = Object.freeze(sorts.map((s) => ({ ...s })));
59
- this.notify();
60
- }
61
- /** Clear all sort descriptors. */
62
- reset() {
63
- this._sorts = Object.freeze([]);
64
- this.notify();
65
- }
66
- // ── Pipeline ──
67
- /** Sort an array using the current descriptors. Returns a new sorted array. */
68
- apply(items, compareFn) {
69
- if (this._sorts.length === 0) return items;
70
- const sorted = items.slice();
71
- const sorts = this._sorts;
72
- sorted.sort((a, b) => {
73
- for (const { key, direction } of sorts) {
74
- let cmp;
75
- if (compareFn) {
76
- cmp = compareFn(a, b, key, direction);
77
- } else {
78
- cmp = defaultCompare(a, b, key);
79
- }
80
- if (direction === "desc") cmp = -cmp;
81
- if (cmp !== 0) return cmp;
82
- }
83
- return 0;
84
- });
85
- return sorted;
86
- }
87
- }
2
+ //#region src/Sorting.ts
3
+ /**
4
+ * Multi-column sort state manager with a comparator pipeline.
5
+ * Maintains an ordered list of sort descriptors and applies them to arrays.
6
+ * Subscribable auto-tracked when used as a ViewModel property.
7
+ */
8
+ var Sorting = class extends Trackable {
9
+ _sorts;
10
+ constructor(options) {
11
+ super();
12
+ this._sorts = Object.freeze(options?.sorts?.map((s) => ({ ...s })) ?? []);
13
+ }
14
+ /** Current list of active sort descriptors, in priority order. */
15
+ get sorts() {
16
+ return this._sorts;
17
+ }
18
+ /** Primary sort key (first descriptor), or null when empty. */
19
+ get key() {
20
+ return this._sorts.length > 0 ? this._sorts[0].key : null;
21
+ }
22
+ /** Primary sort direction. Defaults to 'asc' when empty. */
23
+ get direction() {
24
+ return this._sorts.length > 0 ? this._sorts[0].direction : "asc";
25
+ }
26
+ /** Whether the given key is currently sorted. */
27
+ isSorted(key) {
28
+ return this._sorts.some((s) => s.key === key);
29
+ }
30
+ /** Returns the sort direction for a key, or null if not sorted. */
31
+ directionOf(key) {
32
+ const found = this._sorts.find((s) => s.key === key);
33
+ return found ? found.direction : null;
34
+ }
35
+ /** Returns the priority index of a sorted key, or -1 if not sorted. */
36
+ indexOf(key) {
37
+ return this._sorts.findIndex((s) => s.key === key);
38
+ }
39
+ /** 3-click cycle: not sorted → asc → desc → removed. */
40
+ toggle(key) {
41
+ const idx = this.indexOf(key);
42
+ if (idx === -1) this._sorts = Object.freeze([...this._sorts, {
43
+ key,
44
+ direction: "asc"
45
+ }]);
46
+ else if (this._sorts[idx].direction === "asc") {
47
+ const next = this._sorts.map((s, i) => i === idx ? {
48
+ key: s.key,
49
+ direction: "desc"
50
+ } : s);
51
+ this._sorts = Object.freeze(next);
52
+ } else this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));
53
+ this.notify();
54
+ }
55
+ /** Replace all with a single sort. */
56
+ setSort(key, direction) {
57
+ this._sorts = Object.freeze([{
58
+ key,
59
+ direction
60
+ }]);
61
+ this.notify();
62
+ }
63
+ /** Replace all sorts. */
64
+ setSorts(sorts) {
65
+ this._sorts = Object.freeze(sorts.map((s) => ({ ...s })));
66
+ this.notify();
67
+ }
68
+ /** Clear all sort descriptors. */
69
+ reset() {
70
+ this._sorts = Object.freeze([]);
71
+ this.notify();
72
+ }
73
+ /** Sort an array using the current descriptors. Returns a new sorted array. */
74
+ apply(items, compareFn) {
75
+ if (this._sorts.length === 0) return items;
76
+ const sorted = items.slice();
77
+ const sorts = this._sorts;
78
+ sorted.sort((a, b) => {
79
+ for (const { key, direction } of sorts) {
80
+ let cmp;
81
+ if (compareFn) cmp = compareFn(a, b, key, direction);
82
+ else cmp = defaultCompare(a, b, key);
83
+ if (direction === "desc") cmp = -cmp;
84
+ if (cmp !== 0) return cmp;
85
+ }
86
+ return 0;
87
+ });
88
+ return sorted;
89
+ }
90
+ };
88
91
  function defaultCompare(a, b, key) {
89
- const aVal = a[key];
90
- const bVal = b[key];
91
- if (aVal == null && bVal == null) return 0;
92
- if (aVal == null) return -1;
93
- if (bVal == null) return 1;
94
- if (typeof aVal === "string" && typeof bVal === "string") {
95
- return aVal.localeCompare(bVal);
96
- }
97
- if (aVal < bVal) return -1;
98
- if (aVal > bVal) return 1;
99
- return 0;
92
+ const aVal = a[key];
93
+ const bVal = b[key];
94
+ if (aVal == null && bVal == null) return 0;
95
+ if (aVal == null) return -1;
96
+ if (bVal == null) return 1;
97
+ if (typeof aVal === "string" && typeof bVal === "string") return aVal.localeCompare(bVal);
98
+ if (aVal < bVal) return -1;
99
+ if (aVal > bVal) return 1;
100
+ return 0;
100
101
  }
101
- export {
102
- Sorting
103
- };
104
- //# sourceMappingURL=Sorting.js.map
102
+ //#endregion
103
+ export { Sorting };
104
+
105
+ //# sourceMappingURL=Sorting.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sorting.js","sources":["../src/Sorting.ts"],"sourcesContent":["import { Trackable } from './Trackable';\n\n/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> extends Trackable {\n private _sorts: readonly SortDescriptor[];\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n super();\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this.notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this.notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this.notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this.notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Sorting.js","names":[],"sources":["../src/Sorting.ts"],"sourcesContent":["import { Trackable } from './Trackable';\n\n/** Describes a single sort column with key and direction. */\nexport interface SortDescriptor {\n key: string;\n direction: 'asc' | 'desc';\n}\n\n/**\n * Multi-column sort state manager with a comparator pipeline.\n * Maintains an ordered list of sort descriptors and applies them to arrays.\n * Subscribable — auto-tracked when used as a ViewModel property.\n */\nexport class Sorting<T = any> extends Trackable {\n private _sorts: readonly SortDescriptor[];\n\n constructor(options?: { sorts?: SortDescriptor[] }) {\n super();\n this._sorts = Object.freeze(options?.sorts?.map(s => ({ ...s })) ?? []);\n }\n\n // ── Readable state ──\n\n /** Current list of active sort descriptors, in priority order. */\n get sorts(): readonly SortDescriptor[] {\n return this._sorts;\n }\n\n /** Primary sort key (first descriptor), or null when empty. */\n get key(): string | null {\n return this._sorts.length > 0 ? this._sorts[0].key : null;\n }\n\n /** Primary sort direction. Defaults to 'asc' when empty. */\n get direction(): 'asc' | 'desc' {\n return this._sorts.length > 0 ? this._sorts[0].direction : 'asc';\n }\n\n // ── Query ──\n\n /** Whether the given key is currently sorted. */\n isSorted(key: string): boolean {\n return this._sorts.some(s => s.key === key);\n }\n\n /** Returns the sort direction for a key, or null if not sorted. */\n directionOf(key: string): 'asc' | 'desc' | null {\n const found = this._sorts.find(s => s.key === key);\n return found ? found.direction : null;\n }\n\n /** Returns the priority index of a sorted key, or -1 if not sorted. */\n indexOf(key: string): number {\n return this._sorts.findIndex(s => s.key === key);\n }\n\n // ── Actions ──\n\n /** 3-click cycle: not sorted → asc → desc → removed. */\n toggle(key: string): void {\n const idx = this.indexOf(key);\n if (idx === -1) {\n // Add as asc\n this._sorts = Object.freeze([...this._sorts, { key, direction: 'asc' as const }]);\n } else if (this._sorts[idx].direction === 'asc') {\n // Flip to desc\n const next = this._sorts.map((s, i) =>\n i === idx ? { key: s.key, direction: 'desc' as const } : s\n );\n this._sorts = Object.freeze(next);\n } else {\n // Remove\n this._sorts = Object.freeze(this._sorts.filter((_, i) => i !== idx));\n }\n this.notify();\n }\n\n /** Replace all with a single sort. */\n setSort(key: string, direction: 'asc' | 'desc'): void {\n this._sorts = Object.freeze([{ key, direction }]);\n this.notify();\n }\n\n /** Replace all sorts. */\n setSorts(sorts: SortDescriptor[]): void {\n this._sorts = Object.freeze(sorts.map(s => ({ ...s })));\n this.notify();\n }\n\n /** Clear all sort descriptors. */\n reset(): void {\n this._sorts = Object.freeze([]);\n this.notify();\n }\n\n // ── Pipeline ──\n\n /** Sort an array using the current descriptors. Returns a new sorted array. */\n apply(\n items: T[],\n compareFn?: (a: T, b: T, key: string, dir: 'asc' | 'desc') => number,\n ): T[] {\n if (this._sorts.length === 0) return items;\n const sorted = items.slice();\n const sorts = this._sorts;\n sorted.sort((a, b) => {\n for (const { key, direction } of sorts) {\n let cmp: number;\n if (compareFn) {\n cmp = compareFn(a, b, key, direction);\n } else {\n cmp = defaultCompare(a, b, key);\n }\n if (direction === 'desc') cmp = -cmp;\n if (cmp !== 0) return cmp;\n }\n return 0;\n });\n return sorted;\n }\n}\n\nfunction defaultCompare(a: any, b: any, key: string): number {\n const aVal = a[key];\n const bVal = b[key];\n if (aVal == null && bVal == null) return 0;\n if (aVal == null) return -1;\n if (bVal == null) return 1;\n if (typeof aVal === 'string' && typeof bVal === 'string') {\n return aVal.localeCompare(bVal);\n }\n if (aVal < bVal) return -1;\n if (aVal > bVal) return 1;\n return 0;\n}\n"],"mappings":";;;;;;;AAaA,IAAa,UAAb,cAAsC,UAAU;CAC9C;CAEA,YAAY,SAAwC;AAClD,SAAO;AACP,OAAK,SAAS,OAAO,OAAO,SAAS,OAAO,KAAI,OAAM,EAAE,GAAG,GAAG,EAAE,IAAI,EAAE,CAAC;;;CAMzE,IAAI,QAAmC;AACrC,SAAO,KAAK;;;CAId,IAAI,MAAqB;AACvB,SAAO,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,GAAG,MAAM;;;CAIvD,IAAI,YAA4B;AAC9B,SAAO,KAAK,OAAO,SAAS,IAAI,KAAK,OAAO,GAAG,YAAY;;;CAM7D,SAAS,KAAsB;AAC7B,SAAO,KAAK,OAAO,MAAK,MAAK,EAAE,QAAQ,IAAI;;;CAI7C,YAAY,KAAoC;EAC9C,MAAM,QAAQ,KAAK,OAAO,MAAK,MAAK,EAAE,QAAQ,IAAI;AAClD,SAAO,QAAQ,MAAM,YAAY;;;CAInC,QAAQ,KAAqB;AAC3B,SAAO,KAAK,OAAO,WAAU,MAAK,EAAE,QAAQ,IAAI;;;CAMlD,OAAO,KAAmB;EACxB,MAAM,MAAM,KAAK,QAAQ,IAAI;AAC7B,MAAI,QAAQ,GAEV,MAAK,SAAS,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ;GAAE;GAAK,WAAW;GAAgB,CAAC,CAAC;WACxE,KAAK,OAAO,KAAK,cAAc,OAAO;GAE/C,MAAM,OAAO,KAAK,OAAO,KAAK,GAAG,MAC/B,MAAM,MAAM;IAAE,KAAK,EAAE;IAAK,WAAW;IAAiB,GAAG,EAC1D;AACD,QAAK,SAAS,OAAO,OAAO,KAAK;QAGjC,MAAK,SAAS,OAAO,OAAO,KAAK,OAAO,QAAQ,GAAG,MAAM,MAAM,IAAI,CAAC;AAEtE,OAAK,QAAQ;;;CAIf,QAAQ,KAAa,WAAiC;AACpD,OAAK,SAAS,OAAO,OAAO,CAAC;GAAE;GAAK;GAAW,CAAC,CAAC;AACjD,OAAK,QAAQ;;;CAIf,SAAS,OAA+B;AACtC,OAAK,SAAS,OAAO,OAAO,MAAM,KAAI,OAAM,EAAE,GAAG,GAAG,EAAE,CAAC;AACvD,OAAK,QAAQ;;;CAIf,QAAc;AACZ,OAAK,SAAS,OAAO,OAAO,EAAE,CAAC;AAC/B,OAAK,QAAQ;;;CAMf,MACE,OACA,WACK;AACL,MAAI,KAAK,OAAO,WAAW,EAAG,QAAO;EACrC,MAAM,SAAS,MAAM,OAAO;EAC5B,MAAM,QAAQ,KAAK;AACnB,SAAO,MAAM,GAAG,MAAM;AACpB,QAAK,MAAM,EAAE,KAAK,eAAe,OAAO;IACtC,IAAI;AACJ,QAAI,UACF,OAAM,UAAU,GAAG,GAAG,KAAK,UAAU;QAErC,OAAM,eAAe,GAAG,GAAG,IAAI;AAEjC,QAAI,cAAc,OAAQ,OAAM,CAAC;AACjC,QAAI,QAAQ,EAAG,QAAO;;AAExB,UAAO;IACP;AACF,SAAO;;;AAIX,SAAS,eAAe,GAAQ,GAAQ,KAAqB;CAC3D,MAAM,OAAO,EAAE;CACf,MAAM,OAAO,EAAE;AACf,KAAI,QAAQ,QAAQ,QAAQ,KAAM,QAAO;AACzC,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI,QAAQ,KAAM,QAAO;AACzB,KAAI,OAAO,SAAS,YAAY,OAAO,SAAS,SAC9C,QAAO,KAAK,cAAc,KAAK;AAEjC,KAAI,OAAO,KAAM,QAAO;AACxB,KAAI,OAAO,KAAM,QAAO;AACxB,QAAO"}