@pilotiq/pilotiq 0.24.1 → 0.24.3

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 (518) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/boost/guidelines.md +571 -0
  3. package/boost/skills/pilotiq-actions/SKILL.md +49 -0
  4. package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
  5. package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
  6. package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -0
  7. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  9. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  10. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  11. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  12. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  13. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  14. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  15. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  16. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  17. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  18. package/dist/Pilotiq.d.ts +31 -0
  19. package/dist/Pilotiq.d.ts.map +1 -1
  20. package/dist/Pilotiq.js +3 -1
  21. package/dist/Pilotiq.js.map +1 -1
  22. package/dist/PilotiqRegistry.d.ts +13 -0
  23. package/dist/PilotiqRegistry.d.ts.map +1 -1
  24. package/dist/PilotiqRegistry.js +15 -0
  25. package/dist/PilotiqRegistry.js.map +1 -1
  26. package/dist/pageData/misc.d.ts.map +1 -1
  27. package/dist/pageData/misc.js +6 -0
  28. package/dist/pageData/misc.js.map +1 -1
  29. package/dist/pageData/navigation.d.ts +1 -0
  30. package/dist/pageData/navigation.d.ts.map +1 -1
  31. package/dist/pageData/navigation.js +3 -0
  32. package/dist/pageData/navigation.js.map +1 -1
  33. package/dist/pageData/relationPages.d.ts.map +1 -1
  34. package/dist/pageData/relationPages.js +3 -0
  35. package/dist/pageData/relationPages.js.map +1 -1
  36. package/dist/pageData/resourcePages.d.ts.map +1 -1
  37. package/dist/pageData/resourcePages.js +8 -0
  38. package/dist/pageData/resourcePages.js.map +1 -1
  39. package/dist/react/AppShell.d.ts +8 -0
  40. package/dist/react/AppShell.d.ts.map +1 -1
  41. package/dist/react/AppShell.js.map +1 -1
  42. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  43. package/dist/react/layouts/SidebarLayout.js +10 -2
  44. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  45. package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
  46. package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
  47. package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
  48. package/dist/routes/relations.d.ts.map +1 -1
  49. package/dist/routes/relations.js +25 -18
  50. package/dist/routes/relations.js.map +1 -1
  51. package/dist/routes/resources.js.map +1 -1
  52. package/package.json +10 -5
  53. package/.turbo/turbo-build.log +0 -8
  54. package/CLAUDE.md +0 -265
  55. package/src/Cluster.test.ts +0 -283
  56. package/src/Cluster.ts +0 -83
  57. package/src/Column.test.ts +0 -199
  58. package/src/Column.ts +0 -710
  59. package/src/Global.test.ts +0 -367
  60. package/src/Global.ts +0 -169
  61. package/src/Page.test.ts +0 -114
  62. package/src/Page.ts +0 -208
  63. package/src/Pilotiq.perf.test.ts +0 -252
  64. package/src/Pilotiq.test.ts +0 -129
  65. package/src/Pilotiq.ts +0 -1158
  66. package/src/PilotiqRegistry.ts +0 -36
  67. package/src/PilotiqServiceProvider.ts +0 -121
  68. package/src/RelationManager.test.ts +0 -400
  69. package/src/RelationManager.ts +0 -527
  70. package/src/RenderHook.test.ts +0 -252
  71. package/src/RenderHook.ts +0 -242
  72. package/src/Resource.test.ts +0 -284
  73. package/src/Resource.ts +0 -526
  74. package/src/RightPanel.test.ts +0 -202
  75. package/src/RightPanel.ts +0 -132
  76. package/src/Tab.test.ts +0 -91
  77. package/src/Tab.ts +0 -156
  78. package/src/UserMenuItem.ts +0 -145
  79. package/src/actions/Action.test.ts +0 -2526
  80. package/src/actions/Action.ts +0 -1515
  81. package/src/actions/ActionGroup.test.ts +0 -112
  82. package/src/actions/ActionGroup.ts +0 -173
  83. package/src/actions/attachFactory.ts +0 -172
  84. package/src/actions/bulkFactories.ts +0 -168
  85. package/src/actions/crudFactories.ts +0 -220
  86. package/src/actions/exportFactory.ts +0 -225
  87. package/src/actions/factoryHelpers.ts +0 -177
  88. package/src/actions/importFactory.ts +0 -243
  89. package/src/actions/index.ts +0 -17
  90. package/src/actions/m2mFactories.ts +0 -193
  91. package/src/actions/relationFactories.ts +0 -372
  92. package/src/applyPageHooks.test.ts +0 -463
  93. package/src/applyPageHooks.ts +0 -330
  94. package/src/authorization.test.ts +0 -483
  95. package/src/breadcrumbs.test.ts +0 -238
  96. package/src/cells/coerce.test.ts +0 -85
  97. package/src/cells/coerce.ts +0 -84
  98. package/src/clusterPaths.ts +0 -35
  99. package/src/columns/BadgeColumn.test.ts +0 -54
  100. package/src/columns/BadgeColumn.ts +0 -32
  101. package/src/columns/BooleanColumn.test.ts +0 -41
  102. package/src/columns/BooleanColumn.ts +0 -18
  103. package/src/columns/ColorColumn.test.ts +0 -37
  104. package/src/columns/ColorColumn.ts +0 -38
  105. package/src/columns/IconColumn.test.ts +0 -54
  106. package/src/columns/IconColumn.ts +0 -37
  107. package/src/columns/ImageColumn.test.ts +0 -41
  108. package/src/columns/ImageColumn.ts +0 -28
  109. package/src/columns/SelectColumn.ts +0 -98
  110. package/src/columns/TextColumn.test.ts +0 -190
  111. package/src/columns/TextColumn.ts +0 -20
  112. package/src/columns/TextInputColumn.ts +0 -68
  113. package/src/columns/ToggleColumn.ts +0 -46
  114. package/src/columns/editableColumns.test.ts +0 -238
  115. package/src/columns/index.ts +0 -9
  116. package/src/defaultGlobalPages.ts +0 -95
  117. package/src/defaultPages.test.ts +0 -634
  118. package/src/defaultPages.ts +0 -617
  119. package/src/defaultViewPage.test.ts +0 -147
  120. package/src/elements/Form.test.ts +0 -223
  121. package/src/elements/Form.ts +0 -416
  122. package/src/elements/ListTabs.ts +0 -28
  123. package/src/elements/Table.test.ts +0 -422
  124. package/src/elements/Table.ts +0 -850
  125. package/src/elements/TableGroup.test.ts +0 -260
  126. package/src/elements/TableGroup.ts +0 -334
  127. package/src/elements/dispatchAction.test.ts +0 -463
  128. package/src/elements/dispatchAction.ts +0 -355
  129. package/src/elements/dispatchForm.test.ts +0 -477
  130. package/src/elements/dispatchForm.ts +0 -1993
  131. package/src/elements/dispatchTable.test.ts +0 -1514
  132. package/src/elements/dispatchTable.ts +0 -745
  133. package/src/elements/index.ts +0 -21
  134. package/src/entries/BadgeEntry.ts +0 -39
  135. package/src/entries/CodeEntry.test.ts +0 -40
  136. package/src/entries/CodeEntry.ts +0 -52
  137. package/src/entries/ColorEntry.ts +0 -63
  138. package/src/entries/ComponentEntry.test.ts +0 -173
  139. package/src/entries/ComponentEntry.ts +0 -95
  140. package/src/entries/Entry.ts +0 -304
  141. package/src/entries/IconEntry.ts +0 -49
  142. package/src/entries/ImageEntry.ts +0 -61
  143. package/src/entries/KeyValueEntry.ts +0 -47
  144. package/src/entries/RepeatableEntry.test.ts +0 -239
  145. package/src/entries/RepeatableEntry.ts +0 -173
  146. package/src/entries/TextEntry.test.ts +0 -394
  147. package/src/entries/TextEntry.ts +0 -60
  148. package/src/entries/index.ts +0 -12
  149. package/src/entries/leaves.test.ts +0 -306
  150. package/src/entries/registry.ts +0 -54
  151. package/src/fields/BuilderField.test.ts +0 -1188
  152. package/src/fields/BuilderField.ts +0 -605
  153. package/src/fields/BuilderRelationship.test.ts +0 -811
  154. package/src/fields/CheckboxField.test.ts +0 -44
  155. package/src/fields/CheckboxField.ts +0 -27
  156. package/src/fields/CheckboxListField.test.ts +0 -99
  157. package/src/fields/CheckboxListField.ts +0 -66
  158. package/src/fields/ColorPickerField.test.ts +0 -33
  159. package/src/fields/ColorPickerField.ts +0 -25
  160. package/src/fields/DateField.ts +0 -54
  161. package/src/fields/DateTimeField.test.ts +0 -55
  162. package/src/fields/EmailField.ts +0 -16
  163. package/src/fields/Field.test.ts +0 -654
  164. package/src/fields/Field.ts +0 -817
  165. package/src/fields/FileUploadField.test.ts +0 -143
  166. package/src/fields/FileUploadField.ts +0 -159
  167. package/src/fields/HiddenField.test.ts +0 -27
  168. package/src/fields/HiddenField.ts +0 -28
  169. package/src/fields/KeyValueField.test.ts +0 -105
  170. package/src/fields/KeyValueField.ts +0 -55
  171. package/src/fields/MarkdownField.test.ts +0 -167
  172. package/src/fields/MarkdownField.ts +0 -162
  173. package/src/fields/NumberField.ts +0 -33
  174. package/src/fields/RadioField.test.ts +0 -94
  175. package/src/fields/RadioField.ts +0 -67
  176. package/src/fields/RepeaterField.test.ts +0 -1806
  177. package/src/fields/RepeaterField.ts +0 -939
  178. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  179. package/src/fields/RepeaterSimple.test.ts +0 -248
  180. package/src/fields/RowButton.test.ts +0 -219
  181. package/src/fields/RowButton.ts +0 -135
  182. package/src/fields/SelectField.test.ts +0 -192
  183. package/src/fields/SelectField.ts +0 -235
  184. package/src/fields/SliderField.test.ts +0 -50
  185. package/src/fields/SliderField.ts +0 -53
  186. package/src/fields/SlugField.ts +0 -24
  187. package/src/fields/TagsInputField.test.ts +0 -154
  188. package/src/fields/TagsInputField.ts +0 -133
  189. package/src/fields/TextField.test.ts +0 -213
  190. package/src/fields/TextField.ts +0 -177
  191. package/src/fields/TextareaField.test.ts +0 -58
  192. package/src/fields/TextareaField.ts +0 -59
  193. package/src/fields/ToggleButtonsField.test.ts +0 -106
  194. package/src/fields/ToggleButtonsField.ts +0 -59
  195. package/src/fields/ToggleField.ts +0 -16
  196. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  197. package/src/fields/optionsResolver.ts +0 -95
  198. package/src/fields/resolveField.ts +0 -28
  199. package/src/filters/BooleanFilter.ts +0 -35
  200. package/src/filters/DateRangeFilter.test.ts +0 -194
  201. package/src/filters/DateRangeFilter.ts +0 -148
  202. package/src/filters/Filter.test.ts +0 -268
  203. package/src/filters/Filter.ts +0 -184
  204. package/src/filters/FormFilter.test.ts +0 -238
  205. package/src/filters/FormFilter.ts +0 -215
  206. package/src/filters/MultiSelectFilter.test.ts +0 -119
  207. package/src/filters/MultiSelectFilter.ts +0 -78
  208. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  209. package/src/filters/QueryBuilderFilter.ts +0 -398
  210. package/src/filters/SelectFilter.ts +0 -46
  211. package/src/filters/TernaryFilter.test.ts +0 -160
  212. package/src/filters/TernaryFilter.ts +0 -72
  213. package/src/filters/TrashedFilter.test.ts +0 -149
  214. package/src/filters/TrashedFilter.ts +0 -55
  215. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  216. package/src/filters/queryBuilder/Constraint.ts +0 -115
  217. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  218. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  219. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  220. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  221. package/src/filters/queryBuilder/index.ts +0 -12
  222. package/src/icons/index.ts +0 -2
  223. package/src/icons/lucide.ts +0 -204
  224. package/src/icons/registry.test.ts +0 -56
  225. package/src/icons/registry.ts +0 -41
  226. package/src/icons/types.ts +0 -47
  227. package/src/index.ts +0 -525
  228. package/src/io/csv.test.ts +0 -142
  229. package/src/io/csv.ts +0 -170
  230. package/src/nestedRelationManagerData.test.ts +0 -547
  231. package/src/notifications/Notification.test.ts +0 -210
  232. package/src/notifications/Notification.ts +0 -354
  233. package/src/notifications/broadcast.test.ts +0 -110
  234. package/src/notifications/broadcast.ts +0 -95
  235. package/src/notifications/database.test.ts +0 -383
  236. package/src/notifications/database.ts +0 -398
  237. package/src/notifications/databaseNotifications.test.ts +0 -187
  238. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  239. package/src/notifications/dispatchNotificationAction.ts +0 -142
  240. package/src/notifications/flash.test.ts +0 -89
  241. package/src/notifications/flash.ts +0 -71
  242. package/src/notifications/index.ts +0 -45
  243. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  244. package/src/notifications/registerBroadcastAuth.ts +0 -100
  245. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  246. package/src/notifications/resolveSavedNotification.ts +0 -59
  247. package/src/notifications/types.ts +0 -93
  248. package/src/orm/m2mAccessor.ts +0 -66
  249. package/src/orm/modelDefaults.test.ts +0 -633
  250. package/src/orm/modelDefaults.ts +0 -666
  251. package/src/pageData/breadcrumbs.ts +0 -288
  252. package/src/pageData/forms.ts +0 -578
  253. package/src/pageData/helpers.ts +0 -857
  254. package/src/pageData/misc.ts +0 -347
  255. package/src/pageData/navigation.ts +0 -842
  256. package/src/pageData/relationPages.ts +0 -1248
  257. package/src/pageData/relationTabs.ts +0 -286
  258. package/src/pageData/resourcePages.ts +0 -609
  259. package/src/pageData.test.ts +0 -1545
  260. package/src/pageData.ts +0 -341
  261. package/src/plugins/index.ts +0 -8
  262. package/src/plugins/themeEditor.test.ts +0 -36
  263. package/src/plugins/themeEditor.ts +0 -45
  264. package/src/react/AppShell.tsx +0 -251
  265. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  266. package/src/react/CollabRoomContext.ts +0 -98
  267. package/src/react/CollabTextRendererRegistry.ts +0 -102
  268. package/src/react/CommandPalette.tsx +0 -375
  269. package/src/react/CurrentUserContext.tsx +0 -50
  270. package/src/react/CustomPageWrapperGate.tsx +0 -69
  271. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  272. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  273. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  274. package/src/react/FieldPresenceRegistry.ts +0 -46
  275. package/src/react/FormCollabBindingRegistry.ts +0 -242
  276. package/src/react/FormStateContext.tsx +0 -591
  277. package/src/react/HeadHooks.tsx +0 -126
  278. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  279. package/src/react/MarkdownEditorRegistry.ts +0 -107
  280. package/src/react/NotificationActionStrip.tsx +0 -263
  281. package/src/react/NotificationBell.tsx +0 -426
  282. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  283. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  284. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  285. package/src/react/PendingSuggestionsContext.tsx +0 -172
  286. package/src/react/RecordWrapperGate.tsx +0 -58
  287. package/src/react/RecordWrapperRegistry.ts +0 -39
  288. package/src/react/RenderHookSlot.tsx +0 -32
  289. package/src/react/RightSidebar.tsx +0 -257
  290. package/src/react/RightSidebarContext.tsx +0 -234
  291. package/src/react/RightSidebarTrigger.tsx +0 -53
  292. package/src/react/RowCoordsContext.tsx +0 -23
  293. package/src/react/SchemaRenderer.tsx +0 -549
  294. package/src/react/SearchTrigger.tsx +0 -46
  295. package/src/react/ThemeProvider.tsx +0 -93
  296. package/src/react/ThemeSettingsPage.tsx +0 -579
  297. package/src/react/ThemeToggle.tsx +0 -20
  298. package/src/react/Toaster.tsx +0 -158
  299. package/src/react/UserMenu.tsx +0 -196
  300. package/src/react/WidgetDataContext.tsx +0 -157
  301. package/src/react/cells/EditableCell.tsx +0 -389
  302. package/src/react/component-slots.test.ts +0 -103
  303. package/src/react/component-slots.ts +0 -116
  304. package/src/react/fieldJsHandler.test.ts +0 -166
  305. package/src/react/fieldJsHandler.ts +0 -79
  306. package/src/react/fields/BuilderInput.tsx +0 -1078
  307. package/src/react/fields/CheckboxInput.tsx +0 -39
  308. package/src/react/fields/CheckboxListInput.tsx +0 -102
  309. package/src/react/fields/ColorInput.tsx +0 -71
  310. package/src/react/fields/DateFieldInput.tsx +0 -70
  311. package/src/react/fields/DateTimeInput.tsx +0 -62
  312. package/src/react/fields/FieldShell.tsx +0 -348
  313. package/src/react/fields/FileUploadInput.tsx +0 -639
  314. package/src/react/fields/HiddenInput.tsx +0 -17
  315. package/src/react/fields/KeyValueInput.tsx +0 -230
  316. package/src/react/fields/MarkdownInput.tsx +0 -560
  317. package/src/react/fields/RadioInput.tsx +0 -81
  318. package/src/react/fields/RepeaterInput.test.ts +0 -116
  319. package/src/react/fields/RepeaterInput.tsx +0 -1420
  320. package/src/react/fields/SelectFieldInput.tsx +0 -280
  321. package/src/react/fields/SliderInput.tsx +0 -81
  322. package/src/react/fields/TagsInput.tsx +0 -283
  323. package/src/react/fields/TextLikeInput.tsx +0 -256
  324. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  325. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  326. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  327. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  328. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  329. package/src/react/fields/repeaterReconcile.ts +0 -104
  330. package/src/react/fields/rowChromeButton.tsx +0 -336
  331. package/src/react/fields/rowState.ts +0 -106
  332. package/src/react/fields/syncRowGates.test.ts +0 -202
  333. package/src/react/fields/syncRowGates.ts +0 -66
  334. package/src/react/fields/textInputControls.tsx +0 -238
  335. package/src/react/fields/useRowReorderDnd.ts +0 -78
  336. package/src/react/formStateHelpers.test.ts +0 -508
  337. package/src/react/formStateHelpers.ts +0 -381
  338. package/src/react/hooks/use-mobile.ts +0 -19
  339. package/src/react/icon-context.tsx +0 -60
  340. package/src/react/index.ts +0 -194
  341. package/src/react/layouts/SidebarLayout.tsx +0 -250
  342. package/src/react/layouts/TopbarLayout.tsx +0 -258
  343. package/src/react/navigate.tsx +0 -37
  344. package/src/react/onProviderSynced.test.ts +0 -90
  345. package/src/react/parseRecordEditUrl.test.ts +0 -122
  346. package/src/react/parseRecordEditUrl.ts +0 -94
  347. package/src/react/persistedState.ts +0 -40
  348. package/src/react/registry.ts +0 -48
  349. package/src/react/right-panel-registry.tsx +0 -47
  350. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  351. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  352. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  353. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  354. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  355. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  356. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  357. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  358. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  359. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  360. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  361. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  362. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  363. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  364. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  365. package/src/react/schemaRenderer/constants.ts +0 -50
  366. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  367. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  368. package/src/react/schemaRenderer/helpers.tsx +0 -81
  369. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  370. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  371. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  372. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  373. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  374. package/src/react/schemaRenderer/table/links.tsx +0 -112
  375. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  376. package/src/react/schemaRenderer/table/url.tsx +0 -143
  377. package/src/react/theme-preview/apply.ts +0 -99
  378. package/src/react/theme-preview/build-html.ts +0 -436
  379. package/src/react/ui/button.tsx +0 -51
  380. package/src/react/ui/calendar.tsx +0 -67
  381. package/src/react/ui/checkbox.tsx +0 -29
  382. package/src/react/ui/dialog.tsx +0 -108
  383. package/src/react/ui/dropdown-menu.tsx +0 -97
  384. package/src/react/ui/input.tsx +0 -20
  385. package/src/react/ui/label.tsx +0 -21
  386. package/src/react/ui/popover.tsx +0 -50
  387. package/src/react/ui/select.tsx +0 -169
  388. package/src/react/ui/separator.tsx +0 -25
  389. package/src/react/ui/sheet.tsx +0 -136
  390. package/src/react/ui/sidebar.tsx +0 -723
  391. package/src/react/ui/skeleton.tsx +0 -13
  392. package/src/react/ui/slider.tsx +0 -34
  393. package/src/react/ui/switch.tsx +0 -28
  394. package/src/react/ui/table.tsx +0 -105
  395. package/src/react/ui/tabs.tsx +0 -63
  396. package/src/react/ui/textarea.tsx +0 -18
  397. package/src/react/ui/tooltip.tsx +0 -64
  398. package/src/react/useResizableWidth.ts +0 -139
  399. package/src/react/utils.ts +0 -6
  400. package/src/react/widgetRegistry.test.ts +0 -43
  401. package/src/react/widgetRegistry.ts +0 -50
  402. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  403. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  404. package/src/react/widgets/ViewRenderer.tsx +0 -71
  405. package/src/relationManagerData.test.ts +0 -1595
  406. package/src/richtext/index.ts +0 -8
  407. package/src/richtext/registry.ts +0 -89
  408. package/src/routes/globals.ts +0 -148
  409. package/src/routes/guard.test.ts +0 -325
  410. package/src/routes/helpers.ts +0 -704
  411. package/src/routes/pages.ts +0 -175
  412. package/src/routes/panel.ts +0 -204
  413. package/src/routes/relations.ts +0 -1243
  414. package/src/routes/resources.ts +0 -781
  415. package/src/routes/theme.ts +0 -91
  416. package/src/routes-nested-relations.test.ts +0 -676
  417. package/src/routes-relations.test.ts +0 -972
  418. package/src/routes.test.ts +0 -2027
  419. package/src/routes.ts +0 -303
  420. package/src/schema/Alert.test.ts +0 -109
  421. package/src/schema/Alert.ts +0 -131
  422. package/src/schema/Block.ts +0 -169
  423. package/src/schema/Breadcrumbs.ts +0 -40
  424. package/src/schema/Card.ts +0 -35
  425. package/src/schema/Divider.ts +0 -20
  426. package/src/schema/Element.ts +0 -219
  427. package/src/schema/EmptyState.test.ts +0 -37
  428. package/src/schema/EmptyState.ts +0 -63
  429. package/src/schema/Fieldset.ts +0 -43
  430. package/src/schema/Grid.ts +0 -43
  431. package/src/schema/Group.ts +0 -30
  432. package/src/schema/Heading.ts +0 -39
  433. package/src/schema/Html.ts +0 -67
  434. package/src/schema/Icon.ts +0 -54
  435. package/src/schema/Image.ts +0 -57
  436. package/src/schema/LinkTag.ts +0 -41
  437. package/src/schema/Markdown.ts +0 -85
  438. package/src/schema/MetaTag.ts +0 -41
  439. package/src/schema/RelationTabs.ts +0 -71
  440. package/src/schema/ScriptTag.ts +0 -55
  441. package/src/schema/Section.ts +0 -160
  442. package/src/schema/ServerDataElement.test.ts +0 -140
  443. package/src/schema/ServerDataElement.ts +0 -156
  444. package/src/schema/SlotComponent.test.ts +0 -77
  445. package/src/schema/SlotComponent.ts +0 -71
  446. package/src/schema/Split.ts +0 -50
  447. package/src/schema/Stat.test.ts +0 -118
  448. package/src/schema/Stat.ts +0 -154
  449. package/src/schema/StatsOverview.test.ts +0 -141
  450. package/src/schema/StatsOverview.ts +0 -119
  451. package/src/schema/StyleTag.ts +0 -35
  452. package/src/schema/TableWidget.test.ts +0 -297
  453. package/src/schema/TableWidget.ts +0 -289
  454. package/src/schema/Tabs.ts +0 -79
  455. package/src/schema/Text.ts +0 -58
  456. package/src/schema/UnorderedList.ts +0 -49
  457. package/src/schema/View.test.ts +0 -111
  458. package/src/schema/View.ts +0 -127
  459. package/src/schema/Wizard.ts +0 -220
  460. package/src/schema/containers.test.ts +0 -564
  461. package/src/schema/headTags.test.ts +0 -134
  462. package/src/schema/index.ts +0 -40
  463. package/src/schema/primes.test.ts +0 -269
  464. package/src/schema/resolveSchema.test.ts +0 -379
  465. package/src/schema/resolveSchema.ts +0 -917
  466. package/src/schema/sanitize.ts +0 -58
  467. package/src/search.test.ts +0 -446
  468. package/src/search.ts +0 -178
  469. package/src/sessionFilters.test.ts +0 -375
  470. package/src/sessionFilters.ts +0 -143
  471. package/src/slot-components/index.ts +0 -10
  472. package/src/slot-components/registry.ts +0 -56
  473. package/src/styles/file-upload.css +0 -13
  474. package/src/summarizers/Summarizer.test.ts +0 -84
  475. package/src/summarizers/Summarizer.ts +0 -123
  476. package/src/summarizers/index.ts +0 -11
  477. package/src/theme/base-colors.ts +0 -68
  478. package/src/theme/chart-colors.ts +0 -50
  479. package/src/theme/colors.ts +0 -447
  480. package/src/theme/generate-css.test.ts +0 -139
  481. package/src/theme/generate-css.ts +0 -44
  482. package/src/theme/generate-scale.test.ts +0 -106
  483. package/src/theme/generate-scale.ts +0 -97
  484. package/src/theme/icon-map.ts +0 -42
  485. package/src/theme/index.ts +0 -34
  486. package/src/theme/migrate.test.ts +0 -178
  487. package/src/theme/migrate.ts +0 -81
  488. package/src/theme/presets.ts +0 -135
  489. package/src/theme/radius.ts +0 -18
  490. package/src/theme/resolve.test.ts +0 -238
  491. package/src/theme/resolve.ts +0 -96
  492. package/src/theme/spacing.ts +0 -18
  493. package/src/theme/storage.test.ts +0 -126
  494. package/src/theme/storage.ts +0 -106
  495. package/src/theme/theme-colors.ts +0 -88
  496. package/src/theme/types.ts +0 -125
  497. package/src/uploads/UploadAdapter.ts +0 -35
  498. package/src/uploads/index.ts +0 -2
  499. package/src/uploads/localUpload.test.ts +0 -70
  500. package/src/uploads/localUpload.ts +0 -84
  501. package/src/validation/Validator.ts +0 -49
  502. package/src/validation/index.ts +0 -28
  503. package/src/validation/rules.ts +0 -78
  504. package/src/validation/runValidators.ts +0 -435
  505. package/src/validation/uniqueValidator.test.ts +0 -196
  506. package/src/validation/uniqueValidator.ts +0 -133
  507. package/src/validation/validators.test.ts +0 -268
  508. package/src/vite.test.ts +0 -184
  509. package/src/vite.ts +0 -787
  510. package/src/widgets/index.ts +0 -10
  511. package/src/widgets/registry.ts +0 -45
  512. package/src/widgets.test.ts +0 -592
  513. package/tsconfig.build.json +0 -11
  514. package/tsconfig.json +0 -4
  515. package/tsconfig.test.json +0 -10
  516. package/views/react/Dashboard.tsx +0 -27
  517. package/views/react/Resources/Form.tsx +0 -102
  518. package/views/react/Resources/Index.tsx +0 -49
@@ -1,745 +0,0 @@
1
- import { Element, type ElementMeta } from '../schema/Element.js'
2
- import { Table, type TableContext, type SortDirection } from './Table.js'
3
- import { TableGroup, bucketDateValue, formatDateBucketTitle } from './TableGroup.js'
4
- import type { Filter } from '../filters/Filter.js'
5
- import { Action } from '../actions/Action.js'
6
- import { Column, type ColumnSelectOption } from '../Column.js'
7
- import { SelectColumn, normalizeSelectOptions, type SelectColumnOptionsResolver } from '../columns/SelectColumn.js'
8
- import { ListTab } from '../Tab.js'
9
- import { isRepeaterField } from '../fields/RepeaterField.js'
10
- import { isBuilderField } from '../fields/BuilderField.js'
11
- import { tryRenderRichText, getRichTextRenderer } from '../richtext/registry.js'
12
- import { resolveSchema, type RenderContext } from '../schema/resolveSchema.js'
13
- import { sanitizeHtml } from '../schema/sanitize.js'
14
- import { marked } from 'marked'
15
- import type { SummaryResult } from '../summarizers/Summarizer.js'
16
-
17
- export interface QueryParams {
18
- search?: string
19
- sort?: string // "col:dir" or "col"
20
- page?: string | number
21
- perPage?: string | number
22
- /** Filter values keyed by filter name. Any URL query key not in the
23
- * reserved set above is treated as a candidate filter value. */
24
- [key: string]: unknown
25
- }
26
-
27
- const RESERVED_QUERY_KEYS = new Set(['search', 'sort', 'page', 'perPage', 'group', 'groupKey'])
28
-
29
- export function prefixedKey(prefix: string | undefined, key: string): string {
30
- return prefix === undefined || prefix === '' ? key : `${prefix}_${key}`
31
- }
32
-
33
- function prefixedReservedKeys(prefix: string | undefined): Set<string> {
34
- if (prefix === undefined || prefix === '') return RESERVED_QUERY_KEYS
35
- return new Set([...RESERVED_QUERY_KEYS].map(k => `${prefix}_${k}`))
36
- }
37
-
38
- /**
39
- * Optional hooks passed by the caller (`pageData.resourceIndexData`)
40
- * to plug Resource-level concerns into the table dispatcher without
41
- * giving it a hard dependency on `Resource`.
42
- *
43
- * `canEdit` is consulted once per row when the table has at least one
44
- * editable column (`TextInputColumn / ToggleColumn / SelectColumn`) —
45
- * the result gates per-cell edit affordances. Custom-page tables and
46
- * relation-manager tables (v1) pass `undefined` and skip per-cell edit.
47
- */
48
- export interface LoadTableHooks {
49
- canEdit?: (
50
- user: unknown,
51
- record: Record<string, unknown>,
52
- ) => boolean | Promise<boolean>
53
- }
54
-
55
- export function parseFilterValues(
56
- query: QueryParams,
57
- filters: ReadonlyArray<Filter>,
58
- prefix?: string,
59
- ): Record<string, string> {
60
- if (filters.length === 0) return {}
61
- const filterNames = new Set(filters.map(f => f.name))
62
- const reserved = prefixedReservedKeys(prefix)
63
- const stripPrefix = prefix !== undefined && prefix !== ''
64
- ? `${prefix}_`
65
- : ''
66
- const out: Record<string, string> = {}
67
- for (const [key, val] of Object.entries(query)) {
68
- if (reserved.has(key)) continue
69
- // Strip the table's prefix when present; un-prefixed keys belong to
70
- // some other namespace on the page so they can't match this table's
71
- // filters.
72
- let logicalKey = key
73
- if (stripPrefix !== '') {
74
- if (!key.startsWith(stripPrefix)) continue
75
- logicalKey = key.slice(stripPrefix.length)
76
- }
77
- if (!filterNames.has(logicalKey)) continue
78
- if (typeof val === 'string' && val !== '') out[logicalKey] = val
79
- }
80
- return out
81
- }
82
-
83
- export function parseTableQuery(q: QueryParams = {}, prefix?: string): {
84
- search: string | undefined
85
- sort: { column: string; direction: SortDirection } | undefined
86
- page: number | undefined
87
- perPage: number | undefined
88
- } {
89
- const k = (key: string): string => prefixedKey(prefix, key)
90
- const searchRaw = q[k('search')]
91
- const search = typeof searchRaw === 'string' && searchRaw.trim() !== ''
92
- ? searchRaw.trim()
93
- : undefined
94
-
95
- let sort: { column: string; direction: SortDirection } | undefined
96
- const sortRaw = q[k('sort')]
97
- if (typeof sortRaw === 'string' && sortRaw.trim() !== '') {
98
- const [colRaw, dirRaw] = sortRaw.split(':')
99
- const column = colRaw?.trim()
100
- if (column) {
101
- const direction: SortDirection = dirRaw?.trim() === 'desc' ? 'desc' : 'asc'
102
- sort = { column, direction }
103
- }
104
- }
105
-
106
- const pageRaw = q[k('page')]
107
- const page = pageRaw !== undefined
108
- ? Math.max(1, Math.floor(Number(pageRaw)) || 1)
109
- : undefined
110
-
111
- const perPageRaw = q[k('perPage')]
112
- const perPage = perPageRaw !== undefined && Number(perPageRaw) > 0
113
- ? Math.floor(Number(perPageRaw))
114
- : undefined
115
-
116
- return { search, sort, page, perPage }
117
- }
118
-
119
- /**
120
- * Reconcile the URL `?group=` key against a table's configured
121
- * `defaultGroup` + `groups([...])` registration. Returns the column
122
- * the table should band rows by for this request, or `undefined` for
123
- * "no grouping".
124
- *
125
- * Selection rules:
126
- *
127
- * `?group=col` → that column, IF it's registered or matches
128
- * the bare `defaultGroup` (otherwise no grouping).
129
- * `?group=` (empty) → no grouping (overrides defaultGroup).
130
- * absent → fall back to `defaultGroup`.
131
- */
132
- export function parseActiveGroup(
133
- query: QueryParams,
134
- table: Table,
135
- prefix?: string,
136
- ): string | undefined {
137
- const raw = query[prefixedKey(prefix, 'group')]
138
- if (typeof raw === 'string') {
139
- if (raw === '') return undefined // explicit clear
140
- const registered = table.getGroups().map(g => g.getColumn())
141
- const fallback = table.getDefaultGroup()
142
- // Trust either an explicit registration OR the bare-column form.
143
- // Unknown columns → treat as no grouping (silent — stale bookmark).
144
- if (registered.includes(raw)) return raw
145
- if (fallback === raw) return raw
146
- return undefined
147
- }
148
- return table.getDefaultGroup()
149
- }
150
-
151
- /**
152
- * Read the drilled-in group key from the URL. Returns the key string when
153
- * the URL carries a value AND the active group exists AND is `scopable`;
154
- * `undefined` otherwise (stale bookmark, group dropped, or never set).
155
- *
156
- * The active column resolution runs first so the drill-in state can be
157
- * silently dropped without affecting the parent group's reconciliation —
158
- * a stale `groupKey` from a renamed-column or non-scopable group falls
159
- * through to "no drill-in", which is the same as the user not having
160
- * clicked a heading.
161
- */
162
- export function parseActiveGroupKey(
163
- query: QueryParams,
164
- table: Table,
165
- activeColumn: string | undefined,
166
- prefix?: string,
167
- ): string | undefined {
168
- if (activeColumn === undefined) return undefined
169
- const raw = query[prefixedKey(prefix, 'groupKey')]
170
- if (typeof raw !== 'string') return undefined
171
- if (raw === '') return undefined // explicit clear
172
- const group = table.getGroups().find(g => g.getColumn() === activeColumn)
173
- // Bare-column groups (synthesized from defaultGroup with no entry in
174
- // `groups([…])`) can't be scopable — `.scopeQueryByKey()` is a builder
175
- // call. Drop the drill-in silently.
176
- if (!group || !group.isScopable()) return undefined
177
- return raw
178
- }
179
-
180
- /** Walk an Element tree and return every `Table` instance in document order. */
181
- export function findTables(elements: ReadonlyArray<Element>): Table[] {
182
- const tables: Table[] = []
183
- const walk = (els: ReadonlyArray<Element>): void => {
184
- for (const el of els) {
185
- if (el instanceof Table) tables.push(el)
186
- // Plan #14 — Tables inside Repeater / Builder rows aren't
187
- // supported in v1. Stop at the array-row boundary so the parent
188
- // table dispatcher doesn't pick them up.
189
- if (isRepeaterField(el)) continue
190
- if (isBuilderField(el)) continue
191
- const children = el.getChildren()
192
- if (children && children.length > 0) walk(children)
193
- }
194
- }
195
- walk(elements)
196
- return tables
197
- }
198
-
199
- /**
200
- * For every `Table` on the page that has a `records()` handler, run it
201
- * with the parsed `TableContext` and seed the table's render-time state
202
- * (rows, total, sort, search, page).
203
- *
204
- * Tables without a `records()` handler are left untouched — `toMeta()`
205
- * will emit them with no rows, which the renderer falls back to as
206
- * "No records yet."
207
- *
208
- * `pathname` is the absolute route the page lives at (e.g.
209
- * `/admin/articles`). Sort, search, and pagination links in the rendered
210
- * table prefix with it so SPA navigation has a real pathname to route
211
- * against — Vike's client-side router doesn't resolve `?qs`-only
212
- * relative hrefs against the current URL.
213
- */
214
- export async function loadTableRecords(
215
- elements: ReadonlyArray<Element>,
216
- query: QueryParams = {},
217
- pathname?: string,
218
- user?: unknown,
219
- hooks?: LoadTableHooks,
220
- ): Promise<void> {
221
- const tables = findTables(elements)
222
- if (tables.length === 0) return
223
-
224
- await Promise.all(tables.map(async (table) => {
225
- const prefix = table.getQueryStringIdentifier()
226
- const { search, sort, page, perPage } = parseTableQuery(query, prefix)
227
-
228
- // Carry per-table defaults forward when the URL didn't override them.
229
- // Reorderable tables fall back to `(reorderableColumn, asc)` when no
230
- // explicit `defaultSort` is set, so the visible order matches the
231
- // persisted column even before the user clicks a header.
232
- const reorderFallback = table.isReorderable() && !table.getDefaultSort()
233
- ? { column: table.getReorderableColumn()!, direction: 'asc' as const }
234
- : undefined
235
- const effectiveSort = sort ?? table.getDefaultSort() ?? reorderFallback
236
- const effectivePerPage = perPage ?? table.getPerPage()
237
- const effectivePage = page ?? 1
238
-
239
- // Parse filter values from the URL query. Mirror them back onto the
240
- // Filter elements so the renderer can show the active selection, and
241
- // pass them through TableContext for the records handler to consume.
242
- const tableFilters = table.getFilters()
243
- const filterValues = parseFilterValues(query, tableFilters, prefix)
244
- for (const filter of tableFilters) {
245
- const v = filterValues[filter.name]
246
- if (v !== undefined) filter.withValue(v)
247
- }
248
-
249
- // Thread the active list-page tab + its query modifier through to the
250
- // records handler. The active tab was marked by `pageData.resolveActiveTab`
251
- // (`tab.withActive(true)`) before `loadTableRecords` was called, so we
252
- // just walk the schema for it. User-defined `Table.records(fn)`
253
- // handlers can branch on `ctx.tab`; the model adapter consults
254
- // `ctx.tabQuery` to splice the predicate into its ORM query chain.
255
- const activeTab = findActiveTab(elements)
256
-
257
- // Reconcile group + drill-in BEFORE building ctx so `groupScope` can
258
- // be threaded into the records handler (model adapter splices the
259
- // scoper after filters). A live drill-in also resets the page to 1
260
- // server-side — clicking a heading should reliably land on the first
261
- // page of the bucket regardless of where the user was paginated to.
262
- const activeGroupCol = parseActiveGroup(query, table, prefix)
263
- // Stamp the reconciled column onto the table early so
264
- // `getActiveGroupInstance()` can resolve to a registered group (or
265
- // synthesize a bare-column instance) consistently across the file.
266
- table.withActiveGroup(activeGroupCol ?? '')
267
- const activeGroup = table.getActiveGroupInstance()
268
- const drilledKey = parseActiveGroupKey(query, table, activeGroupCol, prefix)
269
- const effectivePageWithDrill = drilledKey !== undefined ? 1 : effectivePage
270
-
271
- const ctx: TableContext = {
272
- ...(search !== undefined ? { search } : {}),
273
- ...(effectiveSort !== undefined ? { sort: effectiveSort } : {}),
274
- ...(effectivePerPage !== undefined ? { perPage: effectivePerPage } : {}),
275
- ...(Object.keys(filterValues).length > 0 ? { filters: filterValues } : {}),
276
- ...(activeTab ? { tab: activeTab.name } : {}),
277
- ...(activeTab?.getQuery() ? { tabQuery: activeTab.getQuery()! } : {}),
278
- ...(user != null ? { user } : {}),
279
- ...(drilledKey !== undefined && activeGroup !== undefined
280
- ? { groupScope: { group: activeGroup, key: drilledKey } }
281
- : {}),
282
- page: effectivePageWithDrill,
283
- }
284
-
285
- // Apply the tab's TableContext customizer last (escape hatch — wins
286
- // over filters/tab/etc. that we just set above).
287
- const ctxFn = activeTab?.getContextFn()
288
- const finalCtx = ctxFn ? ctxFn(ctx) : ctx
289
-
290
- // Skip records + per-row work when the table is deferred (the client
291
- // refetches via `tableUrl`); URL state still mirrors below so the
292
- // skeleton frame keeps current sort / search / page accurate.
293
- const handler = table.getRecords()
294
- if (handler && !table.isDeferred()) {
295
- const result = await handler(finalCtx)
296
- const rawRows = Array.isArray(result) ? result : result.rows
297
- const total = Array.isArray(result) ? rawRows.length : (result.total ?? rawRows.length)
298
-
299
- // Per-row visibility evaluation for row-placement actions with rules.
300
- // Static row actions (no rules) are always visible/enabled, so we
301
- // skip stamping on rows when none of the table's row actions opt in.
302
- const rowActionsWithRules = (table.getChildren() ?? [])
303
- .filter((c): c is Action =>
304
- c instanceof Action
305
- && c.getPlacement() === 'row'
306
- && c.hasVisibilityRules(),
307
- )
308
-
309
- // Per-row server-side `formatStateUsing` eval. Columns with a
310
- // formatter (the function isn't serializable) get their result
311
- // stashed under `row._formatted[columnName]` so the renderer can
312
- // pick it up without re-running the function client-side.
313
- const columnsWithFormatter = (table.getChildren() ?? [])
314
- .filter((c): c is Column => c instanceof Column && c.hasFormatter())
315
-
316
- // Plain-text columns that may carry Tiptap rich-text content.
317
- // Auto-rendered by the registered richtext renderer (Phase D —
318
- // wired by `registerTiptap()`). Conservative: only default-text
319
- // columns without an explicit `formatStateUsing` / `format` /
320
- // editable-input override. Without a registered renderer the
321
- // per-row pass is skipped entirely so non-tiptap apps pay nothing.
322
- const richTextColumns = getRichTextRenderer() !== undefined
323
- ? (table.getChildren() ?? [])
324
- .filter((c): c is Column =>
325
- c instanceof Column
326
- && c.getColumnType() === 'text'
327
- && !c.hasFormatter()
328
- && !c.hasFormat()
329
- && !c.isEditable()
330
- && !c.isRichText(),
331
- )
332
- : []
333
-
334
- // Markdown / HTML columns — opt-in via `Column.markdown() /
335
- // Column.html()`. Each row's value gets server-rendered and the
336
- // resulting HTML stamps `_formatted[col.name]` +
337
- // `_richtextCells[col.name] = true` so the renderer mounts the
338
- // existing prose-sm `dangerouslySetInnerHTML` path.
339
- const explicitRichTextColumns = (table.getChildren() ?? [])
340
- .filter((c): c is Column => c instanceof Column && c.isRichText())
341
-
342
- // Columns with their own per-row recordUrl handler — overrides
343
- // the table-level `Table.recordUrl` for clicks on that column.
344
- const columnsWithRecordUrl = (table.getChildren() ?? [])
345
- .filter((c): c is Column => c instanceof Column && c.hasRecordUrlHandler())
346
-
347
- // Inline-edit columns (`TextInputColumn / ToggleColumn / SelectColumn`).
348
- // Per-row stamping is gated on a `canEdit` hook — without it the
349
- // dispatcher has no way to authorize the cell, so we skip stamping
350
- // entirely and the renderer falls back to the read-only formatter.
351
- const editableColumns = (table.getChildren() ?? [])
352
- .filter((c): c is Column => c instanceof Column && c.isEditable())
353
- const canEditEditableColumns = editableColumns.length > 0 && hooks?.canEdit !== undefined
354
-
355
- // SelectColumns with a per-row options resolver — pre-filtered so the
356
- // row loop doesn't re-walk `editableColumns` to find them. Stamps
357
- // `row._cellSelectOptions[col.name]` with the resolved option list.
358
- const selectColumnsWithResolver: Array<{ name: string; resolve: SelectColumnOptionsResolver }> =
359
- editableColumns
360
- .filter((c): c is SelectColumn => c instanceof SelectColumn)
361
- .map(c => ({ name: c.name, resolve: c.getOptionsResolver() }))
362
- .filter((c): c is { name: string; resolve: SelectColumnOptionsResolver } => c.resolve !== undefined)
363
-
364
- const recordUrlFn = table.getRecordUrl()
365
- const recordClassesFn = table.getRecordClasses()
366
- const cardSchemaFn = table.isCardsLayout() ? table.getCardSchema() : undefined
367
- // `activeGroupCol` + `activeGroup` are reconciled at the top of
368
- // the table-async callback. Stamp the drilled key back onto the
369
- // table here so `toMeta()` emits `activeGroupKey` alongside the
370
- // rest of the render-time state. When drilled in, the rendered
371
- // set is already filtered to one bucket — the renderer doesn't
372
- // want heading rows or per-group summaries. `bandingActive`
373
- // gates every banding-side decision (`_groupValue` stamping,
374
- // stable-sort, per-group summaries); `activeGroup` stays bound
375
- // so the chip + scope wiring can still reach the resolved
376
- // group instance.
377
- table.withActiveGroupKey(drilledKey)
378
- const bandingActive = activeGroup !== undefined && drilledKey === undefined
379
- const groupColumn = bandingActive ? activeGroup!.getColumn() : undefined
380
-
381
- const needsRowMutation =
382
- rowActionsWithRules.length > 0 ||
383
- columnsWithFormatter.length > 0 ||
384
- columnsWithRecordUrl.length > 0 ||
385
- recordUrlFn !== undefined ||
386
- recordClassesFn !== undefined ||
387
- groupColumn !== undefined ||
388
- canEditEditableColumns ||
389
- richTextColumns.length > 0 ||
390
- explicitRichTextColumns.length > 0 ||
391
- cardSchemaFn !== undefined
392
-
393
- const rows = !needsRowMutation
394
- ? rawRows
395
- : await Promise.all(rawRows.map(async row => {
396
- const recordObj = row as Record<string, unknown>
397
- const out: Record<string, unknown> = { ...recordObj }
398
-
399
- if (rowActionsWithRules.length > 0) {
400
- const visibleActions: string[] = []
401
- const disabledActions: string[] = []
402
- const evals = await Promise.all(
403
- rowActionsWithRules.map(a =>
404
- a.evaluate(user !== undefined ? { record: row, user } : { record: row }),
405
- ),
406
- )
407
- rowActionsWithRules.forEach((a, i) => {
408
- const { visible, disabled } = evals[i]!
409
- if (visible) visibleActions.push(a.name)
410
- if (disabled) disabledActions.push(a.name)
411
- })
412
- out['_visibleActions'] = visibleActions
413
- out['_disabledActions'] = disabledActions
414
- }
415
-
416
- if (columnsWithFormatter.length > 0) {
417
- const formatted: Record<string, string> = {}
418
- for (const col of columnsWithFormatter) {
419
- const fn = col.getFormatStateHandler()
420
- if (!fn) continue
421
- try {
422
- formatted[col.name] = fn(recordObj[col.name], recordObj)
423
- } catch {
424
- // Swallow per-row formatter errors; the renderer falls
425
- // back to the raw value.
426
- }
427
- }
428
- out['_formatted'] = formatted
429
- }
430
-
431
- if (richTextColumns.length > 0) {
432
- const formatted = (out['_formatted'] as Record<string, string> | undefined) ?? {}
433
- const rich: Record<string, true> = {}
434
- for (const col of richTextColumns) {
435
- // `formatStateUsing` already won — skip when the column
436
- // had a result stamped in the prior loop (formatStateUsing
437
- // and richTextColumns are mutually exclusive at filter
438
- // time, so this is defensive).
439
- if (formatted[col.name] !== undefined) continue
440
- const html = tryRenderRichText(recordObj[col.name])
441
- if (html === null) continue
442
- formatted[col.name] = html
443
- rich[col.name] = true
444
- }
445
- if (Object.keys(rich).length > 0) {
446
- out['_formatted'] = formatted
447
- out['_richtextCells'] = rich
448
- }
449
- }
450
-
451
- if (explicitRichTextColumns.length > 0) {
452
- const formatted = (out['_formatted'] as Record<string, string> | undefined) ?? {}
453
- const rich = (out['_richtextCells'] as Record<string, true> | undefined) ?? {}
454
- for (const col of explicitRichTextColumns) {
455
- // formatStateUsing wins (declared first by the user — we
456
- // assume they meant it). markdown()/html() yield only when
457
- // the explicit per-row formatter already stamped a value.
458
- if (formatted[col.name] !== undefined) continue
459
- const raw = recordObj[col.name]
460
- if (raw === null || raw === undefined || raw === '') continue
461
- const kind = col.getRichTextKind()
462
- let html: string
463
- try {
464
- html = kind === 'markdown'
465
- ? (marked.parse(String(raw), { gfm: true, breaks: false, async: false }) as string)
466
- : String(raw)
467
- } catch {
468
- // marked errors fall through to a string-cast so a
469
- // malformed cell still ships *something* to the user.
470
- html = String(raw)
471
- }
472
- const sanitizeOpt = col.getSanitize()
473
- const finalHtml = sanitizeOpt === false
474
- ? html
475
- : await sanitizeHtml(html, sanitizeOpt === true ? undefined : sanitizeOpt)
476
- formatted[col.name] = finalHtml
477
- rich[col.name] = true
478
- }
479
- if (Object.keys(rich).length > 0) {
480
- out['_formatted'] = formatted
481
- out['_richtextCells'] = rich
482
- }
483
- }
484
-
485
- if (recordUrlFn !== undefined) {
486
- try {
487
- const url = recordUrlFn(row)
488
- if (url !== undefined) out['_recordUrl'] = url
489
- } catch {
490
- // Per-row recordUrl errors stay silent; rows without a URL
491
- // simply aren't clickable.
492
- }
493
- }
494
-
495
- if (recordClassesFn !== undefined) {
496
- try {
497
- const cls = recordClassesFn(row)
498
- if (cls) out['_recordClasses'] = cls
499
- } catch {
500
- // Silent — row falls back to default classes.
501
- }
502
- }
503
-
504
- if (bandingActive) {
505
- const raw = recordObj[activeGroup!.getColumn()]
506
- // Date-bucketed groups use `YYYY-MM-DD` as the stable-sort
507
- // key so all rows from the same day cluster together;
508
- // unparseable values bucket to '' (bottom).
509
- const value = activeGroup!.resolveKey(row)
510
- out['_groupValue'] = value
511
-
512
- // Per-row resolved title. User-supplied handler wins over
513
- // the date() default formatter. Throwing handler stays
514
- // silent — falls back to the raw `_groupValue`.
515
- const titleFn = activeGroup!.getTitleHandler()
516
- if (titleFn) {
517
- try {
518
- const t = titleFn(row)
519
- if (t !== undefined) out['_groupTitle'] = String(t)
520
- } catch { /* silent */ }
521
- } else if (activeGroup!.isDate() && value !== '') {
522
- out['_groupTitle'] = formatDateBucketTitle(raw)
523
- }
524
-
525
- const descFn = activeGroup!.getDescriptionHandler()
526
- if (descFn) {
527
- try {
528
- const d = descFn(row)
529
- if (d !== undefined) out['_groupDescription'] = String(d)
530
- } catch { /* silent */ }
531
- }
532
- }
533
-
534
- if (columnsWithRecordUrl.length > 0) {
535
- const colUrls: Record<string, string> = {}
536
- for (const col of columnsWithRecordUrl) {
537
- const fn = col.getRecordUrlHandler()
538
- if (!fn) continue
539
- try {
540
- const url = fn(recordObj)
541
- if (url !== undefined) colUrls[col.name] = url
542
- } catch {
543
- // Same as table-level: per-cell URL errors stay silent.
544
- }
545
- }
546
- out['_columnRecordUrls'] = colUrls
547
- }
548
-
549
- if (cardSchemaFn !== undefined) {
550
- try {
551
- const elements = await cardSchemaFn(row, finalCtx)
552
- const cardCtx: RenderContext = {
553
- mode: 'table',
554
- record: row,
555
- ...(user !== undefined && user !== null
556
- ? { user: user as NonNullable<RenderContext['user']> }
557
- : {}),
558
- }
559
- const children = await resolveSchema(elements, cardCtx)
560
- out['_cardChildren'] = children as ElementMeta[]
561
- } catch (err) {
562
- console.warn(`[pilotiq] cardSchema() threw for row in Table:`, err)
563
- out['_cardChildren'] = [] as ElementMeta[]
564
- }
565
- }
566
-
567
- if (canEditEditableColumns) {
568
- // ONE auth call per row regardless of editable column count
569
- // — same record, same answer. Failures or false → no edit
570
- // affordance for any column.
571
- let allowed: boolean
572
- try { allowed = await hooks!.canEdit!(user, recordObj) }
573
- catch { allowed = false }
574
- if (allowed) {
575
- const editableMap: Record<string, true> = {}
576
- const disabledMap: Record<string, true> = {}
577
- for (const col of editableColumns) {
578
- editableMap[col.name] = true
579
- if (col.isDisabledFor(recordObj)) disabledMap[col.name] = true
580
- }
581
- out['_cellEditable'] = editableMap
582
- if (Object.keys(disabledMap).length > 0) {
583
- out['_cellDisabled'] = disabledMap
584
- }
585
-
586
- // Per-row select options. Resolvers run in parallel so a
587
- // table with several SelectColumn resolvers doesn't stall
588
- // on serial awaits. A throwing resolver leaves the slot
589
- // unset — the renderer falls back to the column-level
590
- // static `selectOptions` (or an empty list) so one bad
591
- // row doesn't break the whole table.
592
- if (selectColumnsWithResolver.length > 0) {
593
- const resolvedEntries = await Promise.all(selectColumnsWithResolver.map(async (c) => {
594
- try {
595
- const opts = await c.resolve(recordObj)
596
- return [c.name, normalizeSelectOptions(opts)] as const
597
- } catch {
598
- return [c.name, undefined] as const
599
- }
600
- }))
601
- const optionsMap: Record<string, ColumnSelectOption[]> = {}
602
- for (const [name, opts] of resolvedEntries) {
603
- if (opts !== undefined) optionsMap[name] = opts
604
- }
605
- if (Object.keys(optionsMap).length > 0) {
606
- out['_cellSelectOptions'] = optionsMap
607
- }
608
- }
609
- }
610
- }
611
-
612
- return out
613
- }))
614
-
615
- // Group banding: stable-sort by _groupValue so all rows with the
616
- // same value cluster together. The user's sort still applies
617
- // (records came in pre-sorted from `records()`); we just shuffle
618
- // those clusters so groups are contiguous. Empty/null group values
619
- // bubble to the bottom under the empty-string key. When
620
- // `TableGroup.orderUsing(comparator)` is set on the active group,
621
- // the comparator overrides the default alphabetic order.
622
- const finalRows = groupColumn === undefined
623
- ? rows
624
- : sortRowsByGroupValue(
625
- rows as Array<Record<string, unknown>>,
626
- activeGroup?.getKeyComparator(),
627
- )
628
-
629
- // Per-column summaries. Compute over the rows we just rendered
630
- // (per-page only in v1; cross-page aggregation is deferred). Pulls
631
- // raw column values — summarizers coerce to numbers internally.
632
- const columnsWithSummaries = (table.getChildren() ?? [])
633
- .filter((c): c is Column => c instanceof Column && c.hasSummarizers())
634
- if (columnsWithSummaries.length > 0) {
635
- const summaries: Record<string, SummaryResult[]> = {}
636
- for (const col of columnsWithSummaries) {
637
- const values = (finalRows as Array<Record<string, unknown>>)
638
- .map(r => r[col.name])
639
- summaries[col.name] = col.getSummarizers().map(s => s.toResult(values))
640
- }
641
- table.withSummaries(summaries)
642
-
643
- // Per-group summaries — one summary set per `_groupValue`. Only
644
- // computed when a group is banded; otherwise the global tfoot
645
- // is the only summary row. Empty-group bucket ('') still gets
646
- // its own row when present (mirrors the bucket-to-bottom rule
647
- // already used for sorting). Drill-in mode (`bandingActive=false`)
648
- // intentionally skips per-group summaries — the visible rows are
649
- // already one bucket.
650
- if (bandingActive) {
651
- const buckets: Record<string, Array<Record<string, unknown>>> = {}
652
- for (const r of finalRows as Array<Record<string, unknown>>) {
653
- const key = String(r['_groupValue'] ?? '')
654
- if (!buckets[key]) buckets[key] = []
655
- buckets[key].push(r)
656
- }
657
- const groupSummaries: Record<string, Record<string, SummaryResult[]>> = {}
658
- for (const [groupValue, groupRows] of Object.entries(buckets)) {
659
- const perCol: Record<string, SummaryResult[]> = {}
660
- for (const col of columnsWithSummaries) {
661
- const values = groupRows.map(r => r[col.name])
662
- perCol[col.name] = col.getSummarizers().map(s => s.toResult(values))
663
- }
664
- groupSummaries[groupValue] = perCol
665
- }
666
- table.withGroupSummaries(groupSummaries)
667
- }
668
- }
669
-
670
- table.withRows(finalRows as unknown[], total)
671
- }
672
-
673
- // Mirror the resolved context back onto the table so the renderer can
674
- // produce sort/search/page links without re-parsing the URL. Drilled-in
675
- // requests reset the visible page to 1 — keep the mirror in sync so
676
- // pagination chrome doesn't claim page N on a single-bucket render.
677
- if (effectiveSort) table.withSort(effectiveSort.column, effectiveSort.direction)
678
- if (search !== undefined) table.withSearch(search)
679
- table.withPage(effectivePageWithDrill)
680
- if (pathname) table.withCurrentPath(pathname)
681
- }))
682
- }
683
-
684
- /**
685
- * Locate the currently-active list-page tab in the schema. The active tab
686
- * is the `ListTab` whose `_active` flag was set by
687
- * `pageData.resolveActiveTab` during the page-data pass — the tab strip
688
- * lives in the page schema (above the Table), so this just walks the
689
- * tree until it finds the matching tab. Returns `undefined` when the
690
- * page has no ListTabs (most non-resource pages).
691
- *
692
- * Uses a structural `getType() === 'listTab'` check rather than
693
- * `instanceof ListTab` for the same reason as `findForms / findActions`
694
- * — Vite SSR module-cache duplication can load the package through
695
- * two paths during a dev session and silently break `instanceof`.
696
- */
697
- /**
698
- * Stable-sort rows by their stamped `_groupValue` so all rows with the
699
- * same group are contiguous. Preserves original order within groups
700
- * (ties broken by original index). Empty / unstamped group values sort
701
- * to the end so the "ungrouped" band appears last.
702
- *
703
- * When a `comparator` is supplied (via `TableGroup.orderUsing(...)`) it
704
- * replaces the default alphabetic comparison between distinct keys. The
705
- * empty-bucket-last rule and within-group stability are preserved
706
- * around the user's comparator — those are structural, not policy.
707
- */
708
- function sortRowsByGroupValue(
709
- rows: ReadonlyArray<Record<string, unknown>>,
710
- comparator?: (a: string, b: string) => number,
711
- ): Array<Record<string, unknown>> {
712
- const indexed = rows.map((r, i) => ({ r, i, key: String(r['_groupValue'] ?? '') }))
713
- indexed.sort((a, b) => {
714
- const aEmpty = a.key === ''
715
- const bEmpty = b.key === ''
716
- if (aEmpty && !bEmpty) return 1
717
- if (!aEmpty && bEmpty) return -1
718
- if (a.key === b.key) return a.i - b.i
719
- if (comparator) {
720
- const cmp = comparator(a.key, b.key)
721
- if (cmp !== 0) return cmp
722
- // Comparator-tied → fall back to alphabetic so ties don't churn
723
- // depending on input order (otherwise pinning two keys to the
724
- // same rank would leave them in arrival order, which surprises
725
- // users when their `records()` reorders).
726
- }
727
- return a.key < b.key ? -1 : a.key > b.key ? 1 : 0
728
- })
729
- return indexed.map(({ r }) => r)
730
- }
731
-
732
- function findActiveTab(elements: ReadonlyArray<Element>): ListTab | undefined {
733
- const walk = (els: ReadonlyArray<Element>): ListTab | undefined => {
734
- for (const el of els) {
735
- if (el.getType() === 'listTab' && (el as ListTab).isActive()) return el as ListTab
736
- const children = el.getChildren()
737
- if (children) {
738
- const found = walk(children)
739
- if (found) return found
740
- }
741
- }
742
- return undefined
743
- }
744
- return walk(elements)
745
- }