@pilotiq/pilotiq 0.24.1 → 0.24.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (518) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/boost/guidelines.md +571 -0
  3. package/boost/skills/pilotiq-actions/SKILL.md +49 -0
  4. package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
  5. package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
  6. package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -0
  7. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  9. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  10. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  11. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  12. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  13. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  14. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  15. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  16. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  17. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  18. package/dist/Pilotiq.d.ts +31 -0
  19. package/dist/Pilotiq.d.ts.map +1 -1
  20. package/dist/Pilotiq.js +3 -1
  21. package/dist/Pilotiq.js.map +1 -1
  22. package/dist/PilotiqRegistry.d.ts +13 -0
  23. package/dist/PilotiqRegistry.d.ts.map +1 -1
  24. package/dist/PilotiqRegistry.js +15 -0
  25. package/dist/PilotiqRegistry.js.map +1 -1
  26. package/dist/pageData/misc.d.ts.map +1 -1
  27. package/dist/pageData/misc.js +6 -0
  28. package/dist/pageData/misc.js.map +1 -1
  29. package/dist/pageData/navigation.d.ts +1 -0
  30. package/dist/pageData/navigation.d.ts.map +1 -1
  31. package/dist/pageData/navigation.js +3 -0
  32. package/dist/pageData/navigation.js.map +1 -1
  33. package/dist/pageData/relationPages.d.ts.map +1 -1
  34. package/dist/pageData/relationPages.js +3 -0
  35. package/dist/pageData/relationPages.js.map +1 -1
  36. package/dist/pageData/resourcePages.d.ts.map +1 -1
  37. package/dist/pageData/resourcePages.js +8 -0
  38. package/dist/pageData/resourcePages.js.map +1 -1
  39. package/dist/react/AppShell.d.ts +8 -0
  40. package/dist/react/AppShell.d.ts.map +1 -1
  41. package/dist/react/AppShell.js.map +1 -1
  42. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  43. package/dist/react/layouts/SidebarLayout.js +10 -2
  44. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  45. package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
  46. package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
  47. package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
  48. package/dist/routes/relations.d.ts.map +1 -1
  49. package/dist/routes/relations.js +25 -18
  50. package/dist/routes/relations.js.map +1 -1
  51. package/dist/routes/resources.js.map +1 -1
  52. package/package.json +10 -5
  53. package/.turbo/turbo-build.log +0 -8
  54. package/CLAUDE.md +0 -265
  55. package/src/Cluster.test.ts +0 -283
  56. package/src/Cluster.ts +0 -83
  57. package/src/Column.test.ts +0 -199
  58. package/src/Column.ts +0 -710
  59. package/src/Global.test.ts +0 -367
  60. package/src/Global.ts +0 -169
  61. package/src/Page.test.ts +0 -114
  62. package/src/Page.ts +0 -208
  63. package/src/Pilotiq.perf.test.ts +0 -252
  64. package/src/Pilotiq.test.ts +0 -129
  65. package/src/Pilotiq.ts +0 -1158
  66. package/src/PilotiqRegistry.ts +0 -36
  67. package/src/PilotiqServiceProvider.ts +0 -121
  68. package/src/RelationManager.test.ts +0 -400
  69. package/src/RelationManager.ts +0 -527
  70. package/src/RenderHook.test.ts +0 -252
  71. package/src/RenderHook.ts +0 -242
  72. package/src/Resource.test.ts +0 -284
  73. package/src/Resource.ts +0 -526
  74. package/src/RightPanel.test.ts +0 -202
  75. package/src/RightPanel.ts +0 -132
  76. package/src/Tab.test.ts +0 -91
  77. package/src/Tab.ts +0 -156
  78. package/src/UserMenuItem.ts +0 -145
  79. package/src/actions/Action.test.ts +0 -2526
  80. package/src/actions/Action.ts +0 -1515
  81. package/src/actions/ActionGroup.test.ts +0 -112
  82. package/src/actions/ActionGroup.ts +0 -173
  83. package/src/actions/attachFactory.ts +0 -172
  84. package/src/actions/bulkFactories.ts +0 -168
  85. package/src/actions/crudFactories.ts +0 -220
  86. package/src/actions/exportFactory.ts +0 -225
  87. package/src/actions/factoryHelpers.ts +0 -177
  88. package/src/actions/importFactory.ts +0 -243
  89. package/src/actions/index.ts +0 -17
  90. package/src/actions/m2mFactories.ts +0 -193
  91. package/src/actions/relationFactories.ts +0 -372
  92. package/src/applyPageHooks.test.ts +0 -463
  93. package/src/applyPageHooks.ts +0 -330
  94. package/src/authorization.test.ts +0 -483
  95. package/src/breadcrumbs.test.ts +0 -238
  96. package/src/cells/coerce.test.ts +0 -85
  97. package/src/cells/coerce.ts +0 -84
  98. package/src/clusterPaths.ts +0 -35
  99. package/src/columns/BadgeColumn.test.ts +0 -54
  100. package/src/columns/BadgeColumn.ts +0 -32
  101. package/src/columns/BooleanColumn.test.ts +0 -41
  102. package/src/columns/BooleanColumn.ts +0 -18
  103. package/src/columns/ColorColumn.test.ts +0 -37
  104. package/src/columns/ColorColumn.ts +0 -38
  105. package/src/columns/IconColumn.test.ts +0 -54
  106. package/src/columns/IconColumn.ts +0 -37
  107. package/src/columns/ImageColumn.test.ts +0 -41
  108. package/src/columns/ImageColumn.ts +0 -28
  109. package/src/columns/SelectColumn.ts +0 -98
  110. package/src/columns/TextColumn.test.ts +0 -190
  111. package/src/columns/TextColumn.ts +0 -20
  112. package/src/columns/TextInputColumn.ts +0 -68
  113. package/src/columns/ToggleColumn.ts +0 -46
  114. package/src/columns/editableColumns.test.ts +0 -238
  115. package/src/columns/index.ts +0 -9
  116. package/src/defaultGlobalPages.ts +0 -95
  117. package/src/defaultPages.test.ts +0 -634
  118. package/src/defaultPages.ts +0 -617
  119. package/src/defaultViewPage.test.ts +0 -147
  120. package/src/elements/Form.test.ts +0 -223
  121. package/src/elements/Form.ts +0 -416
  122. package/src/elements/ListTabs.ts +0 -28
  123. package/src/elements/Table.test.ts +0 -422
  124. package/src/elements/Table.ts +0 -850
  125. package/src/elements/TableGroup.test.ts +0 -260
  126. package/src/elements/TableGroup.ts +0 -334
  127. package/src/elements/dispatchAction.test.ts +0 -463
  128. package/src/elements/dispatchAction.ts +0 -355
  129. package/src/elements/dispatchForm.test.ts +0 -477
  130. package/src/elements/dispatchForm.ts +0 -1993
  131. package/src/elements/dispatchTable.test.ts +0 -1514
  132. package/src/elements/dispatchTable.ts +0 -745
  133. package/src/elements/index.ts +0 -21
  134. package/src/entries/BadgeEntry.ts +0 -39
  135. package/src/entries/CodeEntry.test.ts +0 -40
  136. package/src/entries/CodeEntry.ts +0 -52
  137. package/src/entries/ColorEntry.ts +0 -63
  138. package/src/entries/ComponentEntry.test.ts +0 -173
  139. package/src/entries/ComponentEntry.ts +0 -95
  140. package/src/entries/Entry.ts +0 -304
  141. package/src/entries/IconEntry.ts +0 -49
  142. package/src/entries/ImageEntry.ts +0 -61
  143. package/src/entries/KeyValueEntry.ts +0 -47
  144. package/src/entries/RepeatableEntry.test.ts +0 -239
  145. package/src/entries/RepeatableEntry.ts +0 -173
  146. package/src/entries/TextEntry.test.ts +0 -394
  147. package/src/entries/TextEntry.ts +0 -60
  148. package/src/entries/index.ts +0 -12
  149. package/src/entries/leaves.test.ts +0 -306
  150. package/src/entries/registry.ts +0 -54
  151. package/src/fields/BuilderField.test.ts +0 -1188
  152. package/src/fields/BuilderField.ts +0 -605
  153. package/src/fields/BuilderRelationship.test.ts +0 -811
  154. package/src/fields/CheckboxField.test.ts +0 -44
  155. package/src/fields/CheckboxField.ts +0 -27
  156. package/src/fields/CheckboxListField.test.ts +0 -99
  157. package/src/fields/CheckboxListField.ts +0 -66
  158. package/src/fields/ColorPickerField.test.ts +0 -33
  159. package/src/fields/ColorPickerField.ts +0 -25
  160. package/src/fields/DateField.ts +0 -54
  161. package/src/fields/DateTimeField.test.ts +0 -55
  162. package/src/fields/EmailField.ts +0 -16
  163. package/src/fields/Field.test.ts +0 -654
  164. package/src/fields/Field.ts +0 -817
  165. package/src/fields/FileUploadField.test.ts +0 -143
  166. package/src/fields/FileUploadField.ts +0 -159
  167. package/src/fields/HiddenField.test.ts +0 -27
  168. package/src/fields/HiddenField.ts +0 -28
  169. package/src/fields/KeyValueField.test.ts +0 -105
  170. package/src/fields/KeyValueField.ts +0 -55
  171. package/src/fields/MarkdownField.test.ts +0 -167
  172. package/src/fields/MarkdownField.ts +0 -162
  173. package/src/fields/NumberField.ts +0 -33
  174. package/src/fields/RadioField.test.ts +0 -94
  175. package/src/fields/RadioField.ts +0 -67
  176. package/src/fields/RepeaterField.test.ts +0 -1806
  177. package/src/fields/RepeaterField.ts +0 -939
  178. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  179. package/src/fields/RepeaterSimple.test.ts +0 -248
  180. package/src/fields/RowButton.test.ts +0 -219
  181. package/src/fields/RowButton.ts +0 -135
  182. package/src/fields/SelectField.test.ts +0 -192
  183. package/src/fields/SelectField.ts +0 -235
  184. package/src/fields/SliderField.test.ts +0 -50
  185. package/src/fields/SliderField.ts +0 -53
  186. package/src/fields/SlugField.ts +0 -24
  187. package/src/fields/TagsInputField.test.ts +0 -154
  188. package/src/fields/TagsInputField.ts +0 -133
  189. package/src/fields/TextField.test.ts +0 -213
  190. package/src/fields/TextField.ts +0 -177
  191. package/src/fields/TextareaField.test.ts +0 -58
  192. package/src/fields/TextareaField.ts +0 -59
  193. package/src/fields/ToggleButtonsField.test.ts +0 -106
  194. package/src/fields/ToggleButtonsField.ts +0 -59
  195. package/src/fields/ToggleField.ts +0 -16
  196. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  197. package/src/fields/optionsResolver.ts +0 -95
  198. package/src/fields/resolveField.ts +0 -28
  199. package/src/filters/BooleanFilter.ts +0 -35
  200. package/src/filters/DateRangeFilter.test.ts +0 -194
  201. package/src/filters/DateRangeFilter.ts +0 -148
  202. package/src/filters/Filter.test.ts +0 -268
  203. package/src/filters/Filter.ts +0 -184
  204. package/src/filters/FormFilter.test.ts +0 -238
  205. package/src/filters/FormFilter.ts +0 -215
  206. package/src/filters/MultiSelectFilter.test.ts +0 -119
  207. package/src/filters/MultiSelectFilter.ts +0 -78
  208. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  209. package/src/filters/QueryBuilderFilter.ts +0 -398
  210. package/src/filters/SelectFilter.ts +0 -46
  211. package/src/filters/TernaryFilter.test.ts +0 -160
  212. package/src/filters/TernaryFilter.ts +0 -72
  213. package/src/filters/TrashedFilter.test.ts +0 -149
  214. package/src/filters/TrashedFilter.ts +0 -55
  215. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  216. package/src/filters/queryBuilder/Constraint.ts +0 -115
  217. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  218. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  219. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  220. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  221. package/src/filters/queryBuilder/index.ts +0 -12
  222. package/src/icons/index.ts +0 -2
  223. package/src/icons/lucide.ts +0 -204
  224. package/src/icons/registry.test.ts +0 -56
  225. package/src/icons/registry.ts +0 -41
  226. package/src/icons/types.ts +0 -47
  227. package/src/index.ts +0 -525
  228. package/src/io/csv.test.ts +0 -142
  229. package/src/io/csv.ts +0 -170
  230. package/src/nestedRelationManagerData.test.ts +0 -547
  231. package/src/notifications/Notification.test.ts +0 -210
  232. package/src/notifications/Notification.ts +0 -354
  233. package/src/notifications/broadcast.test.ts +0 -110
  234. package/src/notifications/broadcast.ts +0 -95
  235. package/src/notifications/database.test.ts +0 -383
  236. package/src/notifications/database.ts +0 -398
  237. package/src/notifications/databaseNotifications.test.ts +0 -187
  238. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  239. package/src/notifications/dispatchNotificationAction.ts +0 -142
  240. package/src/notifications/flash.test.ts +0 -89
  241. package/src/notifications/flash.ts +0 -71
  242. package/src/notifications/index.ts +0 -45
  243. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  244. package/src/notifications/registerBroadcastAuth.ts +0 -100
  245. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  246. package/src/notifications/resolveSavedNotification.ts +0 -59
  247. package/src/notifications/types.ts +0 -93
  248. package/src/orm/m2mAccessor.ts +0 -66
  249. package/src/orm/modelDefaults.test.ts +0 -633
  250. package/src/orm/modelDefaults.ts +0 -666
  251. package/src/pageData/breadcrumbs.ts +0 -288
  252. package/src/pageData/forms.ts +0 -578
  253. package/src/pageData/helpers.ts +0 -857
  254. package/src/pageData/misc.ts +0 -347
  255. package/src/pageData/navigation.ts +0 -842
  256. package/src/pageData/relationPages.ts +0 -1248
  257. package/src/pageData/relationTabs.ts +0 -286
  258. package/src/pageData/resourcePages.ts +0 -609
  259. package/src/pageData.test.ts +0 -1545
  260. package/src/pageData.ts +0 -341
  261. package/src/plugins/index.ts +0 -8
  262. package/src/plugins/themeEditor.test.ts +0 -36
  263. package/src/plugins/themeEditor.ts +0 -45
  264. package/src/react/AppShell.tsx +0 -251
  265. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  266. package/src/react/CollabRoomContext.ts +0 -98
  267. package/src/react/CollabTextRendererRegistry.ts +0 -102
  268. package/src/react/CommandPalette.tsx +0 -375
  269. package/src/react/CurrentUserContext.tsx +0 -50
  270. package/src/react/CustomPageWrapperGate.tsx +0 -69
  271. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  272. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  273. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  274. package/src/react/FieldPresenceRegistry.ts +0 -46
  275. package/src/react/FormCollabBindingRegistry.ts +0 -242
  276. package/src/react/FormStateContext.tsx +0 -591
  277. package/src/react/HeadHooks.tsx +0 -126
  278. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  279. package/src/react/MarkdownEditorRegistry.ts +0 -107
  280. package/src/react/NotificationActionStrip.tsx +0 -263
  281. package/src/react/NotificationBell.tsx +0 -426
  282. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  283. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  284. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  285. package/src/react/PendingSuggestionsContext.tsx +0 -172
  286. package/src/react/RecordWrapperGate.tsx +0 -58
  287. package/src/react/RecordWrapperRegistry.ts +0 -39
  288. package/src/react/RenderHookSlot.tsx +0 -32
  289. package/src/react/RightSidebar.tsx +0 -257
  290. package/src/react/RightSidebarContext.tsx +0 -234
  291. package/src/react/RightSidebarTrigger.tsx +0 -53
  292. package/src/react/RowCoordsContext.tsx +0 -23
  293. package/src/react/SchemaRenderer.tsx +0 -549
  294. package/src/react/SearchTrigger.tsx +0 -46
  295. package/src/react/ThemeProvider.tsx +0 -93
  296. package/src/react/ThemeSettingsPage.tsx +0 -579
  297. package/src/react/ThemeToggle.tsx +0 -20
  298. package/src/react/Toaster.tsx +0 -158
  299. package/src/react/UserMenu.tsx +0 -196
  300. package/src/react/WidgetDataContext.tsx +0 -157
  301. package/src/react/cells/EditableCell.tsx +0 -389
  302. package/src/react/component-slots.test.ts +0 -103
  303. package/src/react/component-slots.ts +0 -116
  304. package/src/react/fieldJsHandler.test.ts +0 -166
  305. package/src/react/fieldJsHandler.ts +0 -79
  306. package/src/react/fields/BuilderInput.tsx +0 -1078
  307. package/src/react/fields/CheckboxInput.tsx +0 -39
  308. package/src/react/fields/CheckboxListInput.tsx +0 -102
  309. package/src/react/fields/ColorInput.tsx +0 -71
  310. package/src/react/fields/DateFieldInput.tsx +0 -70
  311. package/src/react/fields/DateTimeInput.tsx +0 -62
  312. package/src/react/fields/FieldShell.tsx +0 -348
  313. package/src/react/fields/FileUploadInput.tsx +0 -639
  314. package/src/react/fields/HiddenInput.tsx +0 -17
  315. package/src/react/fields/KeyValueInput.tsx +0 -230
  316. package/src/react/fields/MarkdownInput.tsx +0 -560
  317. package/src/react/fields/RadioInput.tsx +0 -81
  318. package/src/react/fields/RepeaterInput.test.ts +0 -116
  319. package/src/react/fields/RepeaterInput.tsx +0 -1420
  320. package/src/react/fields/SelectFieldInput.tsx +0 -280
  321. package/src/react/fields/SliderInput.tsx +0 -81
  322. package/src/react/fields/TagsInput.tsx +0 -283
  323. package/src/react/fields/TextLikeInput.tsx +0 -256
  324. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  325. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  326. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  327. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  328. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  329. package/src/react/fields/repeaterReconcile.ts +0 -104
  330. package/src/react/fields/rowChromeButton.tsx +0 -336
  331. package/src/react/fields/rowState.ts +0 -106
  332. package/src/react/fields/syncRowGates.test.ts +0 -202
  333. package/src/react/fields/syncRowGates.ts +0 -66
  334. package/src/react/fields/textInputControls.tsx +0 -238
  335. package/src/react/fields/useRowReorderDnd.ts +0 -78
  336. package/src/react/formStateHelpers.test.ts +0 -508
  337. package/src/react/formStateHelpers.ts +0 -381
  338. package/src/react/hooks/use-mobile.ts +0 -19
  339. package/src/react/icon-context.tsx +0 -60
  340. package/src/react/index.ts +0 -194
  341. package/src/react/layouts/SidebarLayout.tsx +0 -250
  342. package/src/react/layouts/TopbarLayout.tsx +0 -258
  343. package/src/react/navigate.tsx +0 -37
  344. package/src/react/onProviderSynced.test.ts +0 -90
  345. package/src/react/parseRecordEditUrl.test.ts +0 -122
  346. package/src/react/parseRecordEditUrl.ts +0 -94
  347. package/src/react/persistedState.ts +0 -40
  348. package/src/react/registry.ts +0 -48
  349. package/src/react/right-panel-registry.tsx +0 -47
  350. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  351. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  352. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  353. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  354. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  355. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  356. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  357. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  358. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  359. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  360. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  361. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  362. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  363. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  364. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  365. package/src/react/schemaRenderer/constants.ts +0 -50
  366. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  367. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  368. package/src/react/schemaRenderer/helpers.tsx +0 -81
  369. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  370. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  371. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  372. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  373. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  374. package/src/react/schemaRenderer/table/links.tsx +0 -112
  375. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  376. package/src/react/schemaRenderer/table/url.tsx +0 -143
  377. package/src/react/theme-preview/apply.ts +0 -99
  378. package/src/react/theme-preview/build-html.ts +0 -436
  379. package/src/react/ui/button.tsx +0 -51
  380. package/src/react/ui/calendar.tsx +0 -67
  381. package/src/react/ui/checkbox.tsx +0 -29
  382. package/src/react/ui/dialog.tsx +0 -108
  383. package/src/react/ui/dropdown-menu.tsx +0 -97
  384. package/src/react/ui/input.tsx +0 -20
  385. package/src/react/ui/label.tsx +0 -21
  386. package/src/react/ui/popover.tsx +0 -50
  387. package/src/react/ui/select.tsx +0 -169
  388. package/src/react/ui/separator.tsx +0 -25
  389. package/src/react/ui/sheet.tsx +0 -136
  390. package/src/react/ui/sidebar.tsx +0 -723
  391. package/src/react/ui/skeleton.tsx +0 -13
  392. package/src/react/ui/slider.tsx +0 -34
  393. package/src/react/ui/switch.tsx +0 -28
  394. package/src/react/ui/table.tsx +0 -105
  395. package/src/react/ui/tabs.tsx +0 -63
  396. package/src/react/ui/textarea.tsx +0 -18
  397. package/src/react/ui/tooltip.tsx +0 -64
  398. package/src/react/useResizableWidth.ts +0 -139
  399. package/src/react/utils.ts +0 -6
  400. package/src/react/widgetRegistry.test.ts +0 -43
  401. package/src/react/widgetRegistry.ts +0 -50
  402. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  403. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  404. package/src/react/widgets/ViewRenderer.tsx +0 -71
  405. package/src/relationManagerData.test.ts +0 -1595
  406. package/src/richtext/index.ts +0 -8
  407. package/src/richtext/registry.ts +0 -89
  408. package/src/routes/globals.ts +0 -148
  409. package/src/routes/guard.test.ts +0 -325
  410. package/src/routes/helpers.ts +0 -704
  411. package/src/routes/pages.ts +0 -175
  412. package/src/routes/panel.ts +0 -204
  413. package/src/routes/relations.ts +0 -1243
  414. package/src/routes/resources.ts +0 -781
  415. package/src/routes/theme.ts +0 -91
  416. package/src/routes-nested-relations.test.ts +0 -676
  417. package/src/routes-relations.test.ts +0 -972
  418. package/src/routes.test.ts +0 -2027
  419. package/src/routes.ts +0 -303
  420. package/src/schema/Alert.test.ts +0 -109
  421. package/src/schema/Alert.ts +0 -131
  422. package/src/schema/Block.ts +0 -169
  423. package/src/schema/Breadcrumbs.ts +0 -40
  424. package/src/schema/Card.ts +0 -35
  425. package/src/schema/Divider.ts +0 -20
  426. package/src/schema/Element.ts +0 -219
  427. package/src/schema/EmptyState.test.ts +0 -37
  428. package/src/schema/EmptyState.ts +0 -63
  429. package/src/schema/Fieldset.ts +0 -43
  430. package/src/schema/Grid.ts +0 -43
  431. package/src/schema/Group.ts +0 -30
  432. package/src/schema/Heading.ts +0 -39
  433. package/src/schema/Html.ts +0 -67
  434. package/src/schema/Icon.ts +0 -54
  435. package/src/schema/Image.ts +0 -57
  436. package/src/schema/LinkTag.ts +0 -41
  437. package/src/schema/Markdown.ts +0 -85
  438. package/src/schema/MetaTag.ts +0 -41
  439. package/src/schema/RelationTabs.ts +0 -71
  440. package/src/schema/ScriptTag.ts +0 -55
  441. package/src/schema/Section.ts +0 -160
  442. package/src/schema/ServerDataElement.test.ts +0 -140
  443. package/src/schema/ServerDataElement.ts +0 -156
  444. package/src/schema/SlotComponent.test.ts +0 -77
  445. package/src/schema/SlotComponent.ts +0 -71
  446. package/src/schema/Split.ts +0 -50
  447. package/src/schema/Stat.test.ts +0 -118
  448. package/src/schema/Stat.ts +0 -154
  449. package/src/schema/StatsOverview.test.ts +0 -141
  450. package/src/schema/StatsOverview.ts +0 -119
  451. package/src/schema/StyleTag.ts +0 -35
  452. package/src/schema/TableWidget.test.ts +0 -297
  453. package/src/schema/TableWidget.ts +0 -289
  454. package/src/schema/Tabs.ts +0 -79
  455. package/src/schema/Text.ts +0 -58
  456. package/src/schema/UnorderedList.ts +0 -49
  457. package/src/schema/View.test.ts +0 -111
  458. package/src/schema/View.ts +0 -127
  459. package/src/schema/Wizard.ts +0 -220
  460. package/src/schema/containers.test.ts +0 -564
  461. package/src/schema/headTags.test.ts +0 -134
  462. package/src/schema/index.ts +0 -40
  463. package/src/schema/primes.test.ts +0 -269
  464. package/src/schema/resolveSchema.test.ts +0 -379
  465. package/src/schema/resolveSchema.ts +0 -917
  466. package/src/schema/sanitize.ts +0 -58
  467. package/src/search.test.ts +0 -446
  468. package/src/search.ts +0 -178
  469. package/src/sessionFilters.test.ts +0 -375
  470. package/src/sessionFilters.ts +0 -143
  471. package/src/slot-components/index.ts +0 -10
  472. package/src/slot-components/registry.ts +0 -56
  473. package/src/styles/file-upload.css +0 -13
  474. package/src/summarizers/Summarizer.test.ts +0 -84
  475. package/src/summarizers/Summarizer.ts +0 -123
  476. package/src/summarizers/index.ts +0 -11
  477. package/src/theme/base-colors.ts +0 -68
  478. package/src/theme/chart-colors.ts +0 -50
  479. package/src/theme/colors.ts +0 -447
  480. package/src/theme/generate-css.test.ts +0 -139
  481. package/src/theme/generate-css.ts +0 -44
  482. package/src/theme/generate-scale.test.ts +0 -106
  483. package/src/theme/generate-scale.ts +0 -97
  484. package/src/theme/icon-map.ts +0 -42
  485. package/src/theme/index.ts +0 -34
  486. package/src/theme/migrate.test.ts +0 -178
  487. package/src/theme/migrate.ts +0 -81
  488. package/src/theme/presets.ts +0 -135
  489. package/src/theme/radius.ts +0 -18
  490. package/src/theme/resolve.test.ts +0 -238
  491. package/src/theme/resolve.ts +0 -96
  492. package/src/theme/spacing.ts +0 -18
  493. package/src/theme/storage.test.ts +0 -126
  494. package/src/theme/storage.ts +0 -106
  495. package/src/theme/theme-colors.ts +0 -88
  496. package/src/theme/types.ts +0 -125
  497. package/src/uploads/UploadAdapter.ts +0 -35
  498. package/src/uploads/index.ts +0 -2
  499. package/src/uploads/localUpload.test.ts +0 -70
  500. package/src/uploads/localUpload.ts +0 -84
  501. package/src/validation/Validator.ts +0 -49
  502. package/src/validation/index.ts +0 -28
  503. package/src/validation/rules.ts +0 -78
  504. package/src/validation/runValidators.ts +0 -435
  505. package/src/validation/uniqueValidator.test.ts +0 -196
  506. package/src/validation/uniqueValidator.ts +0 -133
  507. package/src/validation/validators.test.ts +0 -268
  508. package/src/vite.test.ts +0 -184
  509. package/src/vite.ts +0 -787
  510. package/src/widgets/index.ts +0 -10
  511. package/src/widgets/registry.ts +0 -45
  512. package/src/widgets.test.ts +0 -592
  513. package/tsconfig.build.json +0 -11
  514. package/tsconfig.json +0 -4
  515. package/tsconfig.test.json +0 -10
  516. package/views/react/Dashboard.tsx +0 -27
  517. package/views/react/Resources/Form.tsx +0 -102
  518. package/views/react/Resources/Index.tsx +0 -49
@@ -1,1515 +0,0 @@
1
- import { Element, type ElementMeta } from '../schema/Element.js'
2
- import type { ValidationErrors } from '../validation/index.js'
3
- import type { Notification, NotificationMeta } from '../notifications/Notification.js'
4
- import type { RelationManager, RelationManagerContext } from '../RelationManager.js'
5
- import { buildImportSchema as buildImportModalSchema } from './importFactory.js'
6
- import { callPredicate } from './factoryHelpers.js'
7
- import {
8
- createAction,
9
- deleteAction,
10
- editAction,
11
- forceDeleteAction,
12
- markAsReadAction,
13
- replicateAction,
14
- restoreAction,
15
- viewAction,
16
- } from './crudFactories.js'
17
- import {
18
- bulkDeleteAction,
19
- bulkForceDeleteAction,
20
- bulkReplicateAction,
21
- bulkRestoreAction,
22
- } from './bulkFactories.js'
23
- import {
24
- relationBulkReplicateAction,
25
- relationCreateAction,
26
- relationDeleteAction,
27
- relationEditAction,
28
- relationForceDeleteAction,
29
- relationReplicateAction,
30
- relationRestoreAction,
31
- } from './relationFactories.js'
32
- import {
33
- relationAttachAction,
34
- relationBulkDetachAction,
35
- relationDetachAction,
36
- } from './m2mFactories.js'
37
-
38
- /**
39
- * Where an Action renders. `inline` is the default — appears wherever the
40
- * Action sits in the schema tree (e.g. a button inside a Card). The other
41
- * three are list-page patterns:
42
- * - `header` — top-right of a resource list (e.g. "Create new")
43
- * - `bulk` — appears in the action bar when rows are selected
44
- * - `row` — per-row dropdown menu entry
45
- */
46
- export type ActionPlacement = 'inline' | 'bulk' | 'row' | 'header'
47
-
48
- /**
49
- * Context handed to an Action's handler at dispatch time. `record` is set
50
- * for row/inline actions that operate on a single entity; `records` is set
51
- * for bulk actions. `values` carries any additional payload submitted with
52
- * the action (useful when an action has its own confirmation dialog form).
53
- * `request` is the raw `AppRequest` for handlers that need direct access
54
- * (auth, headers, etc).
55
- */
56
- export interface ActionContext {
57
- record?: unknown
58
- records?: unknown[]
59
- user?: unknown
60
- values?: Record<string, unknown>
61
- request?: unknown
62
- /**
63
- * Row-scoped context populated when this action was dispatched as a
64
- * Repeater / Builder `extraItemActions` button. `index` is the row's
65
- * 0-based position; `id` is the row's stable `__id`; `values` is the
66
- * row's submitted fields (the row's `data` body inside Builder); for
67
- * Builder, `blockType` carries the matched block name. `fieldName`
68
- * is the parent Repeater/Builder field's name — useful when a single
69
- * handler is shared across multiple repeater fields.
70
- *
71
- * Always undefined for non-row actions. Handlers that don't care about
72
- * row context just ignore this field.
73
- */
74
- row?: {
75
- index: number
76
- id: string
77
- values: Record<string, unknown>
78
- fieldName: string
79
- blockType?: string
80
- }
81
- /**
82
- * Stamped by the manager-scoped `_action` route when an action is
83
- * dispatched from a `RelationManager`'s table. Carries the freshly-
84
- * loaded parent record + relationship key so handlers can call
85
- * `parent.related(name).attach(...) / detach(...) / sync(...)`
86
- * (rudder ORM accessor) without re-loading the parent themselves.
87
- *
88
- * Always undefined for resource-level / page-level / dashboard
89
- * dispatch. M2M-aware factories (`relationAttach / relationDetach /
90
- * relationBulkDetach`) consult this and `notify` an error when it's
91
- * missing — that means the action got dispatched outside the manager
92
- * scope (misconfiguration).
93
- */
94
- relation?: {
95
- parent: unknown
96
- parentId: string
97
- relationship: string
98
- }
99
- }
100
-
101
- /** Convenience type: handlers can return either a built `Notification`
102
- * instance, its serialized meta, or arrays of either. */
103
- export type NotificationLike =
104
- | Notification
105
- | NotificationMeta
106
- | ReadonlyArray<Notification | NotificationMeta>
107
-
108
- /** Download envelope a handler can return to ask the route layer to
109
- * stream a file back instead of issuing the standard JSON / 303
110
- * response. Used by `Action.export / Action.bulkExport`; any handler
111
- * is free to return one. The route writes `Content-Type` +
112
- * `Content-Disposition: attachment; filename="…"` and ends with
113
- * `body`; the client renderer detects the disposition header and
114
- * triggers a browser download via a synthesized `<a download>`. */
115
- export interface DownloadEnvelope {
116
- filename: string
117
- contentType: string
118
- body: string
119
- }
120
-
121
- /**
122
- * Result a handler may return to influence the response. `void` is the
123
- * default — the dispatcher 303-redirects to the page the action was
124
- * triggered from. Returning `{ redirect }` overrides that with an
125
- * explicit URL. Returning `{ notify }` flashes one or more toast
126
- * notifications on the next render. Returning `{ download }` triggers
127
- * a file download (mutually exclusive with `redirect` — the route
128
- * prefers the download branch when both are set). Throw an Error to
129
- * surface as a 500 with the message.
130
- */
131
- export type ActionResult =
132
- | void
133
- | { redirect?: string; notify?: NotificationLike; download?: DownloadEnvelope }
134
-
135
- export type ActionHandler = (ctx: ActionContext) => ActionResult | Promise<ActionResult>
136
-
137
- /**
138
- * A confirmation prompt shown before the handler runs. A bare string is
139
- * shorthand for `{ message: string }`; the object form lets callers
140
- * override the dialog title and confirm-button label.
141
- */
142
- export interface ActionConfirm {
143
- title?: string
144
- message: string
145
- confirmLabel?: string
146
- }
147
-
148
- /** HTTP method for form-style actions. `'get'` is implied by `.href()`; the
149
- * others spawn a `<form>`-wrapped submit button at render time. */
150
- export type ActionMethod = 'post' | 'put' | 'patch' | 'delete'
151
-
152
- /** Visual color preset. Maps to a tailwind class group at render time.
153
- * `destructive` is what `Action.destructive()` sugar sets; the other
154
- * presets exist so users can opt-in explicitly. */
155
- export type ActionColor = 'primary' | 'destructive' | 'success' | 'warning' | 'info' | 'ghost'
156
-
157
- /** Visual size preset. Maps to button height + padding + text size. */
158
- export type ActionSize = 'sm' | 'md' | 'lg'
159
-
160
- /** Context passed to visibility / disabled callbacks. `record` is set
161
- * for single-target evaluation (row actions, edit-page header actions);
162
- * `records` for bulk evaluations; `user` from the request when wired.
163
- *
164
- * `values` is populated only when this action is being evaluated inside
165
- * a Repeater / Builder row at meta-build time (via `extraItemActions`).
166
- * It mirrors the resolver's row-scoped values map — predicates branch
167
- * on the row's submitted fields (e.g. `({ values }) => values.status !==
168
- * 'archived'`). */
169
- export interface ActionVisibilityContext {
170
- record?: unknown
171
- records?: unknown[]
172
- user?: unknown
173
- values?: Record<string, unknown>
174
- }
175
-
176
- /** Boolean-or-callback rule used by `.visible()` / `.hidden()` /
177
- * `.disabled()`. Boolean values short-circuit; functions receive the
178
- * evaluation context and return the result (sync or async).
179
- *
180
- * Async support landed with Plan #10 authorization — `Resource.canEdit`
181
- * etc. return Promise<boolean>, and the `Action.create/edit/view/delete`
182
- * factories install those predicates as visibility rules. Sync rules
183
- * keep working unchanged; the awaiter coerces both. */
184
- export type VisibilityRule =
185
- | boolean
186
- | ((ctx: ActionVisibilityContext) => boolean | Promise<boolean>)
187
-
188
- /** Modal width preset — maps to a max-width class on the Dialog popup. */
189
- export type ActionModalWidth = 'sm' | 'md' | 'lg' | 'xl'
190
-
191
- /** Horizontal alignment applied to the modal's header / body / footer
192
- * text. Matches Filament v5's `modalAlignment()` (start / center / end). */
193
- export type ActionModalAlignment = 'start' | 'center' | 'end'
194
-
195
- /** Color preset for the icon shown next to the modal heading. Mirrors
196
- * `BadgeColor` so userspace doesn't have to learn another scale. */
197
- export type ActionModalIconColor =
198
- | 'gray' | 'primary' | 'success' | 'warning' | 'destructive' | 'info'
199
-
200
- /** Context shape passed to `ReplicateOptions.getCreatedNotificationTitle`.
201
- * Single-row factories (`replicate`, `relationReplicate`) populate
202
- * `replica` (the just-created record returned by `M.create`) and
203
- * `source` (the original row). Bulk factories (`bulkReplicate`,
204
- * `relationBulkReplicate`) populate `count` (number of successful
205
- * creates) and `records` (the original selected rows). The opposite
206
- * fields are always undefined for the other call site so consumers
207
- * can branch on whichever is set. */
208
- export interface ReplicateNotificationContext {
209
- replica?: unknown
210
- source?: unknown
211
- count?: number
212
- records?: unknown[]
213
- }
214
-
215
- /** Context shape passed to `ReplicateOptions.getRedirectUrl`. Single-row
216
- * factories only — bulk variants stay on the list / manager URL
217
- * regardless. `replica` is the freshly-created record; `source` is
218
- * the original. */
219
- export interface ReplicateRedirectContext {
220
- replica: unknown
221
- source: unknown
222
- }
223
-
224
- /** Options bag for `Action.replicate` and its three siblings
225
- * (`bulkReplicate`, `relationReplicate`, `relationBulkReplicate`).
226
- * Every field optional. */
227
- export interface ReplicateOptions {
228
- /** Attribute names to drop from the replicated payload IN ADDITION TO
229
- * the always-stripped primary key + soft-delete column. Useful for
230
- * unique columns the source row holds (e.g. `slug`, `email`) so the
231
- * duplicate doesn't trip a unique constraint on save. */
232
- excludeAttributes?: ReadonlyArray<string>
233
- /** Mutate the prepared replica before it's persisted. Receives the
234
- * already-stripped attributes plus the source record; must return the
235
- * attributes to persist (mutate or replace). Useful for stamping
236
- * "Copy of" prefixes onto a name column, regenerating slugs, etc. */
237
- beforeReplicaSaved?: (
238
- replica: Record<string, unknown>,
239
- source: unknown,
240
- ) => Record<string, unknown> | Promise<Record<string, unknown>>
241
- /** Override the success notification title. Single-row factories
242
- * receive `{ replica, source }`; bulk factories receive
243
- * `{ count, records }` (`replica`/`source` undefined). Return a
244
- * string to use it; return `undefined` to fall back to the default
245
- * (`"${labelSingular} replicated"` for single-row,
246
- * `"${count} ${label(s)} replicated"` for bulk). Sync or async. */
247
- getCreatedNotificationTitle?: (
248
- ctx: ReplicateNotificationContext,
249
- ) => string | undefined | Promise<string | undefined>
250
- /** Override the redirect URL. Single-row factories only — bulk
251
- * variants don't redirect. Receives `{ replica, source }`. Return a
252
- * string to use it; return `undefined` to fall back to the default
253
- * (the new record's edit page for `replicate`; the manager list
254
- * URL for `relationReplicate`, owned by the route layer). Sync or
255
- * async. */
256
- getRedirectUrl?: (
257
- ctx: ReplicateRedirectContext,
258
- ) => string | undefined | Promise<string | undefined>
259
- }
260
-
261
- /** Structural shape of a Resource class for the factory functions —
262
- * matches `Resource.ts` exactly but keeps Action.ts free of an import
263
- * cycle. The optional fields are the Plan #10 policy predicates; their
264
- * defaults (return `true`) mean missing methods are equivalent to
265
- * "always allowed." */
266
- export interface ResourceLike {
267
- labelSingular: string
268
- /** Plural label. When unset, factories fall back to
269
- * `${labelSingular}s` (naive). Used by bulk-action notification
270
- * copy so "1 Post" / "5 Posts" render correctly. */
271
- label?: string
272
- getSlug(): string
273
- /** Cluster the resource belongs to, when registered via `Pilotiq.clusters`.
274
- * Action factories thread the cluster slug into URL builders so
275
- * `${basePath}/<cluster>/<slug>/...` round-trips correctly. */
276
- cluster?: { getSlug(): string }
277
- /** Plan #13 — soft-delete opt-in flag. When true, `Action.delete`
278
- * auto-hides on already-trashed rows; `Action.restore` /
279
- * `Action.forceDelete` auto-show on trashed rows. */
280
- softDeletes?: boolean
281
- /** Plan #13 — column name carrying the soft-delete timestamp.
282
- * Defaults to `'deletedAt'` when undefined. */
283
- deletedAtColumn?: string
284
- canCreate?(user: unknown): boolean | Promise<boolean>
285
- canEdit?(user: unknown, record: unknown): boolean | Promise<boolean>
286
- canView?(user: unknown, record: unknown): boolean | Promise<boolean>
287
- canViewAny?(user: unknown): boolean | Promise<boolean>
288
- canDelete?(user: unknown, record: unknown): boolean | Promise<boolean>
289
- canRestore?(user: unknown, record: unknown): boolean | Promise<boolean>
290
- canForceDelete?(user: unknown, record: unknown): boolean | Promise<boolean>
291
- /** Resource model adapter (set when the resource opts into auto-CRUD
292
- * via `static model = M`). Import / Export factories need access to
293
- * `create / update / find / query` to drive their writes / reads. */
294
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
295
- model?: any
296
- /** Resource table configurator. Export factory calls
297
- * `R.table(Table.make())` to discover columns + the records handler
298
- * for the "filtered" / "all" scope. */
299
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
- table?(t: any): any
301
- }
302
-
303
- /** Lazy-load the `Table` class for use inside Action handlers. Direct
304
- * module-level import would cycle (Table → Action → Table); dynamic
305
- * import inside a handler runs after both modules have finished
306
- * loading. Cached after first call so we don't pay the import cost
307
- * on every dispatched export. */
308
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
309
- let _TableClass: any | undefined
310
- async function loadTableClass(): Promise<unknown> {
311
- if (_TableClass !== undefined) return _TableClass.make()
312
- const mod = await import('../elements/Table.js')
313
- _TableClass = mod.Table
314
- return _TableClass.make()
315
- }
316
-
317
- /** Render-time meta for an action that opens a modal (with or without a
318
- * form schema). When `meta.children` is also populated by the resolver,
319
- * the modal renders those Elements as a form whose values pass through
320
- * to the handler as `ctx.values`. */
321
- export interface ActionModalMeta {
322
- heading?: string
323
- description?: string
324
- submitLabel?: string
325
- cancelLabel?: string
326
- icon?: string
327
- /** Color preset applied to the heading icon (`modalIconColor()`).
328
- * Renderer maps to a tailwind text-color class. */
329
- iconColor?: ActionModalIconColor
330
- width?: ActionModalWidth
331
- slideOver?: boolean
332
- /** Default `true`; emitted only when the user passed `false`. The
333
- * renderer turns this into Base UI's `disablePointerDismissal`. */
334
- closeByClickingAway?: boolean
335
- /** Default `true`; emitted only when `false`. Renderer cancels the
336
- * Base UI `onOpenChange` event when reason is `'escapeKey'`. */
337
- closeByEscaping?: boolean
338
- /** Sticky header inside a scrolling modal body. Default `false`. */
339
- stickyHeader?: boolean
340
- /** Sticky footer inside a scrolling modal body. Default `false`. */
341
- stickyFooter?: boolean
342
- /** Override the default autofocus behaviour. When `false` no element
343
- * inside the modal receives focus on mount; when `true` the renderer
344
- * focuses the first form input (or the submit button when there is
345
- * no form). When omitted, the legacy default applies (the submit
346
- * button autofocuses for confirm-only modals). */
347
- autofocus?: boolean
348
- /** Horizontal alignment for the heading / body / footer text. */
349
- alignment?: ActionModalAlignment
350
- /** When `true`, an X close-button renders in the top-right of the
351
- * popup. Default `false`. */
352
- closeButton?: boolean
353
- }
354
-
355
-
356
- export interface ActionMeta extends ElementMeta {
357
- type: 'action'
358
- name: string
359
- label: string
360
- placement: ActionPlacement
361
- destructive: boolean
362
- icon?: string
363
- confirm?: ActionConfirm
364
- href?: string
365
- method?: ActionMethod
366
- action?: string
367
- /** POST URL for handler-style actions. Set server-side by the route
368
- * registrar so the client knows where to dispatch. */
369
- dispatchUrl?: string
370
- /** True when this action submits its enclosing `<form>` — renders as
371
- * `<button type="submit">` and lets the form's `action`/`method`
372
- * attributes drive the request. */
373
- submit?: boolean
374
- /** When `submit` is true and this id is set, the rendered button uses
375
- * the HTML `form="<id>"` attribute so it can submit a form it lives
376
- * outside of (e.g. a Save action in the page header). */
377
- form?: string
378
- /** When `submit` is true, attach `name`+`value` to the `<button>` so
379
- * the clicked button's pair lands in the form body. Used by the
380
- * "Create & create another" pattern: a secondary submit posts a
381
- * sentinel like `{ _continueCreate: '1' }` that the server reads to
382
- * route the redirect back to the create page. */
383
- formField?: { name: string; value: string }
384
- /** Modal-style action chrome. Present when `.schema()` and/or any of
385
- * the `modalXxx` builders ran. The fields themselves arrive on
386
- * `meta.children` via the schema resolver. */
387
- modal?: ActionModalMeta
388
- /** Resolved Elements stamped by `resolveSchema` after `toMeta()` when
389
- * `.modalContentFooter([…])` was called. Renderer mounts them between
390
- * the form body and the Cancel/Submit footer — useful for an inline
391
- * Alert, summary text, or auxiliary Action that lives alongside the
392
- * primary submit. Mirrors `Section.afterHeader`'s parallel-slot
393
- * pattern; not populated when the setter wasn't used. */
394
- modalContentFooter?: Array<Record<string, unknown>>
395
- /** Color preset — drives button colors at render time. `destructive`
396
- * coincides with `destructive: true` (kept for back-compat). */
397
- color?: ActionColor
398
- /** Size preset — drives button height/padding/text-size. */
399
- size?: ActionSize
400
- /** Hover tooltip text. Wraps the rendered button in a Tooltip. */
401
- tooltip?: string
402
- /** Outlined trigger style — replaces the solid color background with
403
- * a border + transparent bg. */
404
- outlined?: boolean
405
- /** Icon-only trigger style — hides the label and renders a square
406
- * button. Requires `icon` to be set. */
407
- iconOnly?: boolean
408
- /** Optional badge shown on the trigger (e.g. unread count). */
409
- badge?: string | number
410
- badgeColor?: string
411
- /** Disabled flag set at evaluation time. The trigger renders greyed-out
412
- * and skips dispatch when true. */
413
- disabled?: boolean
414
- /** True when the action has `.visible()`, `.hidden()`, or `.disabled()`
415
- * rules — the row renderer uses this to know whether to consult the
416
- * row's `_visibleActions` / `_disabledActions` lookup. Static actions
417
- * without rules render unconditionally. */
418
- conditional?: boolean
419
- }
420
-
421
- /**
422
- * Action — a button-or-menu-entry that performs work when clicked.
423
- *
424
- * One class for all four placements; pick one via `.inline()` / `.row()` /
425
- * `.bulk()` / `.header()` (or `.placement(...)`). Actions can sit inline
426
- * inside any container Element (Card, Section, etc.) or attach to a
427
- * Resource's list page.
428
- *
429
- * Phase 1.4 ships the shape + serialization. Handler dispatch and
430
- * confirmation-form support land in Phase 2 alongside Resource lifecycle.
431
- */
432
- export class Action extends Element {
433
- readonly name: string
434
-
435
- protected _label: string
436
- protected _icon?: string
437
- protected _placement: ActionPlacement = 'inline'
438
- protected _destructive = false
439
- protected _confirm?: ActionConfirm
440
- protected _handler?: ActionHandler
441
- /**
442
- * Notification-registry handler key — set by `.handler(string)`. The
443
- * notification-action route looks this up against
444
- * `Pilotiq.notificationHandlers({…})` at request time. Mutually
445
- * exclusive with the closure `_handler` — passing a string clears
446
- * `_handler`, passing a function clears `_handlerName`. Closures can't
447
- * round-trip through a `data` JSON column; the registry path is the
448
- * persisted-notification escape hatch.
449
- */
450
- protected _handlerName?: string
451
- /**
452
- * Per-fire context for registry-handler dispatch. Round-trips through
453
- * the notification's `data.actions` JSON column verbatim and arrives
454
- * on the handler's `ctx.payload`.
455
- */
456
- protected _payload?: Record<string, unknown>
457
- /**
458
- * Filament-style chain modifier — when set on an action used inside a
459
- * `Notification.actions([…])` slot, firing the action also flips the
460
- * notification's `read_at`. No-op when the action isn't in a
461
- * notification context.
462
- */
463
- protected _markAsReadOnFire = false
464
- /**
465
- * When set, click-through opens in a new tab. Honored by the
466
- * notification action strip renderers (bell + toast); ignored by
467
- * everything else (Resource action triggers don't expose this knob —
468
- * use `target` on the underlying anchor if you need it elsewhere).
469
- */
470
- protected _openUrlInNewTab = false
471
- protected _href?: string
472
- protected _method?: ActionMethod
473
- protected _actionUrl?: string
474
- protected _dispatchUrl?: string
475
- protected _submit = false
476
- protected _formTarget?: string
477
- protected _formField?: { name: string; value: string }
478
-
479
- // Modal chrome — present whenever `.schema()` or any of the modal
480
- // builders below have been called.
481
- protected _hasModal = false
482
- protected _modalHeading?: string
483
- protected _modalDescription?: string
484
- protected _modalSubmitLabel?: string
485
- protected _modalCancelLabel?: string
486
- protected _modalIcon?: string
487
- protected _modalIconColor?: ActionModalIconColor
488
- protected _modalWidth?: ActionModalWidth
489
- protected _slideOver = false
490
- // Defaults match the existing renderer behaviour (closeable both ways,
491
- // no sticky chrome, no X button). Setters with a default arg of `false`
492
- // / `true` mirror Filament's call shapes — `closeModalByClickingAway()`
493
- // disables pointer dismiss, `stickyModalHeader()` enables sticky.
494
- protected _closeModalByClickingAway = true
495
- protected _closeModalByEscaping = true
496
- protected _stickyModalHeader = false
497
- protected _stickyModalFooter = false
498
- protected _modalAutofocus?: boolean
499
- protected _modalAlignment?: ActionModalAlignment
500
- protected _modalCloseButton = false
501
- // Filament v5 — auxiliary Elements rendered between the modal body
502
- // and the Cancel/Submit footer. Resolved by `resolveSchema` after
503
- // `toMeta()` (parallel-slot pattern, mirrors `Section.afterHeader`).
504
- protected _modalContentFooter?: Element[]
505
-
506
- // Trigger variants & cosmetics
507
- protected _color?: ActionColor
508
- protected _size?: ActionSize
509
- protected _tooltip?: string
510
- protected _outlined = false
511
- protected _iconOnly = false
512
- protected _badge?: string | number
513
- protected _badgeColor?: string
514
-
515
- // Conditional visibility / disabled rules
516
- protected _visible?: VisibilityRule
517
- protected _hidden?: VisibilityRule
518
- protected _isDisabled?: VisibilityRule
519
-
520
- private constructor(name: string) {
521
- super()
522
- this.name = name
523
- this._label = name.charAt(0).toUpperCase() + name.slice(1)
524
- }
525
-
526
- static make(name: string): Action {
527
- return new Action(name)
528
- }
529
-
530
- // ─── Resource-aware factories ─────────────────────────
531
- //
532
- // Pre-configured Action shapes that target a Resource's standard CRUD
533
- // pages. Drop into `Table.recordActions([…])`, `headerActions([…])`,
534
- // or `ViewPage.getActions(...)` — placement is stamped by the slot.
535
- // Filament-style: explicit, but ergonomic.
536
- //
537
- // Each factory uses `:id` template substitution for row context; the
538
- // renderer fills in the row's id when rendering. Header / view actions
539
- // ignore the template (no `:id` needed for create / list URLs).
540
- //
541
- // Plan #10 — each factory auto-attaches a visibility rule that
542
- // delegates to the Resource's matching policy method (`R.canCreate`
543
- // for `Action.create`, etc). When `R.canX` is unset (default returns
544
- // `true`) the action stays visible. Pass an explicit `.visible(...)`
545
- // after the factory to override.
546
-
547
- /** Create-action factory — link to `${basePath}/${R.slug}/create`.
548
- * Auto-hides when `R.canCreate(user)` returns false. */
549
- static create(R: ResourceLike, basePath: string): Action {
550
- return createAction(R, basePath)
551
- }
552
-
553
- /**
554
- * Edit-action factory — link to the resource's edit page.
555
- *
556
- * Pass `recordId` when building actions for a single-record context
557
- * (e.g. `ViewPage.getActions()`); the URL is baked at config time.
558
- * Omit `recordId` for row context (`Table.recordActions(...)`); the
559
- * URL keeps the `:id` template and the renderer substitutes per-row.
560
- *
561
- * Auto-hides when `R.canEdit(user, record)` returns false.
562
- */
563
- static edit(R: ResourceLike, basePath: string, recordId?: string): Action {
564
- return editAction(R, basePath, recordId)
565
- }
566
-
567
- /** View-action factory — link to the resource's view page. See `Action.edit` for the `recordId` semantics.
568
- * Auto-hides when `R.canView(user, record)` returns false. */
569
- static view(R: ResourceLike, basePath: string, recordId?: string): Action {
570
- return viewAction(R, basePath, recordId)
571
- }
572
-
573
- /**
574
- * Delete-action factory — POSTs to the resource's delete route,
575
- * destructive style, with a confirmation prompt referencing the
576
- * resource label. Same `recordId` semantics as `Action.edit`.
577
- * Auto-hides when `R.canDelete(user, record)` returns false.
578
- *
579
- * Plan #13 — when `R.softDeletes = true`, additionally hides on
580
- * already-trashed rows (Restore + ForceDelete take over).
581
- */
582
- static delete(R: ResourceLike, basePath: string, recordId?: string): Action {
583
- return deleteAction(R, basePath, recordId)
584
- }
585
-
586
- /**
587
- * Replicate-action factory — handler-style. Strips PK + soft-delete
588
- * column + `opts.excludeAttributes` from `ctx.record`, optionally
589
- * runs `opts.beforeReplicaSaved`, and creates a new row via
590
- * `R.model.create(...)`. Redirects to the new record's edit page
591
- * on success. Auto-hides when `R.canCreate(user)` returns false.
592
- */
593
- static replicate(
594
- R: ResourceLike,
595
- basePath: string,
596
- recordId?: string,
597
- opts: ReplicateOptions = {},
598
- ): Action {
599
- return replicateAction(R, basePath, recordId, opts)
600
- }
601
-
602
- /**
603
- * Plan #13 — Restore factory. POSTs to the resource's restore route,
604
- * success-styled, no confirm prompt. Auto-hides on live (non-trashed)
605
- * rows AND when `R.canRestore(user, record)` returns false.
606
- */
607
- static restore(R: ResourceLike, basePath: string, recordId?: string): Action {
608
- return restoreAction(R, basePath, recordId)
609
- }
610
-
611
- /**
612
- * Plan #13 — Force-delete factory. POSTs to the resource's
613
- * force-delete route, destructive-styled, with a stricter confirm
614
- * prompt. Auto-hides on live rows + when `R.canForceDelete` denies.
615
- */
616
- static forceDelete(R: ResourceLike, basePath: string, recordId?: string): Action {
617
- return forceDeleteAction(R, basePath, recordId)
618
- }
619
-
620
- // ─── Notification factories ───────────────────────────────────
621
- //
622
- // Pre-configured Action shapes that target the bell-table read /
623
- // unread endpoints mounted by `Pilotiq.databaseNotifications()`. Use
624
- // inside a custom notification inbox page's `recordActions(...)` (or
625
- // alongside a `Notification.actions([…])` slot when one ships) to
626
- // give end-users explicit "Mark as read" / "Mark as unread" buttons.
627
- //
628
- // Filament-style chain modifier `Action::make('view')->markAsRead()`
629
- // is a separate concern — it'd add an implicit mark-read side-effect
630
- // to a custom action. v1 ships only the explicit factory; the chain
631
- // modifier is deferred until a consumer asks.
632
-
633
- /**
634
- * Mark-as-read factory — POSTs to the panel's notification read
635
- * endpoint for the given notification id. The endpoint
636
- * (`${base}/_notifications/:id/read`) is mounted by
637
- * `Pilotiq.databaseNotifications()`, so calling this without
638
- * opting into the bell surface produces an Action whose POST 404s.
639
- *
640
- * `notificationId` is baked at config time. For row context where
641
- * the id varies per row, omit it and the URL keeps the `:id`
642
- * template; the renderer substitutes per-row at render time
643
- * (parallel to `Action.edit`'s row form).
644
- *
645
- * No auto-visibility. Wrap in `.visible(({ record }) => !record.readAt)`
646
- * to hide on already-read rows.
647
- */
648
- static markAsRead(basePath: string, notificationId?: string): Action {
649
- return markAsReadAction(basePath, notificationId)
650
- }
651
-
652
- // ─── Bulk factories (Plan #13) ────────────────────────────────
653
- //
654
- // Handler-style bulk actions that iterate `ctx.records`, run policy
655
- // per-row, and call the matching Resource / Model method. No new
656
- // routes — the existing `/_action/:actionName` dispatcher already
657
- // handles bulk via `ctx.records`. Drop into `bulkActions([...])`
658
- // from inside `Resource.table()`.
659
- //
660
- // Each returns a notification with the count succeeded; rows whose
661
- // policy denied (or whose call threw) are silently skipped — surface
662
- // them via your own logging if needed.
663
-
664
- /** Bulk delete — calls `R.deleteRecord(id)` per row. On a
665
- * soft-delete resource that hits `Model.delete()` which writes
666
- * `deletedAt`. */
667
- static bulkDelete(R: ResourceLike, _basePath: string): Action {
668
- return bulkDeleteAction(R, _basePath)
669
- }
670
-
671
- /** Bulk restore — calls `R.model.restore(id)` per row. */
672
- static bulkRestore(R: ResourceLike, _basePath: string): Action {
673
- return bulkRestoreAction(R, _basePath)
674
- }
675
-
676
- /** Bulk force-delete — calls `R.model.forceDelete(id)` per row. */
677
- static bulkForceDelete(R: ResourceLike, _basePath: string): Action {
678
- return bulkForceDeleteAction(R, _basePath)
679
- }
680
-
681
- /**
682
- * Bulk replicate — calls `R.model.create(...)` once per selected row
683
- * with the source row's attributes minus PK / soft-delete column /
684
- * `opts.excludeAttributes`. Sibling of `Action.replicate`.
685
- */
686
- static bulkReplicate(
687
- R: ResourceLike,
688
- _basePath: string,
689
- opts: ReplicateOptions = {},
690
- ): Action {
691
- return bulkReplicateAction(R, _basePath, opts)
692
- }
693
-
694
- // ─── Import / Export factories ────────────────────────────────
695
- //
696
- // Pre-built CSV / JSON in/out for any Resource that has `R.model`.
697
- // `Action.export` walks the configured `R.table()` records handler
698
- // in pages (so it picks up the active filter/search/sort by default,
699
- // and stays consistent with what the user is looking at). The bulk
700
- // variant exports `ctx.records` instead. `Action.import` opens a
701
- // form-modal with a `FileUpload`, parses the uploaded file, and runs
702
- // create / upsert through `R.model`.
703
- //
704
- // Internals live in `./exportFactory.ts` and `./importFactory.ts`
705
- // — kept out of this file because they need to import `Table` /
706
- // field types that themselves import `Action`. Lazy-import inside
707
- // the handler avoids the cycle (handlers run at request-time, well
708
- // after module load).
709
-
710
- /** Header-placement export — downloads the table as CSV (default) or
711
- * JSON. Visibility defaults to `R.canViewAny(user)`. Drop into
712
- * `headerActions([...])` from inside `Resource.table()`. See
713
- * `docs/plans/import-export-actions.md` for the full options bag. */
714
- static export(
715
- R: ResourceLike,
716
- _basePath: string,
717
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
718
- opts: import('./exportFactory.js').ExportOptions = {},
719
- ): Action {
720
- return Action.make('export')
721
- .label('Export')
722
- .handler(async (ctx) => {
723
- const ef = await import('./exportFactory.js')
724
- const tableInst = R.table?.(await loadTableClass())
725
- if (!tableInst) {
726
- return { notify: { title: 'Export not configured (resource has no table())', type: 'error' } as never }
727
- }
728
- const cols = ef.resolveExportColumns(opts.columns, R)
729
- if (cols.length === 0) {
730
- return { notify: { title: 'Export has no columns', type: 'error' } as never }
731
- }
732
- const maxRows = opts.maxRows ?? 50_000
733
- const records = await ef.collectExportRows(tableInst, ctx, opts.scope ?? 'filtered', maxRows, opts.chunkSize)
734
- if (records.length > maxRows) {
735
- return { notify: { title: `Export exceeded ${maxRows} rows`, type: 'error' } as never }
736
- }
737
- const rows = records.map(r => ef.buildExportRow(r, cols))
738
- const format = opts.format ?? 'csv'
739
- const { body, contentType } = ef.encodeExport(rows, cols, format)
740
- const filename = typeof opts.filename === 'function'
741
- ? opts.filename(ctx)
742
- : (opts.filename ?? ef.defaultExportFilename(R.getSlug(), format))
743
- return { download: { filename, contentType, body } }
744
- })
745
- .visible(({ user }) => callPredicate(R.canViewAny, user))
746
- }
747
-
748
- /** Bulk-placement export — downloads the rows the user selected via
749
- * the bulk-select checkboxes. Same options as `Action.export` minus
750
- * `scope` (always operates on `ctx.records`). Drop into
751
- * `bulkActions([...])`. */
752
- static bulkExport(
753
- R: ResourceLike,
754
- _basePath: string,
755
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
756
- opts: Omit<import('./exportFactory.js').ExportOptions, 'scope'> = {},
757
- ): Action {
758
- return Action.make('bulkExport')
759
- .label('Export selected')
760
- .bulk()
761
- .handler(async (ctx) => {
762
- const ef = await import('./exportFactory.js')
763
- const cols = ef.resolveExportColumns(opts.columns, R)
764
- if (cols.length === 0) {
765
- return { notify: { title: 'Export has no columns', type: 'error' } as never }
766
- }
767
- const records = ctx.records ?? []
768
- const maxRows = opts.maxRows ?? 50_000
769
- if (records.length > maxRows) {
770
- return { notify: { title: `Export exceeded ${maxRows} rows`, type: 'error' } as never }
771
- }
772
- const rows = records.map(r => ef.buildExportRow(r, cols))
773
- const format = opts.format ?? 'csv'
774
- const { body, contentType } = ef.encodeExport(rows, cols, format)
775
- const filename = typeof opts.filename === 'function'
776
- ? opts.filename(ctx)
777
- : (opts.filename ?? ef.defaultExportFilename(R.getSlug(), format))
778
- return { download: { filename, contentType, body } }
779
- })
780
- .visible(({ user }) => callPredicate(R.canViewAny, user))
781
- }
782
-
783
- /** Header-placement import — opens a modal with a `FileUpload` (and a
784
- * Mode select when `upsertBy` is set), parses the uploaded file, and
785
- * walks each row through `R.model.create` / `R.model.update`.
786
- * Visibility defaults to `R.canCreate(user)`. Drop into
787
- * `headerActions([...])` from inside `Resource.table()`. See
788
- * `docs/plans/import-export-actions.md` for the full options bag. */
789
- static import(
790
- R: ResourceLike,
791
- _basePath: string,
792
- opts: import('./importFactory.js').ImportOptions = {},
793
- ): Action {
794
- const upsertable = typeof opts.upsertBy === 'string' && opts.upsertBy.length > 0
795
- const a = Action.make('import')
796
- .label('Import')
797
- .modalHeading(`Import ${R.label ?? `${R.labelSingular}s`}`)
798
- .modalSubmitLabel('Import')
799
- .modalCancelLabel('Cancel')
800
- .handler(async (ctx) => {
801
- const M = R.model
802
- if (!M || typeof M.create !== 'function') {
803
- return { notify: { title: 'Import not configured (resource has no model.create)', type: 'error' } as never }
804
- }
805
- if (upsertable && typeof M.update !== 'function') {
806
- return { notify: { title: 'Upsert import requires model.update', type: 'error' } as never }
807
- }
808
- const fileUrl = String((ctx.values?.['file'] as unknown) ?? '')
809
- if (fileUrl.length === 0) {
810
- return { notify: { title: 'No file uploaded', type: 'error' } as never }
811
- }
812
- const ifac = await import('./importFactory.js')
813
- let text: string
814
- try {
815
- const r = await fetch(fileUrl)
816
- if (!r.ok) {
817
- return { notify: { title: `Failed to fetch upload (${r.status})`, type: 'error' } as never }
818
- }
819
- text = await r.text()
820
- } catch (err) {
821
- return { notify: { title: `Failed to read upload: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
822
- }
823
- const format = opts.format ?? (fileUrl.toLowerCase().endsWith('.json') ? 'json' : 'csv')
824
- let rows: Array<Record<string, unknown>>
825
- try {
826
- rows = ifac.parseImportText(text, format, opts.columns)
827
- } catch (err) {
828
- return { notify: { title: `Failed to parse ${format.toUpperCase()}: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
829
- }
830
- const maxRows = opts.maxRows ?? 10_000
831
- if (rows.length > maxRows) {
832
- return { notify: { title: `Import too large (${rows.length} > ${maxRows})`, type: 'error' } as never }
833
- }
834
- const mode = upsertable && (ctx.values?.['mode'] === 'upsert') ? 'upsert' : 'create'
835
- const summary = await ifac.runImport(rows, M, mode, opts, ctx)
836
- return { notify: ifac.buildImportNotification(summary, { upsert: upsertable }) as never }
837
- })
838
- .visible(({ user }) => callPredicate(R.canCreate, user))
839
-
840
- // Build the modal-form schema synchronously. importFactory has no
841
- // runtime import edge back to Action.ts (only a type-import, erased
842
- // at compile time), so the top-of-file static import below is
843
- // cycle-free.
844
- a.schema(buildImportModalSchema(opts))
845
- return a
846
- }
847
-
848
- // ─── Relation-manager factories (Plan #11 polish) ─────────────
849
- //
850
- // Mirror `Action.create / edit / delete` but build URLs under the
851
- // parent record: `${base}/${parentSlug}/${parentId}/${rel}/...`.
852
- // Designed to be called inside `RelationManager.static table()` —
853
- // the page-data builder pipes `RelationManagerContext` into that
854
- // configurator so users get `basePath`, `parentId`, and the
855
- // discovered Related resource without threading them by hand.
856
- //
857
- // Visibility predicates use `safeManagerPolicy` so the manager's
858
- // `canX` (when overridden) wins, otherwise falls through to the
859
- // related Resource's `canX`. Throws absorb as `false`.
860
- //
861
- // `:id` template substitution still happens at render time for row
862
- // context — the same mechanism that drives `Action.edit / delete`.
863
- // The parent's id is baked into the URL at config time (it's known
864
- // upfront from `ctx.parentId`), so `:id` unambiguously refers to
865
- // the row's *child* id.
866
-
867
- /** Relation create-action factory — link to
868
- * `${base}/${parentSlug}/${parentId}/${relationship}/create`.
869
- * Visibility delegates to `M.canCreate(user, parentRecord)` with
870
- * fall-through to the related Resource's `canCreate(user)`.
871
- */
872
- static relationCreate(
873
- M: typeof RelationManager,
874
- ctx: RelationManagerContext,
875
- ): Action {
876
- return relationCreateAction(M, ctx)
877
- }
878
-
879
- /** Relation edit-action factory — link to
880
- * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/edit`.
881
- */
882
- static relationEdit(
883
- M: typeof RelationManager,
884
- ctx: RelationManagerContext,
885
- recordId?: string,
886
- ): Action {
887
- return relationEditAction(M, ctx, recordId)
888
- }
889
-
890
- /** Relation delete-action factory — POST to
891
- * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/delete`.
892
- */
893
- static relationDelete(
894
- M: typeof RelationManager,
895
- ctx: RelationManagerContext,
896
- recordId?: string,
897
- ): Action {
898
- return relationDeleteAction(M, ctx, recordId)
899
- }
900
-
901
- /**
902
- * Plan #13 polish — Restore factory for relation managers. POSTs to
903
- * the relation-restore route. Auto-hides on live rows + policy denies.
904
- */
905
- static relationRestore(
906
- M: typeof RelationManager,
907
- ctx: RelationManagerContext,
908
- recordId?: string,
909
- ): Action {
910
- return relationRestoreAction(M, ctx, recordId)
911
- }
912
-
913
- /**
914
- * Plan #13 polish — Force-delete factory for relation managers.
915
- */
916
- static relationForceDelete(
917
- M: typeof RelationManager,
918
- ctx: RelationManagerContext,
919
- recordId?: string,
920
- ): Action {
921
- return relationForceDeleteAction(M, ctx, recordId)
922
- }
923
-
924
- // ─── Relation-manager replicate factories ─────────────────
925
- //
926
- // Sibling of `Action.replicate / bulkReplicate` scoped to a
927
- // RelationManager. Operates on the **related** Resource's model and
928
- // **forces** the parent attachment back onto the replica:
929
- //
930
- // - `hasMany` — re-stamps `<foreignKey> = ctx.parentId` from the
931
- // parent's `static relations[name]` descriptor. Defends against a
932
- // tampered POST body (or a source row loaded from a different
933
- // parent's children) silently re-attaching the new row to a
934
- // different parent.
935
- // - `morphMany` — re-stamps `<morphName>Id` + `<morphName>Type` via
936
- // `computeMorphPayload(parentRecord, descriptor)`, same auto-fill
937
- // the relation-create POST handler uses on the create form.
938
- // - M2M (`belongsToMany / morphToMany / morphedByMany`) — auto-hides.
939
- // Replicate doesn't fit pivot semantics; users create the related
940
- // record via its own Resource, then attach via `relationAttach`.
941
- // - `morphTo` — auto-hides. Child-side polymorphic relations don't
942
- // have a single owner to pin to (the row's existing morph cols
943
- // already point somewhere; cloning is the user's job, not ours).
944
- //
945
- // Both factories dispatch through the manager-scoped
946
- // `_action/:actionName` route already wired in `routes.ts`. The route
947
- // resolves `ctx.record` (and `ctx.records` for bulk) via
948
- // `Related.model.find(id)`, and stamps `ctx.relation = { parent,
949
- // parentId, relationship }` so the handlers can read the live parent
950
- // without re-loading.
951
- //
952
- // Visibility delegates to `safeManagerPolicy(M, 'canCreate', Related,
953
- // user, parentRecord)` — the manager's `canCreate` (when overridden)
954
- // wins, otherwise falls through to the related Resource's `canCreate`.
955
-
956
- /**
957
- * Relation row-replicate factory. Clones the row's child record
958
- * inside the manager's parent scope. Strips PK + soft-delete column
959
- * + `opts.excludeAttributes`, then force-pins the parent attachment
960
- * columns before optional `beforeReplicaSaved` runs.
961
- */
962
- static relationReplicate(
963
- M: typeof RelationManager,
964
- ctx: RelationManagerContext,
965
- recordId?: string,
966
- opts: ReplicateOptions = {},
967
- ): Action {
968
- return relationReplicateAction(M, ctx, recordId, opts)
969
- }
970
-
971
- /**
972
- * Bulk sibling — replicates every selected child row inside the
973
- * manager's parent scope. Same strip + force-pin pipeline per row.
974
- */
975
- static relationBulkReplicate(
976
- M: typeof RelationManager,
977
- ctx: RelationManagerContext,
978
- opts: ReplicateOptions = {},
979
- ): Action {
980
- return relationBulkReplicateAction(M, ctx, opts)
981
- }
982
-
983
- // ─── M2M relation factories ───────────────────────────────
984
- //
985
- // Sibling of `relationCreate / Edit / Delete` for every M2M mode
986
- // (`belongsToMany`, `morphToMany` (owning polymorphic side),
987
- // `morphedByMany` (inverse polymorphic side)). All three modes share
988
- // the same `attach` / `detach` / `sync` accessor surface — the rudder
989
- // ORM stamps + filters the polymorphic discriminator on the morph
990
- // variants automatically, so pilotiq's pivot factories are mode-agnostic
991
- // beyond the visibility gate.
992
- //
993
- // Three factories: `relationAttach` (header, modal-form picker →
994
- // POST `_action/relationAttach`), `relationDetach` (row, direct POST
995
- // to `_detach/:childId`), `relationBulkDetach` (bulk, handler-
996
- // dispatched). The first and third route through the manager-scoped
997
- // `_action/:actionName` endpoint (added in routes.ts) so handlers
998
- // see `ctx.relation = { parent, parentId, relationship }`.
999
- //
1000
- // All three auto-hide outside any M2M mode so dropping a factory into
1001
- // a non-M2M manager is a no-op (visible=false) instead of a confusing
1002
- // 404.
1003
-
1004
- /** Header-placement attach factory — opens a modal with a SelectField
1005
- * listing related records that aren't already attached, and POSTs the
1006
- * selected id to the manager's `_action/relationAttach` endpoint.
1007
- * Visibility delegates to `M.canAttach(user, parentRecord)` AND
1008
- * guards against being dropped into a non-M2M manager. */
1009
- static relationAttach(
1010
- M: typeof RelationManager,
1011
- ctx: RelationManagerContext,
1012
- ): Action {
1013
- return relationAttachAction(M, ctx)
1014
- }
1015
-
1016
- /** Row-placement detach factory — POSTs to
1017
- * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/_detach`,
1018
- * destructive style with a "Detach" confirmation. */
1019
- static relationDetach(
1020
- M: typeof RelationManager,
1021
- ctx: RelationManagerContext,
1022
- recordId?: string,
1023
- ): Action {
1024
- return relationDetachAction(M, ctx, recordId)
1025
- }
1026
-
1027
- /** Bulk-placement bulk-detach factory — handler-dispatched. Calls
1028
- * `parent.related(rel).detach(ids)` for the selected rows. */
1029
- static relationBulkDetach(
1030
- M: typeof RelationManager,
1031
- ctx: RelationManagerContext,
1032
- ): Action {
1033
- return relationBulkDetachAction(M, ctx)
1034
- }
1035
-
1036
- label(l: string): this { this._label = l; return this }
1037
- icon(i: string): this { this._icon = i; return this }
1038
-
1039
- // ─── Placement ────────────────────────────────────────
1040
-
1041
- placement(p: ActionPlacement): this { this._placement = p; return this }
1042
- inline(): this { return this.placement('inline') }
1043
- row(): this { return this.placement('row') }
1044
- bulk(): this { return this.placement('bulk') }
1045
- header(): this { return this.placement('header') }
1046
-
1047
- // ─── Behavior ─────────────────────────────────────────
1048
-
1049
- destructive(v = true): this {
1050
- this._destructive = v
1051
- if (v && this._color === undefined) this._color = 'destructive'
1052
- return this
1053
- }
1054
-
1055
- /** Set the visual color. `destructive` is also set by `.destructive()`. */
1056
- color(c: ActionColor): this { this._color = c; return this }
1057
-
1058
- /** Set the size preset (sm | md | lg). Default is `md`. */
1059
- size(s: ActionSize): this { this._size = s; return this }
1060
-
1061
- /** Hover tooltip. Wraps the button in a Tooltip primitive. */
1062
- tooltip(t: string): this { this._tooltip = t; return this }
1063
-
1064
- /** Outlined trigger style — border + transparent bg instead of solid color. */
1065
- outlined(v = true): this { this._outlined = v; return this }
1066
-
1067
- /** Icon-only trigger style. Renders a square button with just the icon;
1068
- * the label is used as `aria-label`. Requires `.icon()` to be set. */
1069
- iconButton(v = true): this { this._iconOnly = v; return this }
1070
-
1071
- /** Show a small badge on the trigger (e.g. unread count). */
1072
- badge(value: string | number): this { this._badge = value; return this }
1073
-
1074
- /** Optional color class for the badge (e.g. 'bg-emerald-500'). */
1075
- badgeColor(c: string): this { this._badgeColor = c; return this }
1076
-
1077
- // ─── Conditional visibility / disabled ───────────────
1078
-
1079
- /** Show the action only when `rule` is truthy. Pair with a function for
1080
- * record-aware visibility (e.g. `({ record }) => !record.archived`).
1081
- * Row-placement actions are evaluated per-row at table-load time;
1082
- * other placements are evaluated at schema-resolve time with the
1083
- * page-level context. */
1084
- visible(rule: VisibilityRule): this { this._visible = rule; return this }
1085
-
1086
- /** Inverse of `visible` — hide the action when `rule` is truthy.
1087
- * Both rules combine via AND: visible if `visible !== false` AND
1088
- * `hidden !== true`. */
1089
- hidden(rule: VisibilityRule): this { this._hidden = rule; return this }
1090
-
1091
- /** Disable (render greyed-out and skip dispatch) when `rule` is truthy.
1092
- * Disabled actions still appear in the UI, unlike hidden ones. */
1093
- disabled(rule: VisibilityRule): this { this._isDisabled = rule; return this }
1094
-
1095
- /** Policy-style alias for `.visible(fn)` — semantically identical
1096
- * but reads better when guarding by user permissions. */
1097
- authorize(rule: VisibilityRule): this { return this.visible(rule) }
1098
-
1099
- /** Evaluate the visibility / disabled rules with the given context.
1100
- * Defaults: visible = true, disabled = false. Both `visible` and
1101
- * `hidden` are folded in: `visible: visible !== false && hidden !== true`.
1102
- *
1103
- * Async to support Plan #10 — visibility rules can return Promise<bool>
1104
- * (for `Resource.canX(user, record)` integration). Throwing rules are
1105
- * treated as fail-closed (`visible: false` / `disabled: true`). */
1106
- async evaluate(ctx: ActionVisibilityContext = {}): Promise<{ visible: boolean; disabled: boolean }> {
1107
- const evalRule = async (rule: VisibilityRule | undefined, fallback: boolean): Promise<boolean> => {
1108
- if (rule === undefined) return fallback
1109
- if (typeof rule !== 'function') return rule
1110
- try {
1111
- return await rule(ctx)
1112
- } catch {
1113
- // Fail closed — a throwing rule shouldn't accidentally show a
1114
- // destructive action.
1115
- return !fallback
1116
- }
1117
- }
1118
- const [visibleRaw, hiddenRaw, disabledRaw] = await Promise.all([
1119
- evalRule(this._visible, true),
1120
- evalRule(this._hidden, false),
1121
- evalRule(this._isDisabled, false),
1122
- ])
1123
- return {
1124
- visible: visibleRaw && !hiddenRaw,
1125
- disabled: disabledRaw,
1126
- }
1127
- }
1128
-
1129
- /** True when any visibility / hidden / disabled rule is set. Useful for
1130
- * the resolver to know whether per-row evaluation is needed for a
1131
- * row-placement action. */
1132
- hasVisibilityRules(): boolean {
1133
- return this._visible !== undefined || this._hidden !== undefined || this._isDisabled !== undefined
1134
- }
1135
-
1136
- /**
1137
- * Prompt the user before running the handler. Pass a string for a simple
1138
- * "are you sure?" message, or an object for full control.
1139
- */
1140
- confirm(prompt: string | ActionConfirm): this {
1141
- this._confirm = typeof prompt === 'string' ? { message: prompt } : prompt
1142
- return this
1143
- }
1144
-
1145
- /**
1146
- * Server-side handler. Two shapes:
1147
- *
1148
- * - **Closure** — `handler(async ctx => …)`. Dispatched against the
1149
- * current page's `_action/:name` endpoint. Closures don't survive
1150
- * serialization, so they can't ride a persisted notification —
1151
- * `Notification.toDatabase()` rejects them with a clear error.
1152
- *
1153
- * - **Registry key** — `handler('archive-project')`. Looked up against
1154
- * `Pilotiq.notificationHandlers({…})` at request time. Round-trips
1155
- * through a `Notification`'s `data.actions` JSON column, so this is
1156
- * the path to take when an action lives on a row that may be
1157
- * clicked days later.
1158
- *
1159
- * Mutually exclusive — setting one clears the other.
1160
- */
1161
- handler(fnOrName: ActionHandler | string): this {
1162
- if (typeof fnOrName === 'string') {
1163
- this._handlerName = fnOrName
1164
- delete this._handler
1165
- } else {
1166
- this._handler = fnOrName
1167
- delete this._handlerName
1168
- }
1169
- return this
1170
- }
1171
-
1172
- /**
1173
- * Per-fire context for registry-handler dispatch. JSON-serializable
1174
- * keys only — values flow through the notification's `data.actions`
1175
- * column and arrive on the handler's `ctx.payload`.
1176
- *
1177
- * Action.make('archive')
1178
- * .handler('archive-project')
1179
- * .payload({ projectId: 123 })
1180
- */
1181
- payload(data: Record<string, unknown>): this {
1182
- this._payload = data
1183
- return this
1184
- }
1185
-
1186
- /**
1187
- * Filament-style chain modifier — when this action lives inside a
1188
- * `Notification.actions([…])` slot, firing it also flips the
1189
- * notification's `read_at`. No-op for actions used outside the
1190
- * notification context (Resource actions, etc).
1191
- *
1192
- * Action.make('view').url('/projects/123').markAsRead()
1193
- *
1194
- * On url/post actions the bell client fires the read POST as a
1195
- * client-side side-effect *before* navigating/submitting; on
1196
- * registry-handler actions the route flips `read_at` server-side
1197
- * after the handler runs (one round-trip, not two).
1198
- */
1199
- markAsRead(v = true): this {
1200
- this._markAsReadOnFire = v
1201
- return this
1202
- }
1203
-
1204
- /**
1205
- * Open the action's url in a new tab. Honored by the notification
1206
- * action-strip renderers; equivalent to `target="_blank"` on the
1207
- * underlying anchor.
1208
- */
1209
- openUrlInNewTab(v = true): this {
1210
- this._openUrlInNewTab = v
1211
- return this
1212
- }
1213
-
1214
- // ─── Modal / form-modal action ────────────────────────
1215
-
1216
- /**
1217
- * Attach a form schema that opens in a modal Dialog when the action is
1218
- * triggered. The submitted values flow through validation + coercion
1219
- * server-side and arrive on the handler's `ctx.values`. Triggers modal
1220
- * chrome (heading / submit button / cancel button) if not configured
1221
- * via the other modal builders below.
1222
- */
1223
- schema(elements: Element[]): this {
1224
- this._children = elements
1225
- this._hasModal = true
1226
- return this
1227
- }
1228
-
1229
- modalHeading(s: string): this { this._modalHeading = s; this._hasModal = true; return this }
1230
- modalDescription(s: string): this { this._modalDescription = s; this._hasModal = true; return this }
1231
- modalSubmitLabel(s: string): this { this._modalSubmitLabel = s; this._hasModal = true; return this }
1232
- modalCancelLabel(s: string): this { this._modalCancelLabel = s; this._hasModal = true; return this }
1233
- /** Filament v5 alias for `modalSubmitLabel`. Reads more naturally
1234
- * alongside `modalCancelActionLabel` when both are set. */
1235
- modalSubmitActionLabel(s: string): this { return this.modalSubmitLabel(s) }
1236
- /** Filament v5 alias for `modalCancelLabel`. */
1237
- modalCancelActionLabel(s: string): this { return this.modalCancelLabel(s) }
1238
- modalIcon(i: string): this { this._modalIcon = i; this._hasModal = true; return this }
1239
- modalIconColor(c: ActionModalIconColor): this {
1240
- this._modalIconColor = c
1241
- this._hasModal = true
1242
- return this
1243
- }
1244
- modalWidth(w: ActionModalWidth): this { this._modalWidth = w; this._hasModal = true; return this }
1245
- modalAlignment(a: ActionModalAlignment): this {
1246
- this._modalAlignment = a
1247
- this._hasModal = true
1248
- return this
1249
- }
1250
- slideOver(v = true): this { this._slideOver = v; this._hasModal = true; return this }
1251
-
1252
- /**
1253
- * Disable / re-enable closing the modal by clicking outside the popup.
1254
- * Default: enabled. Calling without an arg disables (matches Filament's
1255
- * `->closeModalByClickingAway(false)` shape — most uses are to gate
1256
- * accidental dismissal of important modals).
1257
- */
1258
- closeModalByClickingAway(v: boolean = false): this {
1259
- this._closeModalByClickingAway = v
1260
- this._hasModal = true
1261
- return this
1262
- }
1263
-
1264
- /**
1265
- * Disable / re-enable closing the modal with the Escape key. Default:
1266
- * enabled. Same shape as `closeModalByClickingAway`. Useful when a
1267
- * partially-completed multi-step modal would lose work on a stray Esc.
1268
- */
1269
- closeModalByEscaping(v: boolean = false): this {
1270
- this._closeModalByEscaping = v
1271
- this._hasModal = true
1272
- return this
1273
- }
1274
-
1275
- /** Sticky header inside the modal body. Useful when the body scrolls
1276
- * past the heading on long forms. Default: off. */
1277
- stickyModalHeader(v: boolean = true): this {
1278
- this._stickyModalHeader = v
1279
- this._hasModal = true
1280
- return this
1281
- }
1282
-
1283
- /** Sticky footer inside the modal body. Pairs naturally with
1284
- * `stickyModalHeader` on long forms — keeps the Submit / Cancel
1285
- * buttons visible while the user scrolls. Default: off. */
1286
- stickyModalFooter(v: boolean = true): this {
1287
- this._stickyModalFooter = v
1288
- this._hasModal = true
1289
- return this
1290
- }
1291
-
1292
- /**
1293
- * Override the default autofocus behaviour. `true` focuses the first
1294
- * form input on mount (or the submit button when there is no form);
1295
- * `false` disables autofocus entirely. Omit to keep the legacy default
1296
- * (submit button autofocuses for confirm-only modals).
1297
- */
1298
- modalAutofocus(v: boolean = true): this {
1299
- this._modalAutofocus = v
1300
- this._hasModal = true
1301
- return this
1302
- }
1303
-
1304
- /** Render an X close button in the top-right corner of the popup.
1305
- * Default: off (the Cancel button in the footer is the documented
1306
- * affordance). Useful for slide-over panels where the footer is
1307
- * far from the top of the viewport. */
1308
- modalCloseButton(v: boolean = true): this {
1309
- this._modalCloseButton = v
1310
- this._hasModal = true
1311
- return this
1312
- }
1313
-
1314
- /**
1315
- * Auxiliary Elements rendered between the modal body and the
1316
- * Cancel/Submit footer. Useful for an inline `Alert` summarising
1317
- * the consequence of the action, supplemental `Text` / `Heading`,
1318
- * or a secondary `Action` (e.g. a "Learn more" link) that sits
1319
- * alongside the primary submit. Resolved through the standard
1320
- * walker so any `.visible() / .disabled()` rules on inner Actions
1321
- * evaluate the same way as anywhere else.
1322
- *
1323
- * Mirrors `Section.afterHeader([Action…])`'s parallel-slot pattern;
1324
- * unlike `afterHeader`, this slot accepts any Element type since
1325
- * non-Action chrome (alerts, copy) is the documented use case in
1326
- * Filament v5's `modalContentFooter` API.
1327
- */
1328
- modalContentFooter(elements: Element[]): this {
1329
- this._modalContentFooter = elements
1330
- this._hasModal = true
1331
- return this
1332
- }
1333
-
1334
- /** Read-only accessor used by `resolveSchema` to walk the slot. */
1335
- getModalContentFooter(): Element[] | undefined { return this._modalContentFooter }
1336
-
1337
- // ─── Link / form modes ────────────────────────────────
1338
-
1339
- /**
1340
- * Render this action as a link to `url`. Mutually exclusive with
1341
- * `.method()` — setting `href` clears any prior method/action URL.
1342
- */
1343
- href(url: string): this {
1344
- this._href = url
1345
- delete this._method
1346
- delete this._actionUrl
1347
- return this
1348
- }
1349
-
1350
- /**
1351
- * Sugar alias for `.href(url)` — matches Filament's `->url(...)` API
1352
- * and reads more naturally inside `Notification.actions([…])`.
1353
- */
1354
- url(href: string): this { return this.href(href) }
1355
-
1356
- /**
1357
- * Render this action as a form-style submit button using `method`. Pair
1358
- * with `.action(url)` to set the form's action URL — falls back to the
1359
- * current page URL otherwise.
1360
- */
1361
- method(m: ActionMethod): this {
1362
- this._method = m
1363
- delete this._href
1364
- return this
1365
- }
1366
-
1367
- /** Form action URL — only meaningful when `.method()` is set. */
1368
- action(url: string): this {
1369
- this._actionUrl = url
1370
- delete this._href
1371
- return this
1372
- }
1373
-
1374
- /**
1375
- * Render-time URL the client should POST to when invoking this
1376
- * action's handler. Set by the route registrar — users don't normally
1377
- * call this directly. Format: `${pageUrl}/_action/${action.name}`.
1378
- */
1379
- dispatchUrl(url: string): this {
1380
- this._dispatchUrl = url
1381
- return this
1382
- }
1383
-
1384
- /**
1385
- * Mark this action as the form-submit button for its enclosing
1386
- * `<form>`. Renders as `<button type="submit">` and relies on the form
1387
- * itself to carry `action` + `method`. Mutually exclusive with
1388
- * `.href()` / `.method()` / handler-style.
1389
- */
1390
- submit(): this {
1391
- this._submit = true
1392
- delete this._href
1393
- delete this._method
1394
- delete this._actionUrl
1395
- delete this._dispatchUrl
1396
- return this
1397
- }
1398
-
1399
- /**
1400
- * Target a specific `<form id="">` when this is a submit action — uses
1401
- * the HTML `form` attribute so the button can submit a form it doesn't
1402
- * live inside. Required when the submit action sits in the page
1403
- * header (outside the form's DOM subtree).
1404
- */
1405
- form(formId: string): this {
1406
- this._formTarget = formId
1407
- return this
1408
- }
1409
-
1410
- /**
1411
- * Attach a `name`+`value` pair to a submit button so it round-trips
1412
- * through the form body when this specific button is clicked. Wires
1413
- * the "Create & create another" pattern: the secondary submit posts
1414
- * `{ _continueCreate: '1' }` and the server routes the redirect back
1415
- * to the create page. Browsers natively only include the clicked
1416
- * submit button's name/value in `FormData`; the FormRenderer threads
1417
- * `event.submitter` into the `FormData` constructor to preserve that.
1418
- */
1419
- formField(name: string, value: string = '1'): this {
1420
- this._formField = { name, value }
1421
- return this
1422
- }
1423
-
1424
- // ─── Getters ──────────────────────────────────────────
1425
-
1426
- getLabel(): string { return this._label }
1427
- getPlacement(): ActionPlacement { return this._placement }
1428
- isDestructive(): boolean { return this._destructive }
1429
- getHandler(): ActionHandler | undefined { return this._handler }
1430
- /** Registry key set by `.handler(string)`; undefined when the handler
1431
- * is a closure (or unset). Mutually exclusive with `getHandler()`. */
1432
- getHandlerName(): string | undefined { return this._handlerName }
1433
- /** Per-fire context set by `.payload({…})`. Empty `{}` when unset
1434
- * (callers can spread without a null check). */
1435
- getPayload(): Record<string, unknown> { return this._payload ?? {} }
1436
- /** Filament chain modifier — true when `.markAsRead()` was called.
1437
- * Read by `Notification.actions([…])` serializer; ignored elsewhere. */
1438
- isMarkAsReadOnFire(): boolean { return this._markAsReadOnFire }
1439
- /** Honored by the notification action-strip renderers; equivalent to
1440
- * `target="_blank"` on the underlying anchor. */
1441
- isOpenUrlInNewTab(): boolean { return this._openUrlInNewTab }
1442
- getHref(): string | undefined { return this._href }
1443
- getMethod(): ActionMethod | undefined { return this._method }
1444
- getActionUrl(): string | undefined { return this._actionUrl }
1445
- getDispatchUrl(): string | undefined { return this._dispatchUrl }
1446
- isSubmit(): boolean { return this._submit }
1447
- getFormTarget(): string | undefined { return this._formTarget }
1448
- getFormField(): { name: string; value: string } | undefined { return this._formField }
1449
- hasModal(): boolean { return this._hasModal }
1450
- /** Schema fields stored as children; `getChildren()` returns the same. */
1451
- getSchema(): Element[] { return this._children ?? [] }
1452
- getColor(): ActionColor | undefined { return this._color }
1453
- getIcon(): string | undefined { return this._icon }
1454
- getSize(): ActionSize | undefined { return this._size }
1455
- getTooltip(): string | undefined { return this._tooltip }
1456
- isOutlined(): boolean { return this._outlined }
1457
- isIconOnly(): boolean { return this._iconOnly }
1458
- getBadge(): string | number | undefined { return this._badge }
1459
-
1460
- // ─── Element contract ────────────────────────────────
1461
-
1462
- getType(): string { return 'action' }
1463
-
1464
- override toMeta(): ActionMeta {
1465
- const modal: ActionModalMeta | undefined = this._hasModal ? {
1466
- ...(this._modalHeading !== undefined ? { heading: this._modalHeading } : {}),
1467
- ...(this._modalDescription !== undefined ? { description: this._modalDescription } : {}),
1468
- ...(this._modalSubmitLabel !== undefined ? { submitLabel: this._modalSubmitLabel } : {}),
1469
- ...(this._modalCancelLabel !== undefined ? { cancelLabel: this._modalCancelLabel } : {}),
1470
- ...(this._modalIcon !== undefined ? { icon: this._modalIcon } : {}),
1471
- ...(this._modalIconColor !== undefined ? { iconColor: this._modalIconColor } : {}),
1472
- ...(this._modalWidth !== undefined ? { width: this._modalWidth } : {}),
1473
- ...(this._modalAlignment !== undefined ? { alignment: this._modalAlignment } : {}),
1474
- ...(this._slideOver ? { slideOver: true } : {}),
1475
- // Boolean defaults are emitted only when the user has flipped them
1476
- // away from the default — keeps the wire-shape unchanged for the
1477
- // common case (no setter ever called).
1478
- ...(this._closeModalByClickingAway === false ? { closeByClickingAway: false } : {}),
1479
- ...(this._closeModalByEscaping === false ? { closeByEscaping: false } : {}),
1480
- ...(this._stickyModalHeader ? { stickyHeader: true } : {}),
1481
- ...(this._stickyModalFooter ? { stickyFooter: true } : {}),
1482
- ...(this._modalCloseButton ? { closeButton: true } : {}),
1483
- ...(this._modalAutofocus !== undefined ? { autofocus: this._modalAutofocus } : {}),
1484
- } : undefined
1485
- return {
1486
- type: 'action',
1487
- name: this.name,
1488
- label: this._label,
1489
- placement: this._placement,
1490
- destructive: this._destructive,
1491
- ...(this._icon ? { icon: this._icon } : {}),
1492
- ...(this._confirm ? { confirm: this._confirm } : {}),
1493
- ...(this._href ? { href: this._href } : {}),
1494
- ...(this._method ? { method: this._method } : {}),
1495
- ...(this._actionUrl ? { action: this._actionUrl } : {}),
1496
- ...(this._dispatchUrl ? { dispatchUrl: this._dispatchUrl } : {}),
1497
- ...(this._submit ? { submit: true } : {}),
1498
- ...(this._formTarget ? { form: this._formTarget } : {}),
1499
- ...(this._formField ? { formField: this._formField } : {}),
1500
- ...(modal ? { modal } : {}),
1501
- ...(this._color ? { color: this._color } : {}),
1502
- ...(this._size ? { size: this._size } : {}),
1503
- ...(this._tooltip ? { tooltip: this._tooltip } : {}),
1504
- ...(this._outlined ? { outlined: true } : {}),
1505
- ...(this._iconOnly ? { iconOnly: true } : {}),
1506
- ...(this._badge !== undefined ? { badge: this._badge } : {}),
1507
- ...(this._badgeColor ? { badgeColor: this._badgeColor } : {}),
1508
- ...(this.hasVisibilityRules() ? { conditional: true } : {}),
1509
- }
1510
- }
1511
- }
1512
-
1513
- /** Re-export for routes/dispatch consumers that need to type-narrow on
1514
- * action validation failures. */
1515
- export type { ValidationErrors }