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