@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,850 +0,0 @@
1
- import { Element, type ElementMeta } from '../schema/Element.js'
2
- import { Column } from '../Column.js'
3
- import { Action } from '../actions/Action.js'
4
- import { ActionGroup } from '../actions/ActionGroup.js'
5
- import { Filter } from '../filters/Filter.js'
6
- import type { SummaryResult } from '../summarizers/Summarizer.js'
7
- import { TableGroup, type TableGroupMeta } from './TableGroup.js'
8
-
9
- /** Either a plain `Action` or an `ActionGroup` (a labelled dropdown of
10
- * actions). Both can sit in any of the table action slots — the slot
11
- * stamps the placement automatically on whichever shape arrives. */
12
- type ActionOrGroup = Action | ActionGroup
13
-
14
- export type SortDirection = 'asc' | 'desc'
15
-
16
- export interface TableContext<R = unknown> {
17
- request?: unknown
18
- search?: string
19
- sort?: { column: string; direction: SortDirection }
20
- page?: number
21
- perPage?: number
22
- records?: R[]
23
- /** Active filter values keyed by filter name (e.g. `{ status: 'published' }`).
24
- * Empty / unsupplied filters are absent. */
25
- filters?: Record<string, string>
26
- /** Active list-page tab name (e.g. `'drafts'`). Set by
27
- * `pageData.resourceIndexData` from `?tab=` before records run. User-
28
- * supplied `Table.records(fn)` handlers can branch on this for custom
29
- * narrowing; the model adapter consults `tabQuery` instead. */
30
- tab?: string
31
- /** Active list-page tab's `modifyQuery` chain — applied alongside
32
- * filter `where` clauses in `modelTableRecords`. Set by the framework;
33
- * users configure it via `ListTab.modifyQuery(fn)`. */
34
- tabQuery?: (q: import('../orm/modelDefaults.js').ModelQuery) => import('../orm/modelDefaults.js').ModelQuery
35
- /** Drill-in scope when the user clicked a group heading. Carries the
36
- * resolved `TableGroup` instance + the bucket key (the same value
37
- * `getKeyFromRecordUsing` would produce). Set by `loadTableRecords`
38
- * after reconciling `?<prefix>groupKey=`. The model adapter calls
39
- * `group.resolveScoper()(q, key)` after filters but before pagination;
40
- * user-supplied `Table.records(fn)` handlers can branch on this for
41
- * cross-table joins or non-default narrowing. */
42
- groupScope?: {
43
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
- group: import('./TableGroup.js').TableGroup<any>
45
- key: string
46
- }
47
- /** Whatever `Pilotiq.user(req => …)` returned for the current request.
48
- * Forwarded into `Resource.query(ctx)` by `modelTableRecords` so user-
49
- * installed scopes (tenant filters, etc.) see the same opaque user
50
- * shape that authorization predicates do. Absent on the dispatcher's
51
- * input ctx when no user resolver is configured. */
52
- user?: unknown
53
- [key: string]: unknown
54
- }
55
-
56
- export type TableQueryHandler<Q = unknown> = (
57
- query: Q,
58
- ctx: TableContext,
59
- ) => Q | Promise<Q>
60
-
61
- /**
62
- * User-supplied row loader. Returns the records to render plus an optional
63
- * `total` for pagination. When `total` is omitted the framework treats
64
- * `rows.length` as the total.
65
- */
66
- export interface TableRecordsResult<R = unknown> {
67
- rows: R[]
68
- total?: number
69
- }
70
-
71
- export type TableRecordsHandler<R = unknown> = (
72
- ctx: TableContext<R>,
73
- ) => TableRecordsResult<R> | R[] | Promise<TableRecordsResult<R> | R[]>
74
-
75
- export interface TableEmptyState {
76
- heading?: string
77
- description?: string
78
- icon?: string
79
- }
80
-
81
- /**
82
- * Per-row URL function. Returns the destination URL for clicks on a
83
- * row's data cells — each data column wraps its content in a real
84
- * `<a href>` (Filament-style), so right-click / cmd-click / middle-click
85
- * "open in new tab" all work natively. Plain left-clicks are intercepted
86
- * for SPA navigation. Return `undefined` for rows that shouldn't be
87
- * clickable. Action and bulk-select cells are never wrapped.
88
- *
89
- * Per-column overrides: `Column.recordUrl(fn)` swaps in a different URL
90
- * for that column's cell, and `Column.recordUrl(false)` opts a column
91
- * out entirely.
92
- */
93
- export type RecordUrlHandler<R = unknown> = (record: R) => string | undefined
94
-
95
- /**
96
- * Per-row CSS class function. Returns extra Tailwind / CSS class names
97
- * appended to that row's `<tr>`. Useful for status-driven row tinting
98
- * ("destructive" when overdue, "warning" when stale). Result is appended
99
- * after the framework's own row classes (striped, cursor-pointer); user
100
- * classes win on equal specificity. Throwing or returning falsy stays
101
- * silent — the row just renders without extras.
102
- */
103
- export type RecordClassesHandler<R = unknown> = (record: R) => string | undefined
104
-
105
- /**
106
- * Per-row card content function. Receives the record + the current
107
- * `TableContext` and returns an `Element[]` rendered inside a card in
108
- * `contentLayout('cards')` mode. Resolved via `resolveSchema` per-row in
109
- * `loadTableRecords`; the result is stamped onto `row._cardChildren`.
110
- *
111
- * Typical content: `Image`, `Heading`, `Text`, `Icon`, `Badge`-style
112
- * `Entry` primitives, plus layout primitives like `Group / Split / Grid`.
113
- * Display-only — `Form` / `Field` / `Filter` / `Action` inside the card
114
- * schema is unsupported in v1.
115
- */
116
- export type CardSchemaHandler<R = unknown> = (
117
- record: R,
118
- ctx: TableContext<R>,
119
- ) => Element[] | Promise<Element[]>
120
-
121
- /**
122
- * Responsive grid column counts for `contentLayout('cards')`. Each
123
- * breakpoint maps to its Tailwind container query (`@sm` = ≥40rem etc.);
124
- * `default` is the base (no media query). Unspecified breakpoints inherit
125
- * the next-smaller. v1 caps each value at 12 to match the grid's column
126
- * limits — values outside `[1, 12]` clamp.
127
- */
128
- export interface CardsPerRow {
129
- default?: number
130
- sm?: number
131
- md?: number
132
- lg?: number
133
- xl?: number
134
- '2xl'?: number
135
- }
136
-
137
- /**
138
- * Table content-layout mode. `'table'` (default) renders the classic
139
- * `<thead>` + `<tbody>` HTML table. `'cards'` hides the header row and
140
- * renders each row as a card in a CSS grid. Columns still drive search /
141
- * sort / filter / group / summarize semantics in cards mode; only the
142
- * row-level rendering differs.
143
- */
144
- export type ContentLayout = 'table' | 'cards'
145
-
146
- /**
147
- * Where filters render relative to the table.
148
- * - `'modal'` (default) — chrome-only popover above the table, opened by
149
- * the toolbar Filters button.
150
- * - `'above-content'` — inline strip rendered between the header bar and
151
- * the table; every filter widget is always visible.
152
- * - `'above-content-collapsible'` — same strip, hidden behind the toolbar
153
- * Filters button. Open / closed state persists per table path.
154
- * - `'below-content'` — inline strip rendered after the table (under the
155
- * pagination row).
156
- *
157
- * Filament v5's sidebar positions (`BeforeContent` / `AfterContent`)
158
- * reshape the page rather than the table chrome and are deferred until a
159
- * consumer asks.
160
- */
161
- export type FiltersLayout =
162
- | 'modal'
163
- | 'above-content'
164
- | 'above-content-collapsible'
165
- | 'below-content'
166
-
167
- export interface TableMeta extends ElementMeta {
168
- type: 'table'
169
- defaultSort?: { column: string; direction: SortDirection }
170
- perPage?: number
171
- searchable: boolean
172
-
173
- // Top-bar chrome
174
- heading?: string
175
- description?: string
176
- striped?: boolean
177
- emptyState?: TableEmptyState
178
- /**
179
- * Distinct empty-state for the "filter/search active but no rows match"
180
- * case. When set AND a search query or one or more URL filter keys are
181
- * present, the renderer picks this shape over `emptyState`. When unset,
182
- * the renderer falls back to `emptyState` (or the framework defaults
183
- * — "No matching records" with a generic clear-filters hint).
184
- */
185
- filteredEmptyState?: TableEmptyState
186
-
187
- /**
188
- * Per-row URL stamped onto each row's data under the reserved
189
- * `_recordUrl` key (alongside the existing `_visibleActions` /
190
- * `_disabledActions` / `_formatted` keys). The renderer reads from
191
- * the row, not the table meta — `RecordUrlHandler` is server-side only.
192
- */
193
- recordUrl?: true
194
-
195
- /**
196
- * Server-side per-row CSS marker — same convention as `recordUrl`.
197
- * Each row's `_recordClasses` carries the resolved string; this flag
198
- * is just a hint for the renderer to look for it.
199
- */
200
- recordClasses?: true
201
-
202
- /**
203
- * Auto-refresh interval in seconds. The client renderer kicks off a
204
- * `setInterval` that re-fetches the current URL via the SPA navigator
205
- * — pagination / sort / filter state is preserved because we re-visit
206
- * the same `pathname + search`. Hidden tabs pause to avoid hammering
207
- * the server in the background; resume on visibility change. Unset =
208
- * no polling.
209
- */
210
- pollInterval?: number
211
-
212
- /** The currently active group — the column rows are banded by. Set
213
- * each request from either `?group=` or `defaultGroup(...)`. The
214
- * renderer emits a heading row whenever `_groupValue` changes between
215
- * adjacent rows. Always a column name; the rich metadata (label /
216
- * collapsibility / etc.) lives on `groups` below. */
217
- defaultGroup?: string
218
-
219
- /** Group selector options. When 2+ groups are registered the renderer
220
- * mounts a "Group by" dropdown above the table; the user's selection
221
- * round-trips via `?group=`. Empty / absent means the bare-column form
222
- * (`Table.defaultGroup('col')` only). */
223
- groups?: TableGroupMeta[]
224
-
225
- /** The drilled-in group key for this request. When set, the renderer
226
- * suppresses group banding (no heading rows, no per-group summaries),
227
- * shows a "Drilled into <Label>: <Key>" chip above the table with an
228
- * × to clear, and the records have already been narrowed to that
229
- * bucket server-side via `TableGroup.scopeQueryByKey`. Sparse —
230
- * omitted unless `?<prefix>groupKey=<value>` is present AND the named
231
- * group exists AND is `scopable`. */
232
- activeGroupKey?: string
233
-
234
- /** Per-column summary results — keyed by column name, each entry is the
235
- * computed `SummaryResult[]` for that column's `summarize([…])`. Filled
236
- * in by `loadTableRecords` after `records()` runs. Renderer emits a
237
- * `<tfoot>` row when this is present. */
238
- summaries?: Record<string, SummaryResult[]>
239
-
240
- /** Per-group summaries — outer key = `_groupValue`, inner = column
241
- * name, value = `SummaryResult[]`. Computed only when an active group
242
- * is set AND at least one column has summarizers. Renderer emits an
243
- * inline summary row at the END of each group band, aligned to the
244
- * same columns as the global footer. */
245
- groupSummaries?: Record<string, Record<string, SummaryResult[]>>
246
-
247
- /** Drag-to-reorder is enabled on this table. The renderer adds a grip
248
- * handle column and binds HTML5 DnD on each `<tr>`. The actual sort
249
- * column the order is written back to lives on `reorderableColumn`. */
250
- reorderable?: true
251
-
252
- /** Name of the column the persisted order is written to. Defaults to
253
- * `'sort'` when `Table.reorderable()` is called without an argument.
254
- * Pilotiq's default `Table.records()` adapter uses this as the default
255
- * sort column when the URL doesn't override `?sort=…`. */
256
- reorderableColumn?: string
257
-
258
- /** Stamped server-side at render time. The renderer POSTs the new id
259
- * order here when a row is dropped. Absent when the route handler
260
- * hasn't tagged the table (panel boot will throw before that point if
261
- * `reorderable()` is set without a corresponding `model.reorder`). */
262
- reorderUrl?: string
263
-
264
- /** Tier-3 — set when `Resource.deferLoading = true`. The SSR pass
265
- * skips `Table.records()` so the client paints a skeleton on first
266
- * frame and fetches rows from `tableUrl` after mount. URL chrome
267
- * (current sort / search / page / group) still mirrors so the
268
- * skeleton frame doesn't reset user-visible state. */
269
- deferred?: true
270
-
271
- /** Stamped server-side at render time alongside `deferred`. The
272
- * renderer GETs this URL with the page's current query string to
273
- * fetch rows after mount. Empty when `deferLoading` is off. */
274
- tableUrl?: string
275
-
276
- /** Tier-3 — when set, the table's URL state (search / sort / page /
277
- * perPage / group / filter values) is namespaced under the identifier
278
- * so multiple tables on one page don't fight over `?search=` etc.
279
- * `?orders_search=foo&invoices_sort=date:desc`. Off by default — bare
280
- * keys are still used when no identifier is set. Custom-page-only:
281
- * resource list pages have one table by default and don't need it. */
282
- queryStringIdentifier?: string
283
-
284
- /** Content-layout mode. Absent = `'table'` (classic HTML table); when
285
- * `'cards'` the renderer hides the column header row and arranges rows
286
- * as cards in a CSS grid. Per-row card content is stamped on each row
287
- * under `_cardChildren: ElementMeta[]` by `loadTableRecords`. Columns
288
- * still drive search / sort / filter / group / summarize semantics. */
289
- contentLayout?: 'cards'
290
-
291
- /** Responsive card column counts for `contentLayout: 'cards'`. Renderer
292
- * maps each breakpoint to a `@container`-scoped Tailwind grid class. */
293
- cardsPerRow?: CardsPerRow
294
-
295
- /** Filter layout position. Absent = `'modal'` (current popover behavior).
296
- * The renderer swaps the toolbar Filters button for an inline strip in
297
- * the matching slot when set. See `FiltersLayout` for the full enum. */
298
- filtersLayout?: Exclude<FiltersLayout, 'modal'>
299
-
300
- // Render-time state — populated by the framework after `records()` runs.
301
- rows?: unknown[]
302
- total?: number
303
- currentSort?: { column: string; direction: SortDirection }
304
- search?: string
305
- currentPage?: number
306
- /** Absolute pathname the table lives at (e.g. `/admin/articles`). The
307
- * renderer prefixes sort/pagination/search hrefs with this so SPA
308
- * navigation resolves against the right route — Vike's client-side
309
- * router doesn't follow `?qs`-only relative links. */
310
- currentPath?: string
311
- }
312
-
313
- /**
314
- * Table container. Children are typically `Column[]` plus header / row /
315
- * bulk Actions. The query hook stays server-side; toMeta emits the
316
- * configured sort/pagination state, the searchable flag, and (after the
317
- * framework runs `records()`) the resolved rows + pagination state.
318
- */
319
- export class Table<R = unknown, Q = unknown> extends Element {
320
- private _query?: TableQueryHandler<Q>
321
- private _records?: TableRecordsHandler<R>
322
- private _defaultSort?: { column: string; direction: SortDirection }
323
- private _perPage?: number
324
-
325
- // Top-bar chrome
326
- private _heading?: string
327
- private _description?: string
328
- private _striped = false
329
- private _emptyState?: TableEmptyState
330
- private _filteredEmptyState?: TableEmptyState
331
-
332
- // Render-time state
333
- private _rows?: R[]
334
- private _total?: number
335
- private _currentSort?: { column: string; direction: SortDirection }
336
- private _currentSearch?: string
337
- private _currentPage?: number
338
- private _currentPath?: string
339
- private _recordUrl?: RecordUrlHandler<R>
340
- private _recordClasses?: RecordClassesHandler<R>
341
- private _pollInterval?: number
342
- private _defaultGroup?: string
343
- // Variance-relaxed — covariant `TableGroup<R>[]` ergonomics
344
- // matter more than tight invariance against the table's `R` parameter.
345
- private _groups: TableGroup<any>[] = [] // eslint-disable-line @typescript-eslint/no-explicit-any
346
- private _activeGroup?: string
347
- private _activeGroupKey?: string
348
- private _summaries?: Record<string, SummaryResult[]>
349
- private _groupSummaries?: Record<string, Record<string, SummaryResult[]>>
350
- private _reorderableColumn?: string
351
- private _reorderUrl?: string
352
- private _deferred = false
353
- private _tableUrl?: string
354
- private _queryStringIdentifier?: string
355
- private _contentLayout: ContentLayout = 'table'
356
- private _cardSchema?: CardSchemaHandler<R>
357
- private _cardsPerRow?: CardsPerRow
358
- private _filtersLayout: FiltersLayout = 'modal'
359
-
360
- private constructor() { super() }
361
-
362
- static make<R = unknown, Q = unknown>(): Table<R, Q> {
363
- return new Table<R, Q>()
364
- }
365
-
366
- // ─── Children ─────────────────────────────────────────
367
-
368
- /** Set children directly — typically a mix of Columns and Actions. */
369
- schema(elements: Element[]): this {
370
- this._children = elements
371
- return this
372
- }
373
-
374
- /** Shorthand: replace the column children. Existing actions are preserved. */
375
- columns(cols: Column[]): this {
376
- const existing = this._children ?? []
377
- const nonColumns = existing.filter(el => !(el instanceof Column))
378
- this._children = [...cols, ...nonColumns]
379
- return this
380
- }
381
-
382
- /** Shorthand: append actions to the children. Placement on each action /
383
- * group is preserved as-is; use the slot variants below
384
- * (`recordActions`, `headerActions`, `bulkActions`) when you want the
385
- * table to assign placement automatically. Both `Action` and
386
- * `ActionGroup` are accepted — groups render as dropdown triggers. */
387
- actions(acts: ActionOrGroup[]): this {
388
- const existing = this._children ?? []
389
- this._children = [...existing, ...acts]
390
- return this
391
- }
392
-
393
- /** Per-row actions slot — rendered in a DropdownMenu on each row.
394
- * Stamps `placement: 'row'` on each action so callers don't need
395
- * `Action.make(...).row()` boilerplate. */
396
- recordActions(acts: ActionOrGroup[]): this {
397
- return this.actions(acts.map(a => a.placement('row')))
398
- }
399
-
400
- /** Header actions slot — rendered top-right of the table. */
401
- headerActions(acts: ActionOrGroup[]): this {
402
- return this.actions(acts.map(a => a.placement('header')))
403
- }
404
-
405
- /** Bulk actions slot — shown in a toolbar when rows are selected. */
406
- bulkActions(acts: ActionOrGroup[]): this {
407
- return this.actions(acts.map(a => a.placement('bulk')))
408
- }
409
-
410
- /** Shorthand: replace the filter children. Existing columns/actions stay. */
411
- filters(filters: Filter[]): this {
412
- const existing = this._children ?? []
413
- const nonFilters = existing.filter(el => !(el instanceof Filter))
414
- this._children = [...nonFilters, ...filters]
415
- return this
416
- }
417
-
418
- // ─── Lifecycle config ────────────────────────────────
419
-
420
- /** Adapter-flavored query builder hook. Reserved for ORM adapters in Phase 3+. */
421
- query(fn: TableQueryHandler<Q>): this { this._query = fn; return this }
422
-
423
- /** Row loader — receives a `TableContext` and returns rows (and optional total). */
424
- records(fn: TableRecordsHandler<R>): this { this._records = fn; return this }
425
-
426
- defaultSort(column: string, direction: SortDirection = 'asc'): this {
427
- this._defaultSort = { column, direction }
428
- return this
429
- }
430
-
431
- paginate(perPage: number): this { this._perPage = perPage; return this }
432
-
433
- // ─── Top-bar chrome ───────────────────────────────────
434
-
435
- /** Title rendered above the table (left of the header bar). */
436
- heading(s: string): this { this._heading = s; return this }
437
-
438
- /** Subtitle rendered under the heading. */
439
- description(s: string): this { this._description = s; return this }
440
-
441
- /** Alternating row backgrounds for visual scanning. */
442
- striped(v = true): this { this._striped = v; return this }
443
-
444
- /** Customize the "no records" placeholder. */
445
- emptyState(state: TableEmptyState): this {
446
- this._emptyState = state
447
- return this
448
- }
449
-
450
- /**
451
- * Customize the "no matching records" placeholder shown when a search
452
- * query or filter is active but the result set is empty. Distinct from
453
- * `emptyState(...)` so the two cases can read differently — empty
454
- * tables typically want a "create your first one" call to action,
455
- * while filtered-empty tables want a "clear filters / adjust search"
456
- * hint.
457
- *
458
- * Falls back to `emptyState(...)` (or the framework defaults) when
459
- * unset, so opting into the distinction is purely additive.
460
- */
461
- filteredEmptyState(state: TableEmptyState): this {
462
- this._filteredEmptyState = state
463
- return this
464
- }
465
-
466
- /**
467
- * Set a per-row URL — each data cell renders as a real `<a href>` so
468
- * "open in new tab" works natively (right-click / cmd-click / middle-
469
- * click). Plain left-clicks SPA-navigate via `useNavigate()`. Action
470
- * and bulk-select cells stay unwrapped, so clicking a row action only
471
- * fires the action — there's no overlapping row-level click handler.
472
- *
473
- * The URL is stamped onto each row under the reserved `_recordUrl`
474
- * key during `loadTableRecords` — same convention as
475
- * `_visibleActions` / `_formatted`.
476
- *
477
- * Per-column overrides: pair with `Column.recordUrl(fn)` to swap a
478
- * column-specific URL, or `Column.recordUrl(false)` to opt a column
479
- * out (e.g. a column whose cell content has its own click affordance).
480
- */
481
- recordUrl(fn: RecordUrlHandler<R>): this {
482
- this._recordUrl = fn
483
- return this
484
- }
485
-
486
- /**
487
- * Per-row CSS class hook. The handler runs server-side once per row,
488
- * after `records()` resolves; the result is stamped under the reserved
489
- * `_recordClasses` key on the row and appended to the rendered `<tr>`'s
490
- * className. Pair with semantic Tailwind tokens (`bg-destructive/10`,
491
- * `text-warning`) so theming stays consistent.
492
- */
493
- recordClasses(fn: RecordClassesHandler<R>): this {
494
- this._recordClasses = fn
495
- return this
496
- }
497
-
498
- /**
499
- * Auto-refresh the table at a regular interval. `seconds` is positive;
500
- * non-positive values silently disable polling. SPA-friendly — the
501
- * client navigates to `pathname + search` (the current URL) so sort /
502
- * filter / pagination state survive the refresh, and AppShell stays
503
- * mounted. Polling pauses while the document is hidden.
504
- */
505
- poll(seconds: number): this {
506
- if (seconds > 0) this._pollInterval = seconds
507
- return this
508
- }
509
-
510
- /**
511
- * Band rows by a column's value. Stable-sorts the rendered rows so
512
- * shared values cluster together, then stamps each row's `_groupValue`
513
- * — the renderer inserts a heading row whenever the value changes.
514
- *
515
- * Accepts either a bare column name or a `TableGroup` instance. When
516
- * passed a `TableGroup` it's auto-added to `groups([…])` if it isn't
517
- * registered already, so `defaultGroup(TableGroup.make('status').label('Status'))`
518
- * doesn't require repeating the registration.
519
- */
520
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
521
- defaultGroup(group: string | TableGroup<any>): this {
522
- if (typeof group === 'string') {
523
- this._defaultGroup = group
524
- } else {
525
- this._defaultGroup = group.getColumn()
526
- const already = this._groups.some(g => g.getColumn() === group.getColumn())
527
- if (!already) this._groups.push(group)
528
- }
529
- return this
530
- }
531
-
532
- /**
533
- * Register the available group options. The renderer mounts a "Group
534
- * by" dropdown above the table when 2+ groups are registered (or 1
535
- * group with rich metadata — label / collapsible / record-derived
536
- * title). The active selection round-trips through the URL via the
537
- * reserved `?group=` key.
538
- */
539
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
540
- groups(items: TableGroup<any>[]): this {
541
- this._groups = items
542
- return this
543
- }
544
-
545
- /**
546
- * Enable drag-to-reorder. `column` names the model attribute the new
547
- * order is written back to (defaults to `'sort'`, matching Filament).
548
- * The route registers `POST {base}/{slug}/_reorder` — the renderer
549
- * POSTs `{ ids }` there on every drop. Boot panics when this is set
550
- * but `Resource.model.reorder` is missing.
551
- *
552
- * The reorder column also doubles as the default sort: when no
553
- * `defaultSort()` is configured and reorder is on, rows render
554
- * `(reorderColumn, asc)` so the visible order matches the persisted
555
- * order. URL `?sort=…` still wins.
556
- */
557
- reorderable(column: string = 'sort'): this {
558
- this._reorderableColumn = column
559
- return this
560
- }
561
-
562
- // ─── Render-time state ────────────────────────────────
563
-
564
- /** Attach loaded rows + total. Called by the framework after `records()` runs. */
565
- withRows(rows: R[], total?: number): this {
566
- this._rows = rows
567
- if (total !== undefined) this._total = total
568
- return this
569
- }
570
-
571
- withSort(column: string, direction: SortDirection): this {
572
- this._currentSort = { column, direction }
573
- return this
574
- }
575
-
576
- withSearch(query: string): this { this._currentSearch = query; return this }
577
- withPage(page: number): this { this._currentPage = page; return this }
578
- withCurrentPath(path: string): this { this._currentPath = path; return this }
579
- withSummaries(summaries: Record<string, SummaryResult[]>): this {
580
- this._summaries = summaries
581
- return this
582
- }
583
- /** Stamp per-group summary results. Outer key = `_groupValue`,
584
- * inner = column name. Set by `loadTableRecords` only when an active
585
- * group is set AND at least one column has summarizers. */
586
- withGroupSummaries(
587
- groupSummaries: Record<string, Record<string, SummaryResult[]>>,
588
- ): this {
589
- this._groupSummaries = groupSummaries
590
- return this
591
- }
592
-
593
- /** Stamp the reorder POST URL. Called by `tagTableReorderUrls` during
594
- * `resourceIndexData` so the renderer knows where to send drops. */
595
- withReorderUrl(url: string): this {
596
- this._reorderUrl = url
597
- return this
598
- }
599
-
600
- /** Mark this table as deferred — the SSR pass skips `records()` and
601
- * the client fetches rows from `tableUrl` after mount. Set by
602
- * `resourceIndexData` when `Resource.deferLoading = true`; user code
603
- * doesn't typically call this directly. */
604
- withDeferred(v: boolean = true): this {
605
- this._deferred = v
606
- return this
607
- }
608
-
609
- /** Stamp the deferred-load fetch URL. Set alongside `withDeferred`
610
- * by `tagTableUrls` during `resourceIndexData`. */
611
- withTableUrl(url: string): this {
612
- this._tableUrl = url
613
- return this
614
- }
615
-
616
- /**
617
- * Namespace this table's URL state under an identifier so multiple
618
- * tables on the same page don't collide on `?search=` / `?sort=` /
619
- * `?page=` / filter keys. With `queryStringIdentifier('orders')`, every
620
- * key is read and written as `orders_<key>` (e.g. `?orders_search=foo`,
621
- * `?orders_sort=date:desc`, `?orders_status=open`). Off by default —
622
- * resource list pages have one Table and don't need a prefix.
623
- *
624
- * The empty string is rejected (would silently disable namespacing
625
- * while implying it's on); identifiers must match `[A-Za-z0-9_-]+`
626
- * so they don't smuggle reserved characters into URL keys.
627
- */
628
- queryStringIdentifier(id: string): this {
629
- if (!/^[A-Za-z0-9_-]+$/.test(id)) {
630
- throw new Error(
631
- `Table.queryStringIdentifier: invalid id ${JSON.stringify(id)} — must match /^[A-Za-z0-9_-]+$/`,
632
- )
633
- }
634
- this._queryStringIdentifier = id
635
- return this
636
- }
637
-
638
- /**
639
- * Pick the content layout for this table. `'table'` (default) renders
640
- * the classic `<thead>` + `<tbody>` HTML table. `'cards'` hides the
641
- * column header row and renders each row as a card in a CSS grid; the
642
- * per-row content comes from `cardSchema(...)`.
643
- *
644
- * Columns still drive search / sort / filter / group / summarize
645
- * semantics in cards mode — they're just not painted as table headers.
646
- * The top-bar gains a "Sort by" dropdown built from `Column.sortable()`
647
- * columns since column headers (the usual sort affordance) are hidden.
648
- */
649
- contentLayout(layout: ContentLayout): this {
650
- this._contentLayout = layout
651
- return this
652
- }
653
-
654
- /** Sugar for `contentLayout('cards')`. */
655
- cards(): this { return this.contentLayout('cards') }
656
-
657
- /**
658
- * Per-row card content. Returns an `Element[]` rendered inside a card
659
- * for the given record + ctx. Resolved server-side per row in
660
- * `loadTableRecords` and stamped on `row._cardChildren`. Required when
661
- * `contentLayout === 'cards'`; `toMeta()` throws otherwise.
662
- *
663
- * Display-only — `Form / Field / Filter / Action` inside the card
664
- * schema is unsupported in v1. Reuse `Heading`, `Text`, `Image`, `Icon`,
665
- * `Group / Split / Grid`, and the read-only `Entry` family
666
- * (`TextEntry / BadgeEntry / IconEntry / ImageEntry / KeyValueEntry /
667
- * ColorEntry / ComponentEntry`) for content.
668
- */
669
- cardSchema(fn: CardSchemaHandler<R>): this {
670
- this._cardSchema = fn
671
- return this
672
- }
673
-
674
- /**
675
- * Responsive grid column counts in cards mode. Each entry maps to a
676
- * Tailwind container query (`@sm` = ≥40rem, etc.). Unspecified
677
- * breakpoints inherit the next-smaller; `default` is the base.
678
- * Values clamp to `[1, 12]`. Default `{ default: 1, sm: 2, lg: 3 }`.
679
- */
680
- cardsPerRow(opts: CardsPerRow): this {
681
- this._cardsPerRow = opts
682
- return this
683
- }
684
-
685
- /**
686
- * Where filters render relative to the table. Default `'modal'` keeps
687
- * the toolbar Filters button + popover. The three inline modes
688
- * (`'above-content'` / `'above-content-collapsible'` / `'below-content'`)
689
- * lay every filter widget out as a horizontal strip in place of the
690
- * popover.
691
- */
692
- filtersLayout(layout: FiltersLayout): this {
693
- this._filtersLayout = layout
694
- return this
695
- }
696
-
697
- /** Render-time setter — the column rows are actually banded by for
698
- * this request, after `?group=` and `defaultGroup(...)` are reconciled.
699
- * Set by `loadTableRecords`. Empty string explicitly clears (URL
700
- * `?group=` overrode `defaultGroup`). */
701
- withActiveGroup(column: string | undefined): this {
702
- if (column === undefined) delete this._activeGroup
703
- else this._activeGroup = column
704
- return this
705
- }
706
-
707
- /** Render-time setter — the drilled-in group key for this request,
708
- * after `?<prefix>groupKey=` was reconciled against a `scopable`
709
- * group. Set by `loadTableRecords`. Empty string / undefined both
710
- * clear, since drill-in needs an actual key to scope against. */
711
- withActiveGroupKey(key: string | undefined): this {
712
- if (key === undefined || key === '') delete this._activeGroupKey
713
- else this._activeGroupKey = key
714
- return this
715
- }
716
-
717
- // ─── Getters ──────────────────────────────────────────
718
-
719
- getQuery(): TableQueryHandler<Q> | undefined { return this._query }
720
- getRecords(): TableRecordsHandler<R> | undefined { return this._records }
721
- getDefaultSort(): { column: string; direction: SortDirection } | undefined { return this._defaultSort }
722
- getPerPage(): number | undefined { return this._perPage }
723
- getRows(): R[] | undefined { return this._rows }
724
- getTotal(): number | undefined { return this._total }
725
- getCurrentSort(): { column: string; direction: SortDirection } | undefined { return this._currentSort }
726
- getCurrentSearch(): string | undefined { return this._currentSearch }
727
- getCurrentPage(): number | undefined { return this._currentPage }
728
- getCurrentPath(): string | undefined { return this._currentPath }
729
- getRecordUrl(): RecordUrlHandler<R> | undefined { return this._recordUrl }
730
- getRecordClasses(): RecordClassesHandler<R> | undefined { return this._recordClasses }
731
- getPollInterval(): number | undefined { return this._pollInterval }
732
- getDefaultGroup(): string | undefined { return this._defaultGroup }
733
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
734
- getGroups(): TableGroup<any>[] { return this._groups }
735
- getActiveGroup(): string | undefined { return this._activeGroup }
736
- getActiveGroupKey(): string | undefined { return this._activeGroupKey }
737
- /** Resolve the active column to a `TableGroup` instance. Returns the
738
- * matching registered group, or — when the active column is set but
739
- * not registered (bare-column form) — synthesizes a no-metadata group
740
- * so the dispatcher has a uniform shape to work with. */
741
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
742
- getActiveGroupInstance(): TableGroup<any> | undefined {
743
- const col = this._activeGroup
744
- if (!col) return undefined
745
- const found = this._groups.find(g => g.getColumn() === col)
746
- return found ?? TableGroup.make(col)
747
- }
748
- getSummaries(): Record<string, SummaryResult[]> | undefined { return this._summaries }
749
- getGroupSummaries(): Record<string, Record<string, SummaryResult[]>> | undefined { return this._groupSummaries }
750
- getReorderableColumn(): string | undefined { return this._reorderableColumn }
751
- isReorderable(): boolean { return this._reorderableColumn !== undefined }
752
- getReorderUrl(): string | undefined { return this._reorderUrl }
753
- isDeferred(): boolean { return this._deferred }
754
- getTableUrl(): string | undefined { return this._tableUrl }
755
- getQueryStringIdentifier(): string | undefined { return this._queryStringIdentifier }
756
- getContentLayout(): ContentLayout { return this._contentLayout }
757
- isCardsLayout(): boolean { return this._contentLayout === 'cards' }
758
- getCardSchema(): CardSchemaHandler<R> | undefined { return this._cardSchema }
759
- getCardsPerRow(): CardsPerRow | undefined { return this._cardsPerRow }
760
- getFiltersLayout(): FiltersLayout { return this._filtersLayout }
761
-
762
- /** Convenience: the `Column` children only. */
763
- getColumns(): Column[] {
764
- return (this._children ?? []).filter((el): el is Column => el instanceof Column)
765
- }
766
-
767
- /** Convenience: the `Filter` children only. */
768
- getFilters(): Filter[] {
769
- return (this._children ?? []).filter((el): el is Filter => el instanceof Filter)
770
- }
771
-
772
- // ─── Serialization ────────────────────────────────────
773
-
774
- getType(): string { return 'table' }
775
-
776
- override toMeta(): TableMeta {
777
- const searchable = this.getColumns().some(c => c.isSearchable())
778
- if (this._contentLayout === 'cards' && this._cardSchema === undefined) {
779
- throw new Error(
780
- 'Table.contentLayout("cards") requires .cardSchema((record, ctx) => Element[]). ' +
781
- 'Cards mode renders each row from a per-row schema; configure one before rendering.',
782
- )
783
- }
784
- const cardsPerRow = this._cardsPerRow !== undefined
785
- ? clampCardsPerRow(this._cardsPerRow)
786
- : undefined
787
- return {
788
- type: 'table',
789
- searchable,
790
- ...(this._defaultSort ? { defaultSort: this._defaultSort } : {}),
791
- ...(this._perPage !== undefined ? { perPage: this._perPage } : {}),
792
- ...(this._heading !== undefined ? { heading: this._heading } : {}),
793
- ...(this._description !== undefined ? { description: this._description } : {}),
794
- ...(this._striped ? { striped: true } : {}),
795
- ...(this._emptyState !== undefined ? { emptyState: this._emptyState } : {}),
796
- ...(this._filteredEmptyState !== undefined ? { filteredEmptyState: this._filteredEmptyState } : {}),
797
- ...(this._recordUrl !== undefined ? { recordUrl: true as const } : {}),
798
- ...(this._recordClasses !== undefined ? { recordClasses: true as const } : {}),
799
- ...(this._pollInterval !== undefined ? { pollInterval: this._pollInterval } : {}),
800
- // `defaultGroup` on the meta means "the column rows are actually
801
- // grouped by for this render". Prefer the render-time activeGroup
802
- // (set by `loadTableRecords` after reconciling `?group=` and the
803
- // configured default); fall back to the configured default when
804
- // no request-side reconciliation has happened (e.g. tests calling
805
- // `toMeta()` directly).
806
- ...(this._activeGroup !== undefined
807
- ? (this._activeGroup === ''
808
- ? {}
809
- : { defaultGroup: this._activeGroup })
810
- : (this._defaultGroup !== undefined
811
- ? { defaultGroup: this._defaultGroup }
812
- : {})),
813
- ...(this._groups.length > 0
814
- ? { groups: this._groups.map(g => g.toMeta()) }
815
- : {}),
816
- ...(this._summaries !== undefined ? { summaries: this._summaries } : {}),
817
- ...(this._groupSummaries !== undefined ? { groupSummaries: this._groupSummaries } : {}),
818
- ...(this._activeGroupKey !== undefined ? { activeGroupKey: this._activeGroupKey } : {}),
819
- ...(this._reorderableColumn !== undefined ? { reorderable: true as const, reorderableColumn: this._reorderableColumn } : {}),
820
- ...(this._reorderUrl !== undefined ? { reorderUrl: this._reorderUrl } : {}),
821
- ...(this._deferred ? { deferred: true as const } : {}),
822
- ...(this._tableUrl !== undefined ? { tableUrl: this._tableUrl } : {}),
823
- ...(this._queryStringIdentifier !== undefined
824
- ? { queryStringIdentifier: this._queryStringIdentifier } : {}),
825
- ...(this._contentLayout === 'cards' ? { contentLayout: 'cards' as const } : {}),
826
- ...(cardsPerRow !== undefined ? { cardsPerRow } : {}),
827
- ...(this._filtersLayout !== 'modal' ? { filtersLayout: this._filtersLayout } : {}),
828
- ...(this._rows !== undefined ? { rows: this._rows } : {}),
829
- ...(this._total !== undefined ? { total: this._total } : {}),
830
- ...(this._currentSort !== undefined ? { currentSort: this._currentSort } : {}),
831
- ...(this._currentSearch !== undefined ? { search: this._currentSearch } : {}),
832
- ...(this._currentPage !== undefined ? { currentPage: this._currentPage } : {}),
833
- ...(this._currentPath !== undefined ? { currentPath: this._currentPath } : {}),
834
- }
835
- }
836
- }
837
-
838
- const CARDS_PER_ROW_KEYS = ['default', 'sm', 'md', 'lg', 'xl', '2xl'] as const
839
-
840
- function clampCardsPerRow(opts: CardsPerRow): CardsPerRow {
841
- const out: CardsPerRow = {}
842
- for (const key of CARDS_PER_ROW_KEYS) {
843
- const v = opts[key]
844
- if (v === undefined) continue
845
- const n = Math.floor(Number(v))
846
- if (!Number.isFinite(n)) continue
847
- out[key] = Math.min(12, Math.max(1, n))
848
- }
849
- return out
850
- }