@pilotiq/pilotiq 0.23.1 → 0.24.2

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 (500) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/dist/actions/exportFactory.d.ts +10 -0
  15. package/dist/actions/exportFactory.d.ts.map +1 -1
  16. package/dist/actions/exportFactory.js +10 -0
  17. package/dist/actions/exportFactory.js.map +1 -1
  18. package/dist/react/CollabRoomContext.d.ts +5 -5
  19. package/dist/react/index.d.ts +0 -1
  20. package/dist/react/index.d.ts.map +1 -1
  21. package/dist/react/index.js +0 -1
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/routes/helpers.d.ts.map +1 -1
  24. package/dist/routes/helpers.js +6 -2
  25. package/dist/routes/helpers.js.map +1 -1
  26. package/dist/routes/relations.d.ts.map +1 -1
  27. package/dist/routes/relations.js +12 -0
  28. package/dist/routes/relations.js.map +1 -1
  29. package/package.json +6 -1
  30. package/.turbo/turbo-build.log +0 -8
  31. package/CLAUDE.md +0 -265
  32. package/dist/react/useCollabSeed.d.ts +0 -23
  33. package/dist/react/useCollabSeed.d.ts.map +0 -1
  34. package/dist/react/useCollabSeed.js +0 -82
  35. package/dist/react/useCollabSeed.js.map +0 -1
  36. package/src/Cluster.test.ts +0 -283
  37. package/src/Cluster.ts +0 -83
  38. package/src/Column.test.ts +0 -199
  39. package/src/Column.ts +0 -710
  40. package/src/Global.test.ts +0 -367
  41. package/src/Global.ts +0 -169
  42. package/src/Page.test.ts +0 -114
  43. package/src/Page.ts +0 -208
  44. package/src/Pilotiq.perf.test.ts +0 -252
  45. package/src/Pilotiq.test.ts +0 -129
  46. package/src/Pilotiq.ts +0 -1158
  47. package/src/PilotiqRegistry.ts +0 -36
  48. package/src/PilotiqServiceProvider.ts +0 -121
  49. package/src/RelationManager.test.ts +0 -400
  50. package/src/RelationManager.ts +0 -527
  51. package/src/RenderHook.test.ts +0 -252
  52. package/src/RenderHook.ts +0 -242
  53. package/src/Resource.test.ts +0 -284
  54. package/src/Resource.ts +0 -526
  55. package/src/RightPanel.test.ts +0 -202
  56. package/src/RightPanel.ts +0 -132
  57. package/src/Tab.test.ts +0 -91
  58. package/src/Tab.ts +0 -156
  59. package/src/UserMenuItem.ts +0 -145
  60. package/src/actions/Action.test.ts +0 -2526
  61. package/src/actions/Action.ts +0 -1515
  62. package/src/actions/ActionGroup.test.ts +0 -112
  63. package/src/actions/ActionGroup.ts +0 -173
  64. package/src/actions/attachFactory.ts +0 -172
  65. package/src/actions/bulkFactories.ts +0 -168
  66. package/src/actions/crudFactories.ts +0 -220
  67. package/src/actions/exportFactory.ts +0 -215
  68. package/src/actions/factoryHelpers.ts +0 -177
  69. package/src/actions/importFactory.ts +0 -243
  70. package/src/actions/index.ts +0 -17
  71. package/src/actions/m2mFactories.ts +0 -193
  72. package/src/actions/relationFactories.ts +0 -372
  73. package/src/applyPageHooks.test.ts +0 -463
  74. package/src/applyPageHooks.ts +0 -330
  75. package/src/authorization.test.ts +0 -483
  76. package/src/breadcrumbs.test.ts +0 -238
  77. package/src/cells/coerce.test.ts +0 -85
  78. package/src/cells/coerce.ts +0 -84
  79. package/src/clusterPaths.ts +0 -35
  80. package/src/columns/BadgeColumn.test.ts +0 -54
  81. package/src/columns/BadgeColumn.ts +0 -32
  82. package/src/columns/BooleanColumn.test.ts +0 -41
  83. package/src/columns/BooleanColumn.ts +0 -18
  84. package/src/columns/ColorColumn.test.ts +0 -37
  85. package/src/columns/ColorColumn.ts +0 -38
  86. package/src/columns/IconColumn.test.ts +0 -54
  87. package/src/columns/IconColumn.ts +0 -37
  88. package/src/columns/ImageColumn.test.ts +0 -41
  89. package/src/columns/ImageColumn.ts +0 -28
  90. package/src/columns/SelectColumn.ts +0 -98
  91. package/src/columns/TextColumn.test.ts +0 -190
  92. package/src/columns/TextColumn.ts +0 -20
  93. package/src/columns/TextInputColumn.ts +0 -68
  94. package/src/columns/ToggleColumn.ts +0 -46
  95. package/src/columns/editableColumns.test.ts +0 -238
  96. package/src/columns/index.ts +0 -9
  97. package/src/defaultGlobalPages.ts +0 -95
  98. package/src/defaultPages.test.ts +0 -634
  99. package/src/defaultPages.ts +0 -617
  100. package/src/defaultViewPage.test.ts +0 -147
  101. package/src/elements/Form.test.ts +0 -223
  102. package/src/elements/Form.ts +0 -416
  103. package/src/elements/ListTabs.ts +0 -28
  104. package/src/elements/Table.test.ts +0 -422
  105. package/src/elements/Table.ts +0 -850
  106. package/src/elements/TableGroup.test.ts +0 -260
  107. package/src/elements/TableGroup.ts +0 -334
  108. package/src/elements/dispatchAction.test.ts +0 -463
  109. package/src/elements/dispatchAction.ts +0 -355
  110. package/src/elements/dispatchForm.test.ts +0 -477
  111. package/src/elements/dispatchForm.ts +0 -1993
  112. package/src/elements/dispatchTable.test.ts +0 -1514
  113. package/src/elements/dispatchTable.ts +0 -745
  114. package/src/elements/index.ts +0 -21
  115. package/src/entries/BadgeEntry.ts +0 -39
  116. package/src/entries/CodeEntry.test.ts +0 -40
  117. package/src/entries/CodeEntry.ts +0 -52
  118. package/src/entries/ColorEntry.ts +0 -63
  119. package/src/entries/ComponentEntry.test.ts +0 -173
  120. package/src/entries/ComponentEntry.ts +0 -95
  121. package/src/entries/Entry.ts +0 -304
  122. package/src/entries/IconEntry.ts +0 -49
  123. package/src/entries/ImageEntry.ts +0 -61
  124. package/src/entries/KeyValueEntry.ts +0 -47
  125. package/src/entries/RepeatableEntry.test.ts +0 -239
  126. package/src/entries/RepeatableEntry.ts +0 -173
  127. package/src/entries/TextEntry.test.ts +0 -394
  128. package/src/entries/TextEntry.ts +0 -60
  129. package/src/entries/index.ts +0 -12
  130. package/src/entries/leaves.test.ts +0 -306
  131. package/src/entries/registry.ts +0 -54
  132. package/src/fields/BuilderField.test.ts +0 -1188
  133. package/src/fields/BuilderField.ts +0 -605
  134. package/src/fields/BuilderRelationship.test.ts +0 -811
  135. package/src/fields/CheckboxField.test.ts +0 -44
  136. package/src/fields/CheckboxField.ts +0 -27
  137. package/src/fields/CheckboxListField.test.ts +0 -99
  138. package/src/fields/CheckboxListField.ts +0 -66
  139. package/src/fields/ColorPickerField.test.ts +0 -33
  140. package/src/fields/ColorPickerField.ts +0 -25
  141. package/src/fields/DateField.ts +0 -54
  142. package/src/fields/DateTimeField.test.ts +0 -55
  143. package/src/fields/EmailField.ts +0 -16
  144. package/src/fields/Field.test.ts +0 -654
  145. package/src/fields/Field.ts +0 -817
  146. package/src/fields/FileUploadField.test.ts +0 -143
  147. package/src/fields/FileUploadField.ts +0 -159
  148. package/src/fields/HiddenField.test.ts +0 -27
  149. package/src/fields/HiddenField.ts +0 -28
  150. package/src/fields/KeyValueField.test.ts +0 -105
  151. package/src/fields/KeyValueField.ts +0 -55
  152. package/src/fields/MarkdownField.test.ts +0 -167
  153. package/src/fields/MarkdownField.ts +0 -162
  154. package/src/fields/NumberField.ts +0 -33
  155. package/src/fields/RadioField.test.ts +0 -94
  156. package/src/fields/RadioField.ts +0 -67
  157. package/src/fields/RepeaterField.test.ts +0 -1806
  158. package/src/fields/RepeaterField.ts +0 -939
  159. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  160. package/src/fields/RepeaterSimple.test.ts +0 -248
  161. package/src/fields/RowButton.test.ts +0 -219
  162. package/src/fields/RowButton.ts +0 -135
  163. package/src/fields/SelectField.test.ts +0 -192
  164. package/src/fields/SelectField.ts +0 -235
  165. package/src/fields/SliderField.test.ts +0 -50
  166. package/src/fields/SliderField.ts +0 -53
  167. package/src/fields/SlugField.ts +0 -24
  168. package/src/fields/TagsInputField.test.ts +0 -154
  169. package/src/fields/TagsInputField.ts +0 -133
  170. package/src/fields/TextField.test.ts +0 -213
  171. package/src/fields/TextField.ts +0 -177
  172. package/src/fields/TextareaField.test.ts +0 -58
  173. package/src/fields/TextareaField.ts +0 -59
  174. package/src/fields/ToggleButtonsField.test.ts +0 -106
  175. package/src/fields/ToggleButtonsField.ts +0 -59
  176. package/src/fields/ToggleField.ts +0 -16
  177. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  178. package/src/fields/optionsResolver.ts +0 -95
  179. package/src/fields/resolveField.ts +0 -28
  180. package/src/filters/BooleanFilter.ts +0 -35
  181. package/src/filters/DateRangeFilter.test.ts +0 -194
  182. package/src/filters/DateRangeFilter.ts +0 -148
  183. package/src/filters/Filter.test.ts +0 -268
  184. package/src/filters/Filter.ts +0 -184
  185. package/src/filters/FormFilter.test.ts +0 -238
  186. package/src/filters/FormFilter.ts +0 -215
  187. package/src/filters/MultiSelectFilter.test.ts +0 -119
  188. package/src/filters/MultiSelectFilter.ts +0 -78
  189. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  190. package/src/filters/QueryBuilderFilter.ts +0 -398
  191. package/src/filters/SelectFilter.ts +0 -46
  192. package/src/filters/TernaryFilter.test.ts +0 -160
  193. package/src/filters/TernaryFilter.ts +0 -72
  194. package/src/filters/TrashedFilter.test.ts +0 -149
  195. package/src/filters/TrashedFilter.ts +0 -55
  196. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  197. package/src/filters/queryBuilder/Constraint.ts +0 -115
  198. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  199. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  200. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  201. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  202. package/src/filters/queryBuilder/index.ts +0 -12
  203. package/src/icons/index.ts +0 -2
  204. package/src/icons/lucide.ts +0 -204
  205. package/src/icons/registry.test.ts +0 -56
  206. package/src/icons/registry.ts +0 -41
  207. package/src/icons/types.ts +0 -47
  208. package/src/index.ts +0 -525
  209. package/src/io/csv.test.ts +0 -142
  210. package/src/io/csv.ts +0 -170
  211. package/src/nestedRelationManagerData.test.ts +0 -547
  212. package/src/notifications/Notification.test.ts +0 -210
  213. package/src/notifications/Notification.ts +0 -354
  214. package/src/notifications/broadcast.test.ts +0 -110
  215. package/src/notifications/broadcast.ts +0 -95
  216. package/src/notifications/database.test.ts +0 -383
  217. package/src/notifications/database.ts +0 -398
  218. package/src/notifications/databaseNotifications.test.ts +0 -187
  219. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  220. package/src/notifications/dispatchNotificationAction.ts +0 -142
  221. package/src/notifications/flash.test.ts +0 -89
  222. package/src/notifications/flash.ts +0 -71
  223. package/src/notifications/index.ts +0 -45
  224. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  225. package/src/notifications/registerBroadcastAuth.ts +0 -100
  226. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  227. package/src/notifications/resolveSavedNotification.ts +0 -59
  228. package/src/notifications/types.ts +0 -93
  229. package/src/orm/m2mAccessor.ts +0 -66
  230. package/src/orm/modelDefaults.test.ts +0 -633
  231. package/src/orm/modelDefaults.ts +0 -666
  232. package/src/pageData/breadcrumbs.ts +0 -288
  233. package/src/pageData/forms.ts +0 -578
  234. package/src/pageData/helpers.ts +0 -857
  235. package/src/pageData/misc.ts +0 -347
  236. package/src/pageData/navigation.ts +0 -842
  237. package/src/pageData/relationPages.ts +0 -1248
  238. package/src/pageData/relationTabs.ts +0 -286
  239. package/src/pageData/resourcePages.ts +0 -609
  240. package/src/pageData.test.ts +0 -1545
  241. package/src/pageData.ts +0 -341
  242. package/src/plugins/index.ts +0 -8
  243. package/src/plugins/themeEditor.test.ts +0 -36
  244. package/src/plugins/themeEditor.ts +0 -45
  245. package/src/react/AppShell.tsx +0 -251
  246. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  247. package/src/react/CollabRoomContext.ts +0 -98
  248. package/src/react/CollabTextRendererRegistry.ts +0 -102
  249. package/src/react/CommandPalette.tsx +0 -375
  250. package/src/react/CurrentUserContext.tsx +0 -50
  251. package/src/react/CustomPageWrapperGate.tsx +0 -69
  252. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  253. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  254. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  255. package/src/react/FieldPresenceRegistry.ts +0 -46
  256. package/src/react/FormCollabBindingRegistry.ts +0 -242
  257. package/src/react/FormStateContext.tsx +0 -591
  258. package/src/react/HeadHooks.tsx +0 -126
  259. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  260. package/src/react/MarkdownEditorRegistry.ts +0 -107
  261. package/src/react/NotificationActionStrip.tsx +0 -263
  262. package/src/react/NotificationBell.tsx +0 -426
  263. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  264. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  265. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  266. package/src/react/PendingSuggestionsContext.tsx +0 -172
  267. package/src/react/RecordWrapperGate.tsx +0 -58
  268. package/src/react/RecordWrapperRegistry.ts +0 -39
  269. package/src/react/RenderHookSlot.tsx +0 -32
  270. package/src/react/RightSidebar.tsx +0 -257
  271. package/src/react/RightSidebarContext.tsx +0 -234
  272. package/src/react/RightSidebarTrigger.tsx +0 -53
  273. package/src/react/RowCoordsContext.tsx +0 -23
  274. package/src/react/SchemaRenderer.tsx +0 -549
  275. package/src/react/SearchTrigger.tsx +0 -46
  276. package/src/react/ThemeProvider.tsx +0 -93
  277. package/src/react/ThemeSettingsPage.tsx +0 -579
  278. package/src/react/ThemeToggle.tsx +0 -20
  279. package/src/react/Toaster.tsx +0 -158
  280. package/src/react/UserMenu.tsx +0 -196
  281. package/src/react/WidgetDataContext.tsx +0 -157
  282. package/src/react/cells/EditableCell.tsx +0 -389
  283. package/src/react/component-slots.test.ts +0 -103
  284. package/src/react/component-slots.ts +0 -116
  285. package/src/react/fieldJsHandler.test.ts +0 -166
  286. package/src/react/fieldJsHandler.ts +0 -79
  287. package/src/react/fields/BuilderInput.tsx +0 -1078
  288. package/src/react/fields/CheckboxInput.tsx +0 -39
  289. package/src/react/fields/CheckboxListInput.tsx +0 -102
  290. package/src/react/fields/ColorInput.tsx +0 -71
  291. package/src/react/fields/DateFieldInput.tsx +0 -70
  292. package/src/react/fields/DateTimeInput.tsx +0 -62
  293. package/src/react/fields/FieldShell.tsx +0 -348
  294. package/src/react/fields/FileUploadInput.tsx +0 -639
  295. package/src/react/fields/HiddenInput.tsx +0 -17
  296. package/src/react/fields/KeyValueInput.tsx +0 -230
  297. package/src/react/fields/MarkdownInput.tsx +0 -560
  298. package/src/react/fields/RadioInput.tsx +0 -81
  299. package/src/react/fields/RepeaterInput.test.ts +0 -116
  300. package/src/react/fields/RepeaterInput.tsx +0 -1420
  301. package/src/react/fields/SelectFieldInput.tsx +0 -280
  302. package/src/react/fields/SliderInput.tsx +0 -81
  303. package/src/react/fields/TagsInput.tsx +0 -283
  304. package/src/react/fields/TextLikeInput.tsx +0 -256
  305. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  306. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  307. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  308. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  309. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  310. package/src/react/fields/repeaterReconcile.ts +0 -104
  311. package/src/react/fields/rowChromeButton.tsx +0 -336
  312. package/src/react/fields/rowState.ts +0 -106
  313. package/src/react/fields/syncRowGates.test.ts +0 -202
  314. package/src/react/fields/syncRowGates.ts +0 -66
  315. package/src/react/fields/textInputControls.tsx +0 -238
  316. package/src/react/fields/useRowReorderDnd.ts +0 -78
  317. package/src/react/formStateHelpers.test.ts +0 -508
  318. package/src/react/formStateHelpers.ts +0 -381
  319. package/src/react/hooks/use-mobile.ts +0 -19
  320. package/src/react/icon-context.tsx +0 -60
  321. package/src/react/index.ts +0 -195
  322. package/src/react/layouts/SidebarLayout.tsx +0 -250
  323. package/src/react/layouts/TopbarLayout.tsx +0 -258
  324. package/src/react/navigate.tsx +0 -37
  325. package/src/react/onProviderSynced.test.ts +0 -90
  326. package/src/react/parseRecordEditUrl.test.ts +0 -122
  327. package/src/react/parseRecordEditUrl.ts +0 -94
  328. package/src/react/persistedState.ts +0 -40
  329. package/src/react/registry.ts +0 -48
  330. package/src/react/right-panel-registry.tsx +0 -47
  331. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  332. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  333. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  334. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  335. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  336. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  337. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  338. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  339. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  340. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  341. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  342. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  343. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  344. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  345. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  346. package/src/react/schemaRenderer/constants.ts +0 -50
  347. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  348. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  349. package/src/react/schemaRenderer/helpers.tsx +0 -81
  350. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  351. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  352. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  353. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  354. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  355. package/src/react/schemaRenderer/table/links.tsx +0 -112
  356. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  357. package/src/react/schemaRenderer/table/url.tsx +0 -143
  358. package/src/react/theme-preview/apply.ts +0 -99
  359. package/src/react/theme-preview/build-html.ts +0 -436
  360. package/src/react/ui/button.tsx +0 -51
  361. package/src/react/ui/calendar.tsx +0 -67
  362. package/src/react/ui/checkbox.tsx +0 -29
  363. package/src/react/ui/dialog.tsx +0 -108
  364. package/src/react/ui/dropdown-menu.tsx +0 -97
  365. package/src/react/ui/input.tsx +0 -20
  366. package/src/react/ui/label.tsx +0 -21
  367. package/src/react/ui/popover.tsx +0 -50
  368. package/src/react/ui/select.tsx +0 -169
  369. package/src/react/ui/separator.tsx +0 -25
  370. package/src/react/ui/sheet.tsx +0 -136
  371. package/src/react/ui/sidebar.tsx +0 -723
  372. package/src/react/ui/skeleton.tsx +0 -13
  373. package/src/react/ui/slider.tsx +0 -34
  374. package/src/react/ui/switch.tsx +0 -28
  375. package/src/react/ui/table.tsx +0 -105
  376. package/src/react/ui/tabs.tsx +0 -63
  377. package/src/react/ui/textarea.tsx +0 -18
  378. package/src/react/ui/tooltip.tsx +0 -64
  379. package/src/react/useCollabSeed.ts +0 -86
  380. package/src/react/useResizableWidth.ts +0 -139
  381. package/src/react/utils.ts +0 -6
  382. package/src/react/widgetRegistry.test.ts +0 -43
  383. package/src/react/widgetRegistry.ts +0 -50
  384. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  385. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  386. package/src/react/widgets/ViewRenderer.tsx +0 -71
  387. package/src/relationManagerData.test.ts +0 -1595
  388. package/src/richtext/index.ts +0 -8
  389. package/src/richtext/registry.ts +0 -89
  390. package/src/routes/globals.ts +0 -148
  391. package/src/routes/guard.test.ts +0 -325
  392. package/src/routes/helpers.ts +0 -700
  393. package/src/routes/pages.ts +0 -175
  394. package/src/routes/panel.ts +0 -204
  395. package/src/routes/relations.ts +0 -1227
  396. package/src/routes/resources.ts +0 -781
  397. package/src/routes/theme.ts +0 -91
  398. package/src/routes-nested-relations.test.ts +0 -676
  399. package/src/routes-relations.test.ts +0 -972
  400. package/src/routes.test.ts +0 -2027
  401. package/src/routes.ts +0 -303
  402. package/src/schema/Alert.test.ts +0 -109
  403. package/src/schema/Alert.ts +0 -131
  404. package/src/schema/Block.ts +0 -169
  405. package/src/schema/Breadcrumbs.ts +0 -40
  406. package/src/schema/Card.ts +0 -35
  407. package/src/schema/Divider.ts +0 -20
  408. package/src/schema/Element.ts +0 -219
  409. package/src/schema/EmptyState.test.ts +0 -37
  410. package/src/schema/EmptyState.ts +0 -63
  411. package/src/schema/Fieldset.ts +0 -43
  412. package/src/schema/Grid.ts +0 -43
  413. package/src/schema/Group.ts +0 -30
  414. package/src/schema/Heading.ts +0 -39
  415. package/src/schema/Html.ts +0 -67
  416. package/src/schema/Icon.ts +0 -54
  417. package/src/schema/Image.ts +0 -57
  418. package/src/schema/LinkTag.ts +0 -41
  419. package/src/schema/Markdown.ts +0 -85
  420. package/src/schema/MetaTag.ts +0 -41
  421. package/src/schema/RelationTabs.ts +0 -71
  422. package/src/schema/ScriptTag.ts +0 -55
  423. package/src/schema/Section.ts +0 -160
  424. package/src/schema/ServerDataElement.test.ts +0 -140
  425. package/src/schema/ServerDataElement.ts +0 -156
  426. package/src/schema/SlotComponent.test.ts +0 -77
  427. package/src/schema/SlotComponent.ts +0 -71
  428. package/src/schema/Split.ts +0 -50
  429. package/src/schema/Stat.test.ts +0 -118
  430. package/src/schema/Stat.ts +0 -154
  431. package/src/schema/StatsOverview.test.ts +0 -141
  432. package/src/schema/StatsOverview.ts +0 -119
  433. package/src/schema/StyleTag.ts +0 -35
  434. package/src/schema/TableWidget.test.ts +0 -297
  435. package/src/schema/TableWidget.ts +0 -289
  436. package/src/schema/Tabs.ts +0 -79
  437. package/src/schema/Text.ts +0 -58
  438. package/src/schema/UnorderedList.ts +0 -49
  439. package/src/schema/View.test.ts +0 -111
  440. package/src/schema/View.ts +0 -127
  441. package/src/schema/Wizard.ts +0 -220
  442. package/src/schema/containers.test.ts +0 -564
  443. package/src/schema/headTags.test.ts +0 -134
  444. package/src/schema/index.ts +0 -40
  445. package/src/schema/primes.test.ts +0 -269
  446. package/src/schema/resolveSchema.test.ts +0 -379
  447. package/src/schema/resolveSchema.ts +0 -917
  448. package/src/schema/sanitize.ts +0 -58
  449. package/src/search.test.ts +0 -446
  450. package/src/search.ts +0 -178
  451. package/src/sessionFilters.test.ts +0 -375
  452. package/src/sessionFilters.ts +0 -143
  453. package/src/slot-components/index.ts +0 -10
  454. package/src/slot-components/registry.ts +0 -56
  455. package/src/styles/file-upload.css +0 -13
  456. package/src/summarizers/Summarizer.test.ts +0 -84
  457. package/src/summarizers/Summarizer.ts +0 -123
  458. package/src/summarizers/index.ts +0 -11
  459. package/src/theme/base-colors.ts +0 -68
  460. package/src/theme/chart-colors.ts +0 -50
  461. package/src/theme/colors.ts +0 -447
  462. package/src/theme/generate-css.test.ts +0 -139
  463. package/src/theme/generate-css.ts +0 -44
  464. package/src/theme/generate-scale.test.ts +0 -106
  465. package/src/theme/generate-scale.ts +0 -97
  466. package/src/theme/icon-map.ts +0 -42
  467. package/src/theme/index.ts +0 -34
  468. package/src/theme/migrate.test.ts +0 -178
  469. package/src/theme/migrate.ts +0 -81
  470. package/src/theme/presets.ts +0 -135
  471. package/src/theme/radius.ts +0 -18
  472. package/src/theme/resolve.test.ts +0 -238
  473. package/src/theme/resolve.ts +0 -96
  474. package/src/theme/spacing.ts +0 -18
  475. package/src/theme/storage.test.ts +0 -126
  476. package/src/theme/storage.ts +0 -106
  477. package/src/theme/theme-colors.ts +0 -88
  478. package/src/theme/types.ts +0 -125
  479. package/src/uploads/UploadAdapter.ts +0 -35
  480. package/src/uploads/index.ts +0 -2
  481. package/src/uploads/localUpload.test.ts +0 -70
  482. package/src/uploads/localUpload.ts +0 -84
  483. package/src/validation/Validator.ts +0 -49
  484. package/src/validation/index.ts +0 -28
  485. package/src/validation/rules.ts +0 -78
  486. package/src/validation/runValidators.ts +0 -435
  487. package/src/validation/uniqueValidator.test.ts +0 -196
  488. package/src/validation/uniqueValidator.ts +0 -133
  489. package/src/validation/validators.test.ts +0 -268
  490. package/src/vite.test.ts +0 -184
  491. package/src/vite.ts +0 -787
  492. package/src/widgets/index.ts +0 -10
  493. package/src/widgets/registry.ts +0 -45
  494. package/src/widgets.test.ts +0 -592
  495. package/tsconfig.build.json +0 -11
  496. package/tsconfig.json +0 -4
  497. package/tsconfig.test.json +0 -10
  498. package/views/react/Dashboard.tsx +0 -27
  499. package/views/react/Resources/Form.tsx +0 -102
  500. 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
- }