@pilotiq/pilotiq 0.1.0

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 (1409) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +11 -0
  3. package/CLAUDE.md +207 -0
  4. package/LICENSE +21 -0
  5. package/dist/Cluster.d.ts +56 -0
  6. package/dist/Cluster.d.ts.map +1 -0
  7. package/dist/Cluster.js +62 -0
  8. package/dist/Cluster.js.map +1 -0
  9. package/dist/Column.d.ts +378 -0
  10. package/dist/Column.d.ts.map +1 -0
  11. package/dist/Column.js +434 -0
  12. package/dist/Column.js.map +1 -0
  13. package/dist/Global.d.ts +123 -0
  14. package/dist/Global.d.ts.map +1 -0
  15. package/dist/Global.js +124 -0
  16. package/dist/Global.js.map +1 -0
  17. package/dist/Page.d.ts +90 -0
  18. package/dist/Page.d.ts.map +1 -0
  19. package/dist/Page.js +107 -0
  20. package/dist/Page.js.map +1 -0
  21. package/dist/Pilotiq.d.ts +505 -0
  22. package/dist/Pilotiq.d.ts.map +1 -0
  23. package/dist/Pilotiq.js +463 -0
  24. package/dist/Pilotiq.js.map +1 -0
  25. package/dist/PilotiqRegistry.d.ts +10 -0
  26. package/dist/PilotiqRegistry.d.ts.map +1 -0
  27. package/dist/PilotiqRegistry.js +32 -0
  28. package/dist/PilotiqRegistry.js.map +1 -0
  29. package/dist/PilotiqServiceProvider.d.ts +16 -0
  30. package/dist/PilotiqServiceProvider.d.ts.map +1 -0
  31. package/dist/PilotiqServiceProvider.js +57 -0
  32. package/dist/PilotiqServiceProvider.js.map +1 -0
  33. package/dist/RelationManager.d.ts +372 -0
  34. package/dist/RelationManager.d.ts.map +1 -0
  35. package/dist/RelationManager.js +342 -0
  36. package/dist/RelationManager.js.map +1 -0
  37. package/dist/RenderHook.d.ts +86 -0
  38. package/dist/RenderHook.d.ts.map +1 -0
  39. package/dist/RenderHook.js +116 -0
  40. package/dist/RenderHook.js.map +1 -0
  41. package/dist/Resource.d.ts +290 -0
  42. package/dist/Resource.d.ts.map +1 -0
  43. package/dist/Resource.js +362 -0
  44. package/dist/Resource.js.map +1 -0
  45. package/dist/RightPanel.d.ts +92 -0
  46. package/dist/RightPanel.d.ts.map +1 -0
  47. package/dist/RightPanel.js +61 -0
  48. package/dist/RightPanel.js.map +1 -0
  49. package/dist/Tab.d.ts +92 -0
  50. package/dist/Tab.d.ts.map +1 -0
  51. package/dist/Tab.js +93 -0
  52. package/dist/Tab.js.map +1 -0
  53. package/dist/UserMenuItem.d.ts +76 -0
  54. package/dist/UserMenuItem.d.ts.map +1 -0
  55. package/dist/UserMenuItem.js +87 -0
  56. package/dist/UserMenuItem.js.map +1 -0
  57. package/dist/actions/Action.d.ts +888 -0
  58. package/dist/actions/Action.d.ts.map +1 -0
  59. package/dist/actions/Action.js +1652 -0
  60. package/dist/actions/Action.js.map +1 -0
  61. package/dist/actions/ActionGroup.d.ts +85 -0
  62. package/dist/actions/ActionGroup.d.ts.map +1 -0
  63. package/dist/actions/ActionGroup.js +132 -0
  64. package/dist/actions/ActionGroup.js.map +1 -0
  65. package/dist/actions/attachFactory.d.ts +67 -0
  66. package/dist/actions/attachFactory.d.ts.map +1 -0
  67. package/dist/actions/attachFactory.js +115 -0
  68. package/dist/actions/attachFactory.js.map +1 -0
  69. package/dist/actions/exportFactory.d.ts +88 -0
  70. package/dist/actions/exportFactory.d.ts.map +1 -0
  71. package/dist/actions/exportFactory.js +144 -0
  72. package/dist/actions/exportFactory.js.map +1 -0
  73. package/dist/actions/importFactory.d.ts +97 -0
  74. package/dist/actions/importFactory.d.ts.map +1 -0
  75. package/dist/actions/importFactory.js +143 -0
  76. package/dist/actions/importFactory.js.map +1 -0
  77. package/dist/actions/index.d.ts +3 -0
  78. package/dist/actions/index.d.ts.map +1 -0
  79. package/dist/actions/index.js +3 -0
  80. package/dist/actions/index.js.map +1 -0
  81. package/dist/applyPageHooks.d.ts +54 -0
  82. package/dist/applyPageHooks.d.ts.map +1 -0
  83. package/dist/applyPageHooks.js +149 -0
  84. package/dist/applyPageHooks.js.map +1 -0
  85. package/dist/cells/coerce.d.ts +25 -0
  86. package/dist/cells/coerce.d.ts.map +1 -0
  87. package/dist/cells/coerce.js +87 -0
  88. package/dist/cells/coerce.js.map +1 -0
  89. package/dist/clusterPaths.d.ts +9 -0
  90. package/dist/clusterPaths.d.ts.map +1 -0
  91. package/dist/clusterPaths.js +19 -0
  92. package/dist/clusterPaths.js.map +1 -0
  93. package/dist/columns/BadgeColumn.d.ts +16 -0
  94. package/dist/columns/BadgeColumn.d.ts.map +1 -0
  95. package/dist/columns/BadgeColumn.js +25 -0
  96. package/dist/columns/BadgeColumn.js.map +1 -0
  97. package/dist/columns/BooleanColumn.d.ts +10 -0
  98. package/dist/columns/BooleanColumn.d.ts.map +1 -0
  99. package/dist/columns/BooleanColumn.js +18 -0
  100. package/dist/columns/BooleanColumn.js.map +1 -0
  101. package/dist/columns/ColorColumn.d.ts +27 -0
  102. package/dist/columns/ColorColumn.d.ts.map +1 -0
  103. package/dist/columns/ColorColumn.js +35 -0
  104. package/dist/columns/ColorColumn.js.map +1 -0
  105. package/dist/columns/IconColumn.d.ts +22 -0
  106. package/dist/columns/IconColumn.d.ts.map +1 -0
  107. package/dist/columns/IconColumn.js +28 -0
  108. package/dist/columns/IconColumn.js.map +1 -0
  109. package/dist/columns/ImageColumn.d.ts +17 -0
  110. package/dist/columns/ImageColumn.d.ts.map +1 -0
  111. package/dist/columns/ImageColumn.js +24 -0
  112. package/dist/columns/ImageColumn.js.map +1 -0
  113. package/dist/columns/SelectColumn.d.ts +36 -0
  114. package/dist/columns/SelectColumn.d.ts.map +1 -0
  115. package/dist/columns/SelectColumn.js +52 -0
  116. package/dist/columns/SelectColumn.js.map +1 -0
  117. package/dist/columns/TextColumn.d.ts +18 -0
  118. package/dist/columns/TextColumn.d.ts.map +1 -0
  119. package/dist/columns/TextColumn.js +20 -0
  120. package/dist/columns/TextColumn.js.map +1 -0
  121. package/dist/columns/TextInputColumn.d.ts +47 -0
  122. package/dist/columns/TextInputColumn.d.ts.map +1 -0
  123. package/dist/columns/TextInputColumn.js +60 -0
  124. package/dist/columns/TextInputColumn.js.map +1 -0
  125. package/dist/columns/ToggleColumn.d.ts +32 -0
  126. package/dist/columns/ToggleColumn.d.ts.map +1 -0
  127. package/dist/columns/ToggleColumn.js +45 -0
  128. package/dist/columns/ToggleColumn.js.map +1 -0
  129. package/dist/columns/index.d.ts +10 -0
  130. package/dist/columns/index.d.ts.map +1 -0
  131. package/dist/columns/index.js +10 -0
  132. package/dist/columns/index.js.map +1 -0
  133. package/dist/defaultGlobalPages.d.ts +11 -0
  134. package/dist/defaultGlobalPages.d.ts.map +1 -0
  135. package/dist/defaultGlobalPages.js +87 -0
  136. package/dist/defaultGlobalPages.js.map +1 -0
  137. package/dist/defaultPages.d.ts +247 -0
  138. package/dist/defaultPages.d.ts.map +1 -0
  139. package/dist/defaultPages.js +558 -0
  140. package/dist/defaultPages.js.map +1 -0
  141. package/dist/elements/Form.d.ts +219 -0
  142. package/dist/elements/Form.d.ts.map +1 -0
  143. package/dist/elements/Form.js +259 -0
  144. package/dist/elements/Form.js.map +1 -0
  145. package/dist/elements/ListTabs.d.ts +17 -0
  146. package/dist/elements/ListTabs.d.ts.map +1 -0
  147. package/dist/elements/ListTabs.js +23 -0
  148. package/dist/elements/ListTabs.js.map +1 -0
  149. package/dist/elements/Table.d.ts +535 -0
  150. package/dist/elements/Table.d.ts.map +1 -0
  151. package/dist/elements/Table.js +481 -0
  152. package/dist/elements/Table.js.map +1 -0
  153. package/dist/elements/TableGroup.d.ts +121 -0
  154. package/dist/elements/TableGroup.d.ts.map +1 -0
  155. package/dist/elements/TableGroup.js +162 -0
  156. package/dist/elements/TableGroup.js.map +1 -0
  157. package/dist/elements/dispatchAction.d.ts +127 -0
  158. package/dist/elements/dispatchAction.d.ts.map +1 -0
  159. package/dist/elements/dispatchAction.js +254 -0
  160. package/dist/elements/dispatchAction.js.map +1 -0
  161. package/dist/elements/dispatchForm.d.ts +220 -0
  162. package/dist/elements/dispatchForm.d.ts.map +1 -0
  163. package/dist/elements/dispatchForm.js +1645 -0
  164. package/dist/elements/dispatchForm.js.map +1 -0
  165. package/dist/elements/dispatchTable.d.ts +69 -0
  166. package/dist/elements/dispatchTable.d.ts.map +1 -0
  167. package/dist/elements/dispatchTable.js +606 -0
  168. package/dist/elements/dispatchTable.js.map +1 -0
  169. package/dist/elements/index.d.ts +3 -0
  170. package/dist/elements/index.d.ts.map +1 -0
  171. package/dist/elements/index.js +3 -0
  172. package/dist/elements/index.js.map +1 -0
  173. package/dist/entries/BadgeEntry.d.ts +21 -0
  174. package/dist/entries/BadgeEntry.d.ts.map +1 -0
  175. package/dist/entries/BadgeEntry.js +32 -0
  176. package/dist/entries/BadgeEntry.js.map +1 -0
  177. package/dist/entries/CodeEntry.d.ts +38 -0
  178. package/dist/entries/CodeEntry.d.ts.map +1 -0
  179. package/dist/entries/CodeEntry.js +44 -0
  180. package/dist/entries/CodeEntry.js.map +1 -0
  181. package/dist/entries/ColorEntry.d.ts +32 -0
  182. package/dist/entries/ColorEntry.d.ts.map +1 -0
  183. package/dist/entries/ColorEntry.js +48 -0
  184. package/dist/entries/ColorEntry.js.map +1 -0
  185. package/dist/entries/ComponentEntry.d.ts +66 -0
  186. package/dist/entries/ComponentEntry.d.ts.map +1 -0
  187. package/dist/entries/ComponentEntry.js +86 -0
  188. package/dist/entries/ComponentEntry.js.map +1 -0
  189. package/dist/entries/Entry.d.ts +175 -0
  190. package/dist/entries/Entry.d.ts.map +1 -0
  191. package/dist/entries/Entry.js +233 -0
  192. package/dist/entries/Entry.js.map +1 -0
  193. package/dist/entries/IconEntry.d.ts +30 -0
  194. package/dist/entries/IconEntry.d.ts.map +1 -0
  195. package/dist/entries/IconEntry.js +34 -0
  196. package/dist/entries/IconEntry.js.map +1 -0
  197. package/dist/entries/ImageEntry.d.ts +33 -0
  198. package/dist/entries/ImageEntry.d.ts.map +1 -0
  199. package/dist/entries/ImageEntry.js +47 -0
  200. package/dist/entries/ImageEntry.js.map +1 -0
  201. package/dist/entries/KeyValueEntry.d.ts +30 -0
  202. package/dist/entries/KeyValueEntry.d.ts.map +1 -0
  203. package/dist/entries/KeyValueEntry.js +38 -0
  204. package/dist/entries/KeyValueEntry.js.map +1 -0
  205. package/dist/entries/RepeatableEntry.d.ts +122 -0
  206. package/dist/entries/RepeatableEntry.d.ts.map +1 -0
  207. package/dist/entries/RepeatableEntry.js +121 -0
  208. package/dist/entries/RepeatableEntry.js.map +1 -0
  209. package/dist/entries/TextEntry.d.ts +38 -0
  210. package/dist/entries/TextEntry.d.ts.map +1 -0
  211. package/dist/entries/TextEntry.js +49 -0
  212. package/dist/entries/TextEntry.js.map +1 -0
  213. package/dist/entries/index.d.ts +2 -0
  214. package/dist/entries/index.d.ts.map +1 -0
  215. package/dist/entries/index.js +8 -0
  216. package/dist/entries/index.js.map +1 -0
  217. package/dist/entries/registry.d.ts +41 -0
  218. package/dist/entries/registry.d.ts.map +1 -0
  219. package/dist/entries/registry.js +17 -0
  220. package/dist/entries/registry.js.map +1 -0
  221. package/dist/fields/BuilderField.d.ts +420 -0
  222. package/dist/fields/BuilderField.d.ts.map +1 -0
  223. package/dist/fields/BuilderField.js +359 -0
  224. package/dist/fields/BuilderField.js.map +1 -0
  225. package/dist/fields/CheckboxField.d.ts +18 -0
  226. package/dist/fields/CheckboxField.d.ts.map +1 -0
  227. package/dist/fields/CheckboxField.js +23 -0
  228. package/dist/fields/CheckboxField.js.map +1 -0
  229. package/dist/fields/CheckboxListField.d.ts +25 -0
  230. package/dist/fields/CheckboxListField.d.ts.map +1 -0
  231. package/dist/fields/CheckboxListField.js +46 -0
  232. package/dist/fields/CheckboxListField.js.map +1 -0
  233. package/dist/fields/ColorPickerField.d.ts +16 -0
  234. package/dist/fields/ColorPickerField.d.ts.map +1 -0
  235. package/dist/fields/ColorPickerField.js +21 -0
  236. package/dist/fields/ColorPickerField.js.map +1 -0
  237. package/dist/fields/DateField.d.ts +29 -0
  238. package/dist/fields/DateField.d.ts.map +1 -0
  239. package/dist/fields/DateField.js +45 -0
  240. package/dist/fields/DateField.js.map +1 -0
  241. package/dist/fields/EmailField.d.ts +8 -0
  242. package/dist/fields/EmailField.d.ts.map +1 -0
  243. package/dist/fields/EmailField.js +13 -0
  244. package/dist/fields/EmailField.js.map +1 -0
  245. package/dist/fields/Field.d.ts +485 -0
  246. package/dist/fields/Field.d.ts.map +1 -0
  247. package/dist/fields/Field.js +539 -0
  248. package/dist/fields/Field.js.map +1 -0
  249. package/dist/fields/FileUploadField.d.ts +43 -0
  250. package/dist/fields/FileUploadField.d.ts.map +1 -0
  251. package/dist/fields/FileUploadField.js +60 -0
  252. package/dist/fields/FileUploadField.js.map +1 -0
  253. package/dist/fields/HiddenField.d.ts +19 -0
  254. package/dist/fields/HiddenField.d.ts.map +1 -0
  255. package/dist/fields/HiddenField.js +24 -0
  256. package/dist/fields/HiddenField.js.map +1 -0
  257. package/dist/fields/KeyValueField.d.ts +36 -0
  258. package/dist/fields/KeyValueField.d.ts.map +1 -0
  259. package/dist/fields/KeyValueField.js +47 -0
  260. package/dist/fields/KeyValueField.js.map +1 -0
  261. package/dist/fields/MarkdownField.d.ts +79 -0
  262. package/dist/fields/MarkdownField.d.ts.map +1 -0
  263. package/dist/fields/MarkdownField.js +117 -0
  264. package/dist/fields/MarkdownField.js.map +1 -0
  265. package/dist/fields/NumberField.d.ts +17 -0
  266. package/dist/fields/NumberField.d.ts.map +1 -0
  267. package/dist/fields/NumberField.js +27 -0
  268. package/dist/fields/NumberField.js.map +1 -0
  269. package/dist/fields/RadioField.d.ts +26 -0
  270. package/dist/fields/RadioField.d.ts.map +1 -0
  271. package/dist/fields/RadioField.js +47 -0
  272. package/dist/fields/RadioField.js.map +1 -0
  273. package/dist/fields/RepeaterField.d.ts +594 -0
  274. package/dist/fields/RepeaterField.d.ts.map +1 -0
  275. package/dist/fields/RepeaterField.js +504 -0
  276. package/dist/fields/RepeaterField.js.map +1 -0
  277. package/dist/fields/RowButton.d.ts +86 -0
  278. package/dist/fields/RowButton.d.ts.map +1 -0
  279. package/dist/fields/RowButton.js +85 -0
  280. package/dist/fields/RowButton.js.map +1 -0
  281. package/dist/fields/SelectField.d.ts +127 -0
  282. package/dist/fields/SelectField.d.ts.map +1 -0
  283. package/dist/fields/SelectField.js +160 -0
  284. package/dist/fields/SelectField.js.map +1 -0
  285. package/dist/fields/SliderField.d.ts +31 -0
  286. package/dist/fields/SliderField.d.ts.map +1 -0
  287. package/dist/fields/SliderField.js +45 -0
  288. package/dist/fields/SliderField.js.map +1 -0
  289. package/dist/fields/SlugField.d.ts +11 -0
  290. package/dist/fields/SlugField.d.ts.map +1 -0
  291. package/dist/fields/SlugField.js +19 -0
  292. package/dist/fields/SlugField.js.map +1 -0
  293. package/dist/fields/TagsInputField.d.ts +65 -0
  294. package/dist/fields/TagsInputField.d.ts.map +1 -0
  295. package/dist/fields/TagsInputField.js +104 -0
  296. package/dist/fields/TagsInputField.js.map +1 -0
  297. package/dist/fields/TextField.d.ts +11 -0
  298. package/dist/fields/TextField.d.ts.map +1 -0
  299. package/dist/fields/TextField.js +19 -0
  300. package/dist/fields/TextField.js.map +1 -0
  301. package/dist/fields/TextareaField.d.ts +40 -0
  302. package/dist/fields/TextareaField.d.ts.map +1 -0
  303. package/dist/fields/TextareaField.js +51 -0
  304. package/dist/fields/TextareaField.js.map +1 -0
  305. package/dist/fields/ToggleButtonsField.d.ts +24 -0
  306. package/dist/fields/ToggleButtonsField.d.ts.map +1 -0
  307. package/dist/fields/ToggleButtonsField.js +41 -0
  308. package/dist/fields/ToggleButtonsField.js.map +1 -0
  309. package/dist/fields/ToggleField.d.ts +8 -0
  310. package/dist/fields/ToggleField.d.ts.map +1 -0
  311. package/dist/fields/ToggleField.js +13 -0
  312. package/dist/fields/ToggleField.js.map +1 -0
  313. package/dist/fields/optionsResolver.d.ts +54 -0
  314. package/dist/fields/optionsResolver.d.ts.map +1 -0
  315. package/dist/fields/optionsResolver.js +62 -0
  316. package/dist/fields/optionsResolver.js.map +1 -0
  317. package/dist/fields/resolveField.d.ts +21 -0
  318. package/dist/fields/resolveField.d.ts.map +1 -0
  319. package/dist/fields/resolveField.js +26 -0
  320. package/dist/fields/resolveField.js.map +1 -0
  321. package/dist/filters/BooleanFilter.d.ts +20 -0
  322. package/dist/filters/BooleanFilter.d.ts.map +1 -0
  323. package/dist/filters/BooleanFilter.js +31 -0
  324. package/dist/filters/BooleanFilter.js.map +1 -0
  325. package/dist/filters/DateRangeFilter.d.ts +68 -0
  326. package/dist/filters/DateRangeFilter.d.ts.map +1 -0
  327. package/dist/filters/DateRangeFilter.js +137 -0
  328. package/dist/filters/DateRangeFilter.js.map +1 -0
  329. package/dist/filters/Filter.d.ts +140 -0
  330. package/dist/filters/Filter.d.ts.map +1 -0
  331. package/dist/filters/Filter.js +99 -0
  332. package/dist/filters/Filter.js.map +1 -0
  333. package/dist/filters/FormFilter.d.ts +103 -0
  334. package/dist/filters/FormFilter.d.ts.map +1 -0
  335. package/dist/filters/FormFilter.js +180 -0
  336. package/dist/filters/FormFilter.js.map +1 -0
  337. package/dist/filters/MultiSelectFilter.d.ts +41 -0
  338. package/dist/filters/MultiSelectFilter.d.ts.map +1 -0
  339. package/dist/filters/MultiSelectFilter.js +67 -0
  340. package/dist/filters/MultiSelectFilter.js.map +1 -0
  341. package/dist/filters/QueryBuilderFilter.d.ts +145 -0
  342. package/dist/filters/QueryBuilderFilter.d.ts.map +1 -0
  343. package/dist/filters/QueryBuilderFilter.js +323 -0
  344. package/dist/filters/QueryBuilderFilter.js.map +1 -0
  345. package/dist/filters/SelectFilter.d.ts +26 -0
  346. package/dist/filters/SelectFilter.d.ts.map +1 -0
  347. package/dist/filters/SelectFilter.js +35 -0
  348. package/dist/filters/SelectFilter.js.map +1 -0
  349. package/dist/filters/TernaryFilter.d.ts +35 -0
  350. package/dist/filters/TernaryFilter.d.ts.map +1 -0
  351. package/dist/filters/TernaryFilter.js +71 -0
  352. package/dist/filters/TernaryFilter.js.map +1 -0
  353. package/dist/filters/TrashedFilter.d.ts +28 -0
  354. package/dist/filters/TrashedFilter.d.ts.map +1 -0
  355. package/dist/filters/TrashedFilter.js +52 -0
  356. package/dist/filters/TrashedFilter.js.map +1 -0
  357. package/dist/filters/queryBuilder/BooleanConstraint.d.ts +13 -0
  358. package/dist/filters/queryBuilder/BooleanConstraint.d.ts.map +1 -0
  359. package/dist/filters/queryBuilder/BooleanConstraint.js +27 -0
  360. package/dist/filters/queryBuilder/BooleanConstraint.js.map +1 -0
  361. package/dist/filters/queryBuilder/Constraint.d.ts +74 -0
  362. package/dist/filters/queryBuilder/Constraint.d.ts.map +1 -0
  363. package/dist/filters/queryBuilder/Constraint.js +45 -0
  364. package/dist/filters/queryBuilder/Constraint.js.map +1 -0
  365. package/dist/filters/queryBuilder/DateConstraint.d.ts +18 -0
  366. package/dist/filters/queryBuilder/DateConstraint.d.ts.map +1 -0
  367. package/dist/filters/queryBuilder/DateConstraint.js +63 -0
  368. package/dist/filters/queryBuilder/DateConstraint.js.map +1 -0
  369. package/dist/filters/queryBuilder/NumberConstraint.d.ts +12 -0
  370. package/dist/filters/queryBuilder/NumberConstraint.d.ts.map +1 -0
  371. package/dist/filters/queryBuilder/NumberConstraint.js +61 -0
  372. package/dist/filters/queryBuilder/NumberConstraint.js.map +1 -0
  373. package/dist/filters/queryBuilder/SelectConstraint.d.ts +22 -0
  374. package/dist/filters/queryBuilder/SelectConstraint.d.ts.map +1 -0
  375. package/dist/filters/queryBuilder/SelectConstraint.js +66 -0
  376. package/dist/filters/queryBuilder/SelectConstraint.js.map +1 -0
  377. package/dist/filters/queryBuilder/TextConstraint.d.ts +18 -0
  378. package/dist/filters/queryBuilder/TextConstraint.d.ts.map +1 -0
  379. package/dist/filters/queryBuilder/TextConstraint.js +58 -0
  380. package/dist/filters/queryBuilder/TextConstraint.js.map +1 -0
  381. package/dist/filters/queryBuilder/index.d.ts +7 -0
  382. package/dist/filters/queryBuilder/index.d.ts.map +1 -0
  383. package/dist/filters/queryBuilder/index.js +7 -0
  384. package/dist/filters/queryBuilder/index.js.map +1 -0
  385. package/dist/icons/index.d.ts +3 -0
  386. package/dist/icons/index.d.ts.map +1 -0
  387. package/dist/icons/index.js +3 -0
  388. package/dist/icons/index.js.map +1 -0
  389. package/dist/icons/lucide.d.ts +16 -0
  390. package/dist/icons/lucide.d.ts.map +1 -0
  391. package/dist/icons/lucide.js +173 -0
  392. package/dist/icons/lucide.js.map +1 -0
  393. package/dist/icons/registry.d.ts +27 -0
  394. package/dist/icons/registry.d.ts.map +1 -0
  395. package/dist/icons/registry.js +35 -0
  396. package/dist/icons/registry.js.map +1 -0
  397. package/dist/icons/types.d.ts +38 -0
  398. package/dist/icons/types.d.ts.map +1 -0
  399. package/dist/icons/types.js +23 -0
  400. package/dist/icons/types.js.map +1 -0
  401. package/dist/index.d.ts +118 -0
  402. package/dist/index.d.ts.map +1 -0
  403. package/dist/index.js +135 -0
  404. package/dist/index.js.map +1 -0
  405. package/dist/io/csv.d.ts +51 -0
  406. package/dist/io/csv.d.ts.map +1 -0
  407. package/dist/io/csv.js +168 -0
  408. package/dist/io/csv.js.map +1 -0
  409. package/dist/notifications/Notification.d.ts +181 -0
  410. package/dist/notifications/Notification.d.ts.map +1 -0
  411. package/dist/notifications/Notification.js +290 -0
  412. package/dist/notifications/Notification.js.map +1 -0
  413. package/dist/notifications/broadcast.d.ts +58 -0
  414. package/dist/notifications/broadcast.d.ts.map +1 -0
  415. package/dist/notifications/broadcast.js +72 -0
  416. package/dist/notifications/broadcast.js.map +1 -0
  417. package/dist/notifications/database.d.ts +164 -0
  418. package/dist/notifications/database.d.ts.map +1 -0
  419. package/dist/notifications/database.js +321 -0
  420. package/dist/notifications/database.js.map +1 -0
  421. package/dist/notifications/dispatchNotificationAction.d.ts +48 -0
  422. package/dist/notifications/dispatchNotificationAction.d.ts.map +1 -0
  423. package/dist/notifications/dispatchNotificationAction.js +100 -0
  424. package/dist/notifications/dispatchNotificationAction.js.map +1 -0
  425. package/dist/notifications/flash.d.ts +34 -0
  426. package/dist/notifications/flash.d.ts.map +1 -0
  427. package/dist/notifications/flash.js +51 -0
  428. package/dist/notifications/flash.js.map +1 -0
  429. package/dist/notifications/index.d.ts +8 -0
  430. package/dist/notifications/index.d.ts.map +1 -0
  431. package/dist/notifications/index.js +6 -0
  432. package/dist/notifications/index.js.map +1 -0
  433. package/dist/notifications/registerBroadcastAuth.d.ts +45 -0
  434. package/dist/notifications/registerBroadcastAuth.d.ts.map +1 -0
  435. package/dist/notifications/registerBroadcastAuth.js +86 -0
  436. package/dist/notifications/registerBroadcastAuth.js.map +1 -0
  437. package/dist/notifications/resolveSavedNotification.d.ts +21 -0
  438. package/dist/notifications/resolveSavedNotification.d.ts.map +1 -0
  439. package/dist/notifications/resolveSavedNotification.js +43 -0
  440. package/dist/notifications/resolveSavedNotification.js.map +1 -0
  441. package/dist/notifications/types.d.ts +87 -0
  442. package/dist/notifications/types.d.ts.map +1 -0
  443. package/dist/notifications/types.js +2 -0
  444. package/dist/notifications/types.js.map +1 -0
  445. package/dist/orm/m2mAccessor.d.ts +49 -0
  446. package/dist/orm/m2mAccessor.d.ts.map +1 -0
  447. package/dist/orm/m2mAccessor.js +45 -0
  448. package/dist/orm/m2mAccessor.js.map +1 -0
  449. package/dist/orm/modelDefaults.d.ts +347 -0
  450. package/dist/orm/modelDefaults.d.ts.map +1 -0
  451. package/dist/orm/modelDefaults.js +375 -0
  452. package/dist/orm/modelDefaults.js.map +1 -0
  453. package/dist/pageData.d.ts +778 -0
  454. package/dist/pageData.d.ts.map +1 -0
  455. package/dist/pageData.js +3725 -0
  456. package/dist/pageData.js.map +1 -0
  457. package/dist/plugins/index.d.ts +2 -0
  458. package/dist/plugins/index.d.ts.map +1 -0
  459. package/dist/plugins/index.js +2 -0
  460. package/dist/plugins/index.js.map +1 -0
  461. package/dist/plugins/themeEditor.d.ts +17 -0
  462. package/dist/plugins/themeEditor.d.ts.map +1 -0
  463. package/dist/plugins/themeEditor.js +23 -0
  464. package/dist/plugins/themeEditor.js.map +1 -0
  465. package/dist/react/AppShell.d.ts +58 -0
  466. package/dist/react/AppShell.d.ts.map +1 -0
  467. package/dist/react/AppShell.js +58 -0
  468. package/dist/react/AppShell.js.map +1 -0
  469. package/dist/react/CommandPalette.d.ts +21 -0
  470. package/dist/react/CommandPalette.d.ts.map +1 -0
  471. package/dist/react/CommandPalette.js +236 -0
  472. package/dist/react/CommandPalette.js.map +1 -0
  473. package/dist/react/FormStateContext.d.ts +83 -0
  474. package/dist/react/FormStateContext.d.ts.map +1 -0
  475. package/dist/react/FormStateContext.js +284 -0
  476. package/dist/react/FormStateContext.js.map +1 -0
  477. package/dist/react/HeadHooks.d.ts +26 -0
  478. package/dist/react/HeadHooks.d.ts.map +1 -0
  479. package/dist/react/HeadHooks.js +141 -0
  480. package/dist/react/HeadHooks.js.map +1 -0
  481. package/dist/react/NotificationActionStrip.d.ts +39 -0
  482. package/dist/react/NotificationActionStrip.d.ts.map +1 -0
  483. package/dist/react/NotificationActionStrip.js +129 -0
  484. package/dist/react/NotificationActionStrip.js.map +1 -0
  485. package/dist/react/NotificationBell.d.ts +20 -0
  486. package/dist/react/NotificationBell.d.ts.map +1 -0
  487. package/dist/react/NotificationBell.js +273 -0
  488. package/dist/react/NotificationBell.js.map +1 -0
  489. package/dist/react/RenderHookSlot.d.ts +20 -0
  490. package/dist/react/RenderHookSlot.d.ts.map +1 -0
  491. package/dist/react/RenderHookSlot.js +24 -0
  492. package/dist/react/RenderHookSlot.js.map +1 -0
  493. package/dist/react/RightSidebar.d.ts +33 -0
  494. package/dist/react/RightSidebar.d.ts.map +1 -0
  495. package/dist/react/RightSidebar.js +82 -0
  496. package/dist/react/RightSidebar.js.map +1 -0
  497. package/dist/react/RightSidebarContext.d.ts +62 -0
  498. package/dist/react/RightSidebarContext.d.ts.map +1 -0
  499. package/dist/react/RightSidebarContext.js +178 -0
  500. package/dist/react/RightSidebarContext.js.map +1 -0
  501. package/dist/react/RightSidebarTrigger.d.ts +16 -0
  502. package/dist/react/RightSidebarTrigger.d.ts.map +1 -0
  503. package/dist/react/RightSidebarTrigger.js +24 -0
  504. package/dist/react/RightSidebarTrigger.js.map +1 -0
  505. package/dist/react/SchemaRenderer.d.ts +63 -0
  506. package/dist/react/SchemaRenderer.d.ts.map +1 -0
  507. package/dist/react/SchemaRenderer.js +3458 -0
  508. package/dist/react/SchemaRenderer.js.map +1 -0
  509. package/dist/react/SearchTrigger.d.ts +13 -0
  510. package/dist/react/SearchTrigger.d.ts.map +1 -0
  511. package/dist/react/SearchTrigger.js +30 -0
  512. package/dist/react/SearchTrigger.js.map +1 -0
  513. package/dist/react/ThemeProvider.d.ts +18 -0
  514. package/dist/react/ThemeProvider.d.ts.map +1 -0
  515. package/dist/react/ThemeProvider.js +66 -0
  516. package/dist/react/ThemeProvider.js.map +1 -0
  517. package/dist/react/ThemeSettingsPage.d.ts +10 -0
  518. package/dist/react/ThemeSettingsPage.d.ts.map +1 -0
  519. package/dist/react/ThemeSettingsPage.js +293 -0
  520. package/dist/react/ThemeSettingsPage.js.map +1 -0
  521. package/dist/react/ThemeToggle.d.ts +2 -0
  522. package/dist/react/ThemeToggle.d.ts.map +1 -0
  523. package/dist/react/ThemeToggle.js +8 -0
  524. package/dist/react/ThemeToggle.js.map +1 -0
  525. package/dist/react/Toaster.d.ts +25 -0
  526. package/dist/react/Toaster.d.ts.map +1 -0
  527. package/dist/react/Toaster.js +89 -0
  528. package/dist/react/Toaster.js.map +1 -0
  529. package/dist/react/UserMenu.d.ts +23 -0
  530. package/dist/react/UserMenu.d.ts.map +1 -0
  531. package/dist/react/UserMenu.js +78 -0
  532. package/dist/react/UserMenu.js.map +1 -0
  533. package/dist/react/WidgetDataContext.d.ts +64 -0
  534. package/dist/react/WidgetDataContext.d.ts.map +1 -0
  535. package/dist/react/WidgetDataContext.js +89 -0
  536. package/dist/react/WidgetDataContext.js.map +1 -0
  537. package/dist/react/cells/EditableCell.d.ts +20 -0
  538. package/dist/react/cells/EditableCell.d.ts.map +1 -0
  539. package/dist/react/cells/EditableCell.js +251 -0
  540. package/dist/react/cells/EditableCell.js.map +1 -0
  541. package/dist/react/fieldJsHandler.d.ts +33 -0
  542. package/dist/react/fieldJsHandler.d.ts.map +1 -0
  543. package/dist/react/fieldJsHandler.js +61 -0
  544. package/dist/react/fieldJsHandler.js.map +1 -0
  545. package/dist/react/fields/BuilderInput.d.ts +21 -0
  546. package/dist/react/fields/BuilderInput.d.ts.map +1 -0
  547. package/dist/react/fields/BuilderInput.js +553 -0
  548. package/dist/react/fields/BuilderInput.js.map +1 -0
  549. package/dist/react/fields/CheckboxInput.d.ts +9 -0
  550. package/dist/react/fields/CheckboxInput.d.ts.map +1 -0
  551. package/dist/react/fields/CheckboxInput.js +23 -0
  552. package/dist/react/fields/CheckboxInput.js.map +1 -0
  553. package/dist/react/fields/CheckboxListInput.d.ts +19 -0
  554. package/dist/react/fields/CheckboxListInput.d.ts.map +1 -0
  555. package/dist/react/fields/CheckboxListInput.js +53 -0
  556. package/dist/react/fields/CheckboxListInput.js.map +1 -0
  557. package/dist/react/fields/ColorInput.d.ts +12 -0
  558. package/dist/react/fields/ColorInput.d.ts.map +1 -0
  559. package/dist/react/fields/ColorInput.js +29 -0
  560. package/dist/react/fields/ColorInput.js.map +1 -0
  561. package/dist/react/fields/DateFieldInput.d.ts +8 -0
  562. package/dist/react/fields/DateFieldInput.d.ts.map +1 -0
  563. package/dist/react/fields/DateFieldInput.js +39 -0
  564. package/dist/react/fields/DateFieldInput.js.map +1 -0
  565. package/dist/react/fields/DateTimeInput.d.ts +13 -0
  566. package/dist/react/fields/DateTimeInput.d.ts.map +1 -0
  567. package/dist/react/fields/DateTimeInput.js +29 -0
  568. package/dist/react/fields/DateTimeInput.js.map +1 -0
  569. package/dist/react/fields/FieldShell.d.ts +23 -0
  570. package/dist/react/fields/FieldShell.d.ts.map +1 -0
  571. package/dist/react/fields/FieldShell.js +46 -0
  572. package/dist/react/fields/FieldShell.js.map +1 -0
  573. package/dist/react/fields/FileUploadInput.d.ts +21 -0
  574. package/dist/react/fields/FileUploadInput.d.ts.map +1 -0
  575. package/dist/react/fields/FileUploadInput.js +120 -0
  576. package/dist/react/fields/FileUploadInput.js.map +1 -0
  577. package/dist/react/fields/HiddenInput.d.ts +11 -0
  578. package/dist/react/fields/HiddenInput.d.ts.map +1 -0
  579. package/dist/react/fields/HiddenInput.js +14 -0
  580. package/dist/react/fields/HiddenInput.js.map +1 -0
  581. package/dist/react/fields/KeyValueInput.d.ts +18 -0
  582. package/dist/react/fields/KeyValueInput.d.ts.map +1 -0
  583. package/dist/react/fields/KeyValueInput.js +122 -0
  584. package/dist/react/fields/KeyValueInput.js.map +1 -0
  585. package/dist/react/fields/MarkdownInput.d.ts +29 -0
  586. package/dist/react/fields/MarkdownInput.d.ts.map +1 -0
  587. package/dist/react/fields/MarkdownInput.js +250 -0
  588. package/dist/react/fields/MarkdownInput.js.map +1 -0
  589. package/dist/react/fields/RadioInput.d.ts +18 -0
  590. package/dist/react/fields/RadioInput.d.ts.map +1 -0
  591. package/dist/react/fields/RadioInput.js +34 -0
  592. package/dist/react/fields/RadioInput.js.map +1 -0
  593. package/dist/react/fields/RepeaterInput.d.ts +92 -0
  594. package/dist/react/fields/RepeaterInput.d.ts.map +1 -0
  595. package/dist/react/fields/RepeaterInput.js +705 -0
  596. package/dist/react/fields/RepeaterInput.js.map +1 -0
  597. package/dist/react/fields/SelectFieldInput.d.ts +23 -0
  598. package/dist/react/fields/SelectFieldInput.d.ts.map +1 -0
  599. package/dist/react/fields/SelectFieldInput.js +146 -0
  600. package/dist/react/fields/SelectFieldInput.js.map +1 -0
  601. package/dist/react/fields/SliderInput.d.ts +16 -0
  602. package/dist/react/fields/SliderInput.d.ts.map +1 -0
  603. package/dist/react/fields/SliderInput.js +37 -0
  604. package/dist/react/fields/SliderInput.js.map +1 -0
  605. package/dist/react/fields/TagsInput.d.ts +27 -0
  606. package/dist/react/fields/TagsInput.d.ts.map +1 -0
  607. package/dist/react/fields/TagsInput.js +189 -0
  608. package/dist/react/fields/TagsInput.js.map +1 -0
  609. package/dist/react/fields/TextLikeInput.d.ts +18 -0
  610. package/dist/react/fields/TextLikeInput.d.ts.map +1 -0
  611. package/dist/react/fields/TextLikeInput.js +46 -0
  612. package/dist/react/fields/TextLikeInput.js.map +1 -0
  613. package/dist/react/fields/ToggleButtonsInput.d.ts +20 -0
  614. package/dist/react/fields/ToggleButtonsInput.d.ts.map +1 -0
  615. package/dist/react/fields/ToggleButtonsInput.js +42 -0
  616. package/dist/react/fields/ToggleButtonsInput.js.map +1 -0
  617. package/dist/react/fields/ToggleFieldInput.d.ts +7 -0
  618. package/dist/react/fields/ToggleFieldInput.d.ts.map +1 -0
  619. package/dist/react/fields/ToggleFieldInput.js +30 -0
  620. package/dist/react/fields/ToggleFieldInput.js.map +1 -0
  621. package/dist/react/fields/rowChromeButton.d.ts +84 -0
  622. package/dist/react/fields/rowChromeButton.d.ts.map +1 -0
  623. package/dist/react/fields/rowChromeButton.js +111 -0
  624. package/dist/react/fields/rowChromeButton.js.map +1 -0
  625. package/dist/react/fields/syncRowGates.d.ts +11 -0
  626. package/dist/react/fields/syncRowGates.d.ts.map +1 -0
  627. package/dist/react/fields/syncRowGates.js +55 -0
  628. package/dist/react/fields/syncRowGates.js.map +1 -0
  629. package/dist/react/formStateHelpers.d.ts +44 -0
  630. package/dist/react/formStateHelpers.d.ts.map +1 -0
  631. package/dist/react/formStateHelpers.js +230 -0
  632. package/dist/react/formStateHelpers.js.map +1 -0
  633. package/dist/react/hooks/use-mobile.d.ts +2 -0
  634. package/dist/react/hooks/use-mobile.d.ts.map +1 -0
  635. package/dist/react/hooks/use-mobile.js +16 -0
  636. package/dist/react/hooks/use-mobile.js.map +1 -0
  637. package/dist/react/icon-context.d.ts +35 -0
  638. package/dist/react/icon-context.d.ts.map +1 -0
  639. package/dist/react/icon-context.js +45 -0
  640. package/dist/react/icon-context.js.map +1 -0
  641. package/dist/react/index.d.ts +26 -0
  642. package/dist/react/index.d.ts.map +1 -0
  643. package/dist/react/index.js +28 -0
  644. package/dist/react/index.js.map +1 -0
  645. package/dist/react/layouts/SidebarLayout.d.ts +3 -0
  646. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -0
  647. package/dist/react/layouts/SidebarLayout.js +85 -0
  648. package/dist/react/layouts/SidebarLayout.js.map +1 -0
  649. package/dist/react/layouts/TopbarLayout.d.ts +3 -0
  650. package/dist/react/layouts/TopbarLayout.d.ts.map +1 -0
  651. package/dist/react/layouts/TopbarLayout.js +103 -0
  652. package/dist/react/layouts/TopbarLayout.js.map +1 -0
  653. package/dist/react/navigate.d.ts +22 -0
  654. package/dist/react/navigate.d.ts.map +1 -0
  655. package/dist/react/navigate.js +30 -0
  656. package/dist/react/navigate.js.map +1 -0
  657. package/dist/react/registry.d.ts +35 -0
  658. package/dist/react/registry.d.ts.map +1 -0
  659. package/dist/react/registry.js +22 -0
  660. package/dist/react/registry.js.map +1 -0
  661. package/dist/react/right-panel-registry.d.ts +32 -0
  662. package/dist/react/right-panel-registry.d.ts.map +1 -0
  663. package/dist/react/right-panel-registry.js +20 -0
  664. package/dist/react/right-panel-registry.js.map +1 -0
  665. package/dist/react/theme-preview/apply.d.ts +11 -0
  666. package/dist/react/theme-preview/apply.d.ts.map +1 -0
  667. package/dist/react/theme-preview/apply.js +93 -0
  668. package/dist/react/theme-preview/apply.js.map +1 -0
  669. package/dist/react/theme-preview/build-html.d.ts +3 -0
  670. package/dist/react/theme-preview/build-html.d.ts.map +1 -0
  671. package/dist/react/theme-preview/build-html.js +437 -0
  672. package/dist/react/theme-preview/build-html.js.map +1 -0
  673. package/dist/react/ui/button.d.ts +9 -0
  674. package/dist/react/ui/button.d.ts.map +1 -0
  675. package/dist/react/ui/button.js +35 -0
  676. package/dist/react/ui/button.js.map +1 -0
  677. package/dist/react/ui/calendar.d.ts +5 -0
  678. package/dist/react/ui/calendar.d.ts.map +1 -0
  679. package/dist/react/ui/calendar.js +34 -0
  680. package/dist/react/ui/calendar.js.map +1 -0
  681. package/dist/react/ui/checkbox.d.ts +4 -0
  682. package/dist/react/ui/checkbox.d.ts.map +1 -0
  683. package/dist/react/ui/checkbox.js +9 -0
  684. package/dist/react/ui/checkbox.js.map +1 -0
  685. package/dist/react/ui/dialog.d.ts +12 -0
  686. package/dist/react/ui/dialog.d.ts.map +1 -0
  687. package/dist/react/ui/dialog.js +34 -0
  688. package/dist/react/ui/dialog.js.map +1 -0
  689. package/dist/react/ui/dropdown-menu.d.ts +12 -0
  690. package/dist/react/ui/dropdown-menu.d.ts.map +1 -0
  691. package/dist/react/ui/dropdown-menu.js +23 -0
  692. package/dist/react/ui/dropdown-menu.js.map +1 -0
  693. package/dist/react/ui/input.d.ts +4 -0
  694. package/dist/react/ui/input.d.ts.map +1 -0
  695. package/dist/react/ui/input.js +8 -0
  696. package/dist/react/ui/input.js.map +1 -0
  697. package/dist/react/ui/label.d.ts +4 -0
  698. package/dist/react/ui/label.d.ts.map +1 -0
  699. package/dist/react/ui/label.js +7 -0
  700. package/dist/react/ui/label.js.map +1 -0
  701. package/dist/react/ui/popover.d.ts +6 -0
  702. package/dist/react/ui/popover.d.ts.map +1 -0
  703. package/dist/react/ui/popover.js +14 -0
  704. package/dist/react/ui/popover.js.map +1 -0
  705. package/dist/react/ui/select.d.ts +17 -0
  706. package/dist/react/ui/select.d.ts.map +1 -0
  707. package/dist/react/ui/select.js +39 -0
  708. package/dist/react/ui/select.js.map +1 -0
  709. package/dist/react/ui/separator.d.ts +4 -0
  710. package/dist/react/ui/separator.d.ts.map +1 -0
  711. package/dist/react/ui/separator.js +9 -0
  712. package/dist/react/ui/separator.js.map +1 -0
  713. package/dist/react/ui/sheet.d.ts +15 -0
  714. package/dist/react/ui/sheet.d.ts.map +1 -0
  715. package/dist/react/ui/sheet.js +37 -0
  716. package/dist/react/ui/sheet.js.map +1 -0
  717. package/dist/react/ui/sidebar.d.ts +64 -0
  718. package/dist/react/ui/sidebar.d.ts.map +1 -0
  719. package/dist/react/ui/sidebar.js +257 -0
  720. package/dist/react/ui/sidebar.js.map +1 -0
  721. package/dist/react/ui/skeleton.d.ts +3 -0
  722. package/dist/react/ui/skeleton.d.ts.map +1 -0
  723. package/dist/react/ui/skeleton.js +7 -0
  724. package/dist/react/ui/skeleton.js.map +1 -0
  725. package/dist/react/ui/slider.d.ts +4 -0
  726. package/dist/react/ui/slider.d.ts.map +1 -0
  727. package/dist/react/ui/slider.js +8 -0
  728. package/dist/react/ui/slider.js.map +1 -0
  729. package/dist/react/ui/switch.d.ts +4 -0
  730. package/dist/react/ui/switch.d.ts.map +1 -0
  731. package/dist/react/ui/switch.js +8 -0
  732. package/dist/react/ui/switch.js.map +1 -0
  733. package/dist/react/ui/table.d.ts +11 -0
  734. package/dist/react/ui/table.d.ts.map +1 -0
  735. package/dist/react/ui/table.js +28 -0
  736. package/dist/react/ui/table.js.map +1 -0
  737. package/dist/react/ui/tabs.d.ts +7 -0
  738. package/dist/react/ui/tabs.d.ts.map +1 -0
  739. package/dist/react/ui/tabs.js +17 -0
  740. package/dist/react/ui/tabs.js.map +1 -0
  741. package/dist/react/ui/textarea.d.ts +4 -0
  742. package/dist/react/ui/textarea.d.ts.map +1 -0
  743. package/dist/react/ui/textarea.js +7 -0
  744. package/dist/react/ui/textarea.js.map +1 -0
  745. package/dist/react/ui/tooltip.d.ts +7 -0
  746. package/dist/react/ui/tooltip.d.ts.map +1 -0
  747. package/dist/react/ui/tooltip.js +17 -0
  748. package/dist/react/ui/tooltip.js.map +1 -0
  749. package/dist/react/useResizableWidth.d.ts +47 -0
  750. package/dist/react/useResizableWidth.d.ts.map +1 -0
  751. package/dist/react/useResizableWidth.js +99 -0
  752. package/dist/react/useResizableWidth.js.map +1 -0
  753. package/dist/react/utils.d.ts +3 -0
  754. package/dist/react/utils.d.ts.map +1 -0
  755. package/dist/react/utils.js +6 -0
  756. package/dist/react/utils.js.map +1 -0
  757. package/dist/react/widgetRegistry.d.ts +33 -0
  758. package/dist/react/widgetRegistry.d.ts.map +1 -0
  759. package/dist/react/widgetRegistry.js +15 -0
  760. package/dist/react/widgetRegistry.js.map +1 -0
  761. package/dist/react/widgets/StatsOverviewRenderer.d.ts +6 -0
  762. package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -0
  763. package/dist/react/widgets/StatsOverviewRenderer.js +124 -0
  764. package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -0
  765. package/dist/react/widgets/TableWidgetRenderer.d.ts +6 -0
  766. package/dist/react/widgets/TableWidgetRenderer.d.ts.map +1 -0
  767. package/dist/react/widgets/TableWidgetRenderer.js +123 -0
  768. package/dist/react/widgets/TableWidgetRenderer.js.map +1 -0
  769. package/dist/react/widgets/ViewRenderer.d.ts +16 -0
  770. package/dist/react/widgets/ViewRenderer.d.ts.map +1 -0
  771. package/dist/react/widgets/ViewRenderer.js +26 -0
  772. package/dist/react/widgets/ViewRenderer.js.map +1 -0
  773. package/dist/richtext/index.d.ts +2 -0
  774. package/dist/richtext/index.d.ts.map +1 -0
  775. package/dist/richtext/index.js +2 -0
  776. package/dist/richtext/index.js.map +1 -0
  777. package/dist/richtext/registry.d.ts +55 -0
  778. package/dist/richtext/registry.d.ts.map +1 -0
  779. package/dist/richtext/registry.js +66 -0
  780. package/dist/richtext/registry.js.map +1 -0
  781. package/dist/routes.d.ts +9 -0
  782. package/dist/routes.d.ts.map +1 -0
  783. package/dist/routes.js +3116 -0
  784. package/dist/routes.js.map +1 -0
  785. package/dist/schema/Alert.d.ts +33 -0
  786. package/dist/schema/Alert.d.ts.map +1 -0
  787. package/dist/schema/Alert.js +41 -0
  788. package/dist/schema/Alert.js.map +1 -0
  789. package/dist/schema/Block.d.ts +112 -0
  790. package/dist/schema/Block.d.ts.map +1 -0
  791. package/dist/schema/Block.js +136 -0
  792. package/dist/schema/Block.js.map +1 -0
  793. package/dist/schema/Breadcrumbs.d.ts +31 -0
  794. package/dist/schema/Breadcrumbs.d.ts.map +1 -0
  795. package/dist/schema/Breadcrumbs.js +30 -0
  796. package/dist/schema/Breadcrumbs.js.map +1 -0
  797. package/dist/schema/Card.d.ts +17 -0
  798. package/dist/schema/Card.d.ts.map +1 -0
  799. package/dist/schema/Card.js +31 -0
  800. package/dist/schema/Card.js.map +1 -0
  801. package/dist/schema/Divider.d.ts +12 -0
  802. package/dist/schema/Divider.d.ts.map +1 -0
  803. package/dist/schema/Divider.js +19 -0
  804. package/dist/schema/Divider.js.map +1 -0
  805. package/dist/schema/Element.d.ts +150 -0
  806. package/dist/schema/Element.d.ts.map +1 -0
  807. package/dist/schema/Element.js +124 -0
  808. package/dist/schema/Element.js.map +1 -0
  809. package/dist/schema/EmptyState.d.ts +48 -0
  810. package/dist/schema/EmptyState.d.ts.map +1 -0
  811. package/dist/schema/EmptyState.js +57 -0
  812. package/dist/schema/EmptyState.js.map +1 -0
  813. package/dist/schema/Fieldset.d.ts +25 -0
  814. package/dist/schema/Fieldset.d.ts.map +1 -0
  815. package/dist/schema/Fieldset.js +39 -0
  816. package/dist/schema/Fieldset.js.map +1 -0
  817. package/dist/schema/Grid.d.ts +23 -0
  818. package/dist/schema/Grid.d.ts.map +1 -0
  819. package/dist/schema/Grid.js +36 -0
  820. package/dist/schema/Grid.js.map +1 -0
  821. package/dist/schema/Group.d.ts +19 -0
  822. package/dist/schema/Group.d.ts.map +1 -0
  823. package/dist/schema/Group.js +26 -0
  824. package/dist/schema/Group.js.map +1 -0
  825. package/dist/schema/Heading.d.ts +25 -0
  826. package/dist/schema/Heading.d.ts.map +1 -0
  827. package/dist/schema/Heading.js +34 -0
  828. package/dist/schema/Heading.js.map +1 -0
  829. package/dist/schema/Html.d.ts +48 -0
  830. package/dist/schema/Html.d.ts.map +1 -0
  831. package/dist/schema/Html.js +60 -0
  832. package/dist/schema/Html.js.map +1 -0
  833. package/dist/schema/Icon.d.ts +34 -0
  834. package/dist/schema/Icon.d.ts.map +1 -0
  835. package/dist/schema/Icon.js +40 -0
  836. package/dist/schema/Icon.js.map +1 -0
  837. package/dist/schema/Image.d.ts +38 -0
  838. package/dist/schema/Image.d.ts.map +1 -0
  839. package/dist/schema/Image.js +48 -0
  840. package/dist/schema/Image.js.map +1 -0
  841. package/dist/schema/LinkTag.d.ts +48 -0
  842. package/dist/schema/LinkTag.d.ts.map +1 -0
  843. package/dist/schema/LinkTag.js +16 -0
  844. package/dist/schema/LinkTag.js.map +1 -0
  845. package/dist/schema/Markdown.d.ts +57 -0
  846. package/dist/schema/Markdown.d.ts.map +1 -0
  847. package/dist/schema/Markdown.js +75 -0
  848. package/dist/schema/Markdown.js.map +1 -0
  849. package/dist/schema/MetaTag.d.ts +41 -0
  850. package/dist/schema/MetaTag.d.ts.map +1 -0
  851. package/dist/schema/MetaTag.js +16 -0
  852. package/dist/schema/MetaTag.js.map +1 -0
  853. package/dist/schema/RelationTabs.d.ts +50 -0
  854. package/dist/schema/RelationTabs.d.ts.map +1 -0
  855. package/dist/schema/RelationTabs.js +48 -0
  856. package/dist/schema/RelationTabs.js.map +1 -0
  857. package/dist/schema/ScriptTag.d.ts +63 -0
  858. package/dist/schema/ScriptTag.d.ts.map +1 -0
  859. package/dist/schema/ScriptTag.js +16 -0
  860. package/dist/schema/ScriptTag.js.map +1 -0
  861. package/dist/schema/Section.d.ts +93 -0
  862. package/dist/schema/Section.d.ts.map +1 -0
  863. package/dist/schema/Section.js +127 -0
  864. package/dist/schema/Section.js.map +1 -0
  865. package/dist/schema/ServerDataElement.d.ts +101 -0
  866. package/dist/schema/ServerDataElement.d.ts.map +1 -0
  867. package/dist/schema/ServerDataElement.js +135 -0
  868. package/dist/schema/ServerDataElement.js.map +1 -0
  869. package/dist/schema/Split.d.ts +31 -0
  870. package/dist/schema/Split.d.ts.map +1 -0
  871. package/dist/schema/Split.js +41 -0
  872. package/dist/schema/Split.js.map +1 -0
  873. package/dist/schema/Stat.d.ts +92 -0
  874. package/dist/schema/Stat.d.ts.map +1 -0
  875. package/dist/schema/Stat.js +116 -0
  876. package/dist/schema/Stat.js.map +1 -0
  877. package/dist/schema/StatsOverview.d.ts +76 -0
  878. package/dist/schema/StatsOverview.d.ts.map +1 -0
  879. package/dist/schema/StatsOverview.js +71 -0
  880. package/dist/schema/StatsOverview.js.map +1 -0
  881. package/dist/schema/StyleTag.d.ts +32 -0
  882. package/dist/schema/StyleTag.d.ts.map +1 -0
  883. package/dist/schema/StyleTag.js +38 -0
  884. package/dist/schema/StyleTag.js.map +1 -0
  885. package/dist/schema/TableWidget.d.ts +148 -0
  886. package/dist/schema/TableWidget.d.ts.map +1 -0
  887. package/dist/schema/TableWidget.js +190 -0
  888. package/dist/schema/TableWidget.js.map +1 -0
  889. package/dist/schema/Tabs.d.ts +40 -0
  890. package/dist/schema/Tabs.d.ts.map +1 -0
  891. package/dist/schema/Tabs.js +66 -0
  892. package/dist/schema/Tabs.js.map +1 -0
  893. package/dist/schema/Text.d.ts +33 -0
  894. package/dist/schema/Text.d.ts.map +1 -0
  895. package/dist/schema/Text.js +40 -0
  896. package/dist/schema/Text.js.map +1 -0
  897. package/dist/schema/UnorderedList.d.ts +36 -0
  898. package/dist/schema/UnorderedList.d.ts.map +1 -0
  899. package/dist/schema/UnorderedList.js +42 -0
  900. package/dist/schema/UnorderedList.js.map +1 -0
  901. package/dist/schema/View.d.ts +81 -0
  902. package/dist/schema/View.d.ts.map +1 -0
  903. package/dist/schema/View.js +81 -0
  904. package/dist/schema/View.js.map +1 -0
  905. package/dist/schema/Wizard.d.ts +67 -0
  906. package/dist/schema/Wizard.d.ts.map +1 -0
  907. package/dist/schema/Wizard.js +94 -0
  908. package/dist/schema/Wizard.js.map +1 -0
  909. package/dist/schema/index.d.ts +26 -0
  910. package/dist/schema/index.d.ts.map +1 -0
  911. package/dist/schema/index.js +26 -0
  912. package/dist/schema/index.js.map +1 -0
  913. package/dist/schema/resolveSchema.d.ts +122 -0
  914. package/dist/schema/resolveSchema.d.ts.map +1 -0
  915. package/dist/schema/resolveSchema.js +648 -0
  916. package/dist/schema/resolveSchema.js.map +1 -0
  917. package/dist/schema/sanitize.d.ts +21 -0
  918. package/dist/schema/sanitize.d.ts.map +1 -0
  919. package/dist/schema/sanitize.js +46 -0
  920. package/dist/schema/sanitize.js.map +1 -0
  921. package/dist/search.d.ts +53 -0
  922. package/dist/search.d.ts.map +1 -0
  923. package/dist/search.js +114 -0
  924. package/dist/search.js.map +1 -0
  925. package/dist/sessionFilters.d.ts +8 -0
  926. package/dist/sessionFilters.d.ts.map +1 -0
  927. package/dist/sessionFilters.js +115 -0
  928. package/dist/sessionFilters.js.map +1 -0
  929. package/dist/summarizers/Summarizer.d.ts +65 -0
  930. package/dist/summarizers/Summarizer.d.ts.map +1 -0
  931. package/dist/summarizers/Summarizer.js +98 -0
  932. package/dist/summarizers/Summarizer.js.map +1 -0
  933. package/dist/summarizers/index.d.ts +2 -0
  934. package/dist/summarizers/index.d.ts.map +1 -0
  935. package/dist/summarizers/index.js +2 -0
  936. package/dist/summarizers/index.js.map +1 -0
  937. package/dist/theme/base-colors.d.ts +3 -0
  938. package/dist/theme/base-colors.d.ts.map +1 -0
  939. package/dist/theme/base-colors.js +64 -0
  940. package/dist/theme/base-colors.js.map +1 -0
  941. package/dist/theme/chart-colors.d.ts +3 -0
  942. package/dist/theme/chart-colors.d.ts.map +1 -0
  943. package/dist/theme/chart-colors.js +46 -0
  944. package/dist/theme/chart-colors.js.map +1 -0
  945. package/dist/theme/colors.d.ts +56 -0
  946. package/dist/theme/colors.d.ts.map +1 -0
  947. package/dist/theme/colors.js +410 -0
  948. package/dist/theme/colors.js.map +1 -0
  949. package/dist/theme/generate-css.d.ts +9 -0
  950. package/dist/theme/generate-css.d.ts.map +1 -0
  951. package/dist/theme/generate-css.js +36 -0
  952. package/dist/theme/generate-css.js.map +1 -0
  953. package/dist/theme/generate-scale.d.ts +3 -0
  954. package/dist/theme/generate-scale.d.ts.map +1 -0
  955. package/dist/theme/generate-scale.js +89 -0
  956. package/dist/theme/generate-scale.js.map +1 -0
  957. package/dist/theme/icon-map.d.ts +9 -0
  958. package/dist/theme/icon-map.d.ts.map +1 -0
  959. package/dist/theme/icon-map.js +40 -0
  960. package/dist/theme/icon-map.js.map +1 -0
  961. package/dist/theme/index.d.ts +15 -0
  962. package/dist/theme/index.d.ts.map +1 -0
  963. package/dist/theme/index.js +13 -0
  964. package/dist/theme/index.js.map +1 -0
  965. package/dist/theme/migrate.d.ts +14 -0
  966. package/dist/theme/migrate.d.ts.map +1 -0
  967. package/dist/theme/migrate.js +79 -0
  968. package/dist/theme/migrate.js.map +1 -0
  969. package/dist/theme/presets.d.ts +30 -0
  970. package/dist/theme/presets.d.ts.map +1 -0
  971. package/dist/theme/presets.js +128 -0
  972. package/dist/theme/presets.js.map +1 -0
  973. package/dist/theme/radius.d.ts +11 -0
  974. package/dist/theme/radius.d.ts.map +1 -0
  975. package/dist/theme/radius.js +17 -0
  976. package/dist/theme/radius.js.map +1 -0
  977. package/dist/theme/resolve.d.ts +13 -0
  978. package/dist/theme/resolve.d.ts.map +1 -0
  979. package/dist/theme/resolve.js +91 -0
  980. package/dist/theme/resolve.js.map +1 -0
  981. package/dist/theme/spacing.d.ts +14 -0
  982. package/dist/theme/spacing.d.ts.map +1 -0
  983. package/dist/theme/spacing.js +17 -0
  984. package/dist/theme/spacing.js.map +1 -0
  985. package/dist/theme/theme-colors.d.ts +9 -0
  986. package/dist/theme/theme-colors.d.ts.map +1 -0
  987. package/dist/theme/theme-colors.js +84 -0
  988. package/dist/theme/theme-colors.js.map +1 -0
  989. package/dist/theme/types.d.ts +94 -0
  990. package/dist/theme/types.d.ts.map +1 -0
  991. package/dist/theme/types.js +2 -0
  992. package/dist/theme/types.js.map +1 -0
  993. package/dist/uploads/UploadAdapter.d.ts +34 -0
  994. package/dist/uploads/UploadAdapter.d.ts.map +1 -0
  995. package/dist/uploads/UploadAdapter.js +2 -0
  996. package/dist/uploads/UploadAdapter.js.map +1 -0
  997. package/dist/uploads/index.d.ts +3 -0
  998. package/dist/uploads/index.d.ts.map +1 -0
  999. package/dist/uploads/index.js +2 -0
  1000. package/dist/uploads/index.js.map +1 -0
  1001. package/dist/uploads/localUpload.d.ts +25 -0
  1002. package/dist/uploads/localUpload.d.ts.map +1 -0
  1003. package/dist/uploads/localUpload.js +65 -0
  1004. package/dist/uploads/localUpload.js.map +1 -0
  1005. package/dist/validation/Validator.d.ts +40 -0
  1006. package/dist/validation/Validator.d.ts.map +1 -0
  1007. package/dist/validation/Validator.js +25 -0
  1008. package/dist/validation/Validator.js.map +1 -0
  1009. package/dist/validation/index.d.ts +5 -0
  1010. package/dist/validation/index.d.ts.map +1 -0
  1011. package/dist/validation/index.js +5 -0
  1012. package/dist/validation/index.js.map +1 -0
  1013. package/dist/validation/rules.d.ts +9 -0
  1014. package/dist/validation/rules.d.ts.map +1 -0
  1015. package/dist/validation/rules.js +61 -0
  1016. package/dist/validation/rules.js.map +1 -0
  1017. package/dist/validation/runValidators.d.ts +30 -0
  1018. package/dist/validation/runValidators.d.ts.map +1 -0
  1019. package/dist/validation/runValidators.js +438 -0
  1020. package/dist/validation/runValidators.js.map +1 -0
  1021. package/dist/validation/uniqueValidator.d.ts +61 -0
  1022. package/dist/validation/uniqueValidator.d.ts.map +1 -0
  1023. package/dist/validation/uniqueValidator.js +80 -0
  1024. package/dist/validation/uniqueValidator.js.map +1 -0
  1025. package/dist/vite.d.ts +19 -0
  1026. package/dist/vite.d.ts.map +1 -0
  1027. package/dist/vite.js +696 -0
  1028. package/dist/vite.js.map +1 -0
  1029. package/dist/widgets/index.d.ts +2 -0
  1030. package/dist/widgets/index.d.ts.map +1 -0
  1031. package/dist/widgets/index.js +7 -0
  1032. package/dist/widgets/index.js.map +1 -0
  1033. package/dist/widgets/registry.d.ts +32 -0
  1034. package/dist/widgets/registry.d.ts.map +1 -0
  1035. package/dist/widgets/registry.js +17 -0
  1036. package/dist/widgets/registry.js.map +1 -0
  1037. package/package.json +101 -0
  1038. package/src/Cluster.test.ts +283 -0
  1039. package/src/Cluster.ts +83 -0
  1040. package/src/Column.test.ts +140 -0
  1041. package/src/Column.ts +612 -0
  1042. package/src/Global.test.ts +367 -0
  1043. package/src/Global.ts +169 -0
  1044. package/src/Page.test.ts +50 -0
  1045. package/src/Page.ts +139 -0
  1046. package/src/Pilotiq.test.ts +47 -0
  1047. package/src/Pilotiq.ts +705 -0
  1048. package/src/PilotiqRegistry.ts +36 -0
  1049. package/src/PilotiqServiceProvider.ts +69 -0
  1050. package/src/RelationManager.test.ts +400 -0
  1051. package/src/RelationManager.ts +527 -0
  1052. package/src/RenderHook.test.ts +252 -0
  1053. package/src/RenderHook.ts +226 -0
  1054. package/src/Resource.test.ts +240 -0
  1055. package/src/Resource.ts +439 -0
  1056. package/src/RightPanel.test.ts +202 -0
  1057. package/src/RightPanel.ts +132 -0
  1058. package/src/Tab.test.ts +91 -0
  1059. package/src/Tab.ts +156 -0
  1060. package/src/UserMenuItem.ts +145 -0
  1061. package/src/actions/Action.test.ts +2479 -0
  1062. package/src/actions/Action.ts +2124 -0
  1063. package/src/actions/ActionGroup.test.ts +112 -0
  1064. package/src/actions/ActionGroup.ts +173 -0
  1065. package/src/actions/attachFactory.ts +172 -0
  1066. package/src/actions/exportFactory.ts +215 -0
  1067. package/src/actions/importFactory.ts +222 -0
  1068. package/src/actions/index.ts +17 -0
  1069. package/src/applyPageHooks.test.ts +298 -0
  1070. package/src/applyPageHooks.ts +242 -0
  1071. package/src/authorization.test.ts +483 -0
  1072. package/src/breadcrumbs.test.ts +238 -0
  1073. package/src/cells/coerce.test.ts +85 -0
  1074. package/src/cells/coerce.ts +84 -0
  1075. package/src/clusterPaths.ts +35 -0
  1076. package/src/columns/BadgeColumn.test.ts +54 -0
  1077. package/src/columns/BadgeColumn.ts +32 -0
  1078. package/src/columns/BooleanColumn.test.ts +41 -0
  1079. package/src/columns/BooleanColumn.ts +18 -0
  1080. package/src/columns/ColorColumn.test.ts +37 -0
  1081. package/src/columns/ColorColumn.ts +38 -0
  1082. package/src/columns/IconColumn.test.ts +54 -0
  1083. package/src/columns/IconColumn.ts +37 -0
  1084. package/src/columns/ImageColumn.test.ts +41 -0
  1085. package/src/columns/ImageColumn.ts +28 -0
  1086. package/src/columns/SelectColumn.ts +60 -0
  1087. package/src/columns/TextColumn.test.ts +190 -0
  1088. package/src/columns/TextColumn.ts +20 -0
  1089. package/src/columns/TextInputColumn.ts +68 -0
  1090. package/src/columns/ToggleColumn.ts +46 -0
  1091. package/src/columns/editableColumns.test.ts +193 -0
  1092. package/src/columns/index.ts +9 -0
  1093. package/src/defaultGlobalPages.ts +95 -0
  1094. package/src/defaultPages.test.ts +634 -0
  1095. package/src/defaultPages.ts +614 -0
  1096. package/src/defaultViewPage.test.ts +147 -0
  1097. package/src/elements/Form.test.ts +223 -0
  1098. package/src/elements/Form.ts +397 -0
  1099. package/src/elements/ListTabs.ts +28 -0
  1100. package/src/elements/Table.test.ts +422 -0
  1101. package/src/elements/Table.ts +816 -0
  1102. package/src/elements/TableGroup.test.ts +149 -0
  1103. package/src/elements/TableGroup.ts +199 -0
  1104. package/src/elements/dispatchAction.test.ts +463 -0
  1105. package/src/elements/dispatchAction.ts +355 -0
  1106. package/src/elements/dispatchForm.test.ts +455 -0
  1107. package/src/elements/dispatchForm.ts +1855 -0
  1108. package/src/elements/dispatchTable.test.ts +1247 -0
  1109. package/src/elements/dispatchTable.ts +666 -0
  1110. package/src/elements/index.ts +21 -0
  1111. package/src/entries/BadgeEntry.ts +39 -0
  1112. package/src/entries/CodeEntry.test.ts +40 -0
  1113. package/src/entries/CodeEntry.ts +52 -0
  1114. package/src/entries/ColorEntry.ts +63 -0
  1115. package/src/entries/ComponentEntry.test.ts +173 -0
  1116. package/src/entries/ComponentEntry.ts +95 -0
  1117. package/src/entries/Entry.ts +304 -0
  1118. package/src/entries/IconEntry.ts +49 -0
  1119. package/src/entries/ImageEntry.ts +61 -0
  1120. package/src/entries/KeyValueEntry.ts +47 -0
  1121. package/src/entries/RepeatableEntry.test.ts +239 -0
  1122. package/src/entries/RepeatableEntry.ts +173 -0
  1123. package/src/entries/TextEntry.test.ts +394 -0
  1124. package/src/entries/TextEntry.ts +60 -0
  1125. package/src/entries/index.ts +12 -0
  1126. package/src/entries/leaves.test.ts +306 -0
  1127. package/src/entries/registry.ts +54 -0
  1128. package/src/fields/BuilderField.test.ts +1188 -0
  1129. package/src/fields/BuilderField.ts +568 -0
  1130. package/src/fields/BuilderRelationship.test.ts +811 -0
  1131. package/src/fields/CheckboxField.test.ts +44 -0
  1132. package/src/fields/CheckboxField.ts +27 -0
  1133. package/src/fields/CheckboxListField.test.ts +99 -0
  1134. package/src/fields/CheckboxListField.ts +66 -0
  1135. package/src/fields/ColorPickerField.test.ts +33 -0
  1136. package/src/fields/ColorPickerField.ts +25 -0
  1137. package/src/fields/DateField.ts +54 -0
  1138. package/src/fields/DateTimeField.test.ts +55 -0
  1139. package/src/fields/EmailField.ts +16 -0
  1140. package/src/fields/Field.test.ts +639 -0
  1141. package/src/fields/Field.ts +773 -0
  1142. package/src/fields/FileUploadField.test.ts +97 -0
  1143. package/src/fields/FileUploadField.ts +71 -0
  1144. package/src/fields/HiddenField.test.ts +27 -0
  1145. package/src/fields/HiddenField.ts +28 -0
  1146. package/src/fields/KeyValueField.test.ts +105 -0
  1147. package/src/fields/KeyValueField.ts +55 -0
  1148. package/src/fields/MarkdownField.test.ts +167 -0
  1149. package/src/fields/MarkdownField.ts +151 -0
  1150. package/src/fields/NumberField.ts +33 -0
  1151. package/src/fields/RadioField.test.ts +94 -0
  1152. package/src/fields/RadioField.ts +67 -0
  1153. package/src/fields/RepeaterField.test.ts +1806 -0
  1154. package/src/fields/RepeaterField.ts +791 -0
  1155. package/src/fields/RepeaterRelationship.test.ts +1630 -0
  1156. package/src/fields/RepeaterSimple.test.ts +248 -0
  1157. package/src/fields/RowButton.test.ts +149 -0
  1158. package/src/fields/RowButton.ts +125 -0
  1159. package/src/fields/SelectField.test.ts +192 -0
  1160. package/src/fields/SelectField.ts +235 -0
  1161. package/src/fields/SliderField.test.ts +50 -0
  1162. package/src/fields/SliderField.ts +53 -0
  1163. package/src/fields/SlugField.ts +24 -0
  1164. package/src/fields/TagsInputField.test.ts +154 -0
  1165. package/src/fields/TagsInputField.ts +133 -0
  1166. package/src/fields/TextField.ts +24 -0
  1167. package/src/fields/TextareaField.test.ts +58 -0
  1168. package/src/fields/TextareaField.ts +59 -0
  1169. package/src/fields/ToggleButtonsField.test.ts +106 -0
  1170. package/src/fields/ToggleButtonsField.ts +59 -0
  1171. package/src/fields/ToggleField.ts +16 -0
  1172. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +319 -0
  1173. package/src/fields/optionsResolver.ts +95 -0
  1174. package/src/fields/resolveField.ts +28 -0
  1175. package/src/filters/BooleanFilter.ts +35 -0
  1176. package/src/filters/DateRangeFilter.test.ts +194 -0
  1177. package/src/filters/DateRangeFilter.ts +148 -0
  1178. package/src/filters/Filter.test.ts +268 -0
  1179. package/src/filters/Filter.ts +184 -0
  1180. package/src/filters/FormFilter.test.ts +238 -0
  1181. package/src/filters/FormFilter.ts +215 -0
  1182. package/src/filters/MultiSelectFilter.test.ts +119 -0
  1183. package/src/filters/MultiSelectFilter.ts +78 -0
  1184. package/src/filters/QueryBuilderFilter.test.ts +644 -0
  1185. package/src/filters/QueryBuilderFilter.ts +398 -0
  1186. package/src/filters/SelectFilter.ts +46 -0
  1187. package/src/filters/TernaryFilter.test.ts +160 -0
  1188. package/src/filters/TernaryFilter.ts +72 -0
  1189. package/src/filters/TrashedFilter.test.ts +149 -0
  1190. package/src/filters/TrashedFilter.ts +55 -0
  1191. package/src/filters/queryBuilder/BooleanConstraint.ts +31 -0
  1192. package/src/filters/queryBuilder/Constraint.ts +115 -0
  1193. package/src/filters/queryBuilder/DateConstraint.ts +69 -0
  1194. package/src/filters/queryBuilder/NumberConstraint.ts +66 -0
  1195. package/src/filters/queryBuilder/SelectConstraint.ts +72 -0
  1196. package/src/filters/queryBuilder/TextConstraint.ts +65 -0
  1197. package/src/filters/queryBuilder/index.ts +12 -0
  1198. package/src/icons/index.ts +2 -0
  1199. package/src/icons/lucide.ts +204 -0
  1200. package/src/icons/registry.test.ts +56 -0
  1201. package/src/icons/registry.ts +41 -0
  1202. package/src/icons/types.ts +47 -0
  1203. package/src/index.ts +521 -0
  1204. package/src/io/csv.test.ts +142 -0
  1205. package/src/io/csv.ts +170 -0
  1206. package/src/nestedRelationManagerData.test.ts +526 -0
  1207. package/src/notifications/Notification.test.ts +210 -0
  1208. package/src/notifications/Notification.ts +354 -0
  1209. package/src/notifications/broadcast.test.ts +110 -0
  1210. package/src/notifications/broadcast.ts +95 -0
  1211. package/src/notifications/database.test.ts +383 -0
  1212. package/src/notifications/database.ts +398 -0
  1213. package/src/notifications/databaseNotifications.test.ts +187 -0
  1214. package/src/notifications/dispatchNotificationAction.test.ts +341 -0
  1215. package/src/notifications/dispatchNotificationAction.ts +142 -0
  1216. package/src/notifications/flash.test.ts +89 -0
  1217. package/src/notifications/flash.ts +71 -0
  1218. package/src/notifications/index.ts +45 -0
  1219. package/src/notifications/registerBroadcastAuth.test.ts +134 -0
  1220. package/src/notifications/registerBroadcastAuth.ts +100 -0
  1221. package/src/notifications/resolveSavedNotification.test.ts +82 -0
  1222. package/src/notifications/resolveSavedNotification.ts +59 -0
  1223. package/src/notifications/types.ts +93 -0
  1224. package/src/orm/m2mAccessor.ts +66 -0
  1225. package/src/orm/modelDefaults.test.ts +633 -0
  1226. package/src/orm/modelDefaults.ts +632 -0
  1227. package/src/pageData.test.ts +1121 -0
  1228. package/src/pageData.ts +4662 -0
  1229. package/src/plugins/index.ts +1 -0
  1230. package/src/plugins/themeEditor.ts +24 -0
  1231. package/src/react/AppShell.tsx +148 -0
  1232. package/src/react/CommandPalette.tsx +375 -0
  1233. package/src/react/FormStateContext.tsx +398 -0
  1234. package/src/react/HeadHooks.tsx +126 -0
  1235. package/src/react/NotificationActionStrip.tsx +263 -0
  1236. package/src/react/NotificationBell.tsx +426 -0
  1237. package/src/react/RenderHookSlot.tsx +32 -0
  1238. package/src/react/RightSidebar.tsx +257 -0
  1239. package/src/react/RightSidebarContext.tsx +211 -0
  1240. package/src/react/RightSidebarTrigger.tsx +53 -0
  1241. package/src/react/SchemaRenderer.tsx +6128 -0
  1242. package/src/react/SearchTrigger.tsx +46 -0
  1243. package/src/react/ThemeProvider.tsx +93 -0
  1244. package/src/react/ThemeSettingsPage.tsx +579 -0
  1245. package/src/react/ThemeToggle.tsx +20 -0
  1246. package/src/react/Toaster.tsx +158 -0
  1247. package/src/react/UserMenu.tsx +196 -0
  1248. package/src/react/WidgetDataContext.tsx +157 -0
  1249. package/src/react/cells/EditableCell.tsx +376 -0
  1250. package/src/react/fieldJsHandler.test.ts +166 -0
  1251. package/src/react/fieldJsHandler.ts +79 -0
  1252. package/src/react/fields/BuilderInput.tsx +995 -0
  1253. package/src/react/fields/CheckboxInput.tsx +39 -0
  1254. package/src/react/fields/CheckboxListInput.tsx +81 -0
  1255. package/src/react/fields/ColorInput.tsx +51 -0
  1256. package/src/react/fields/DateFieldInput.tsx +70 -0
  1257. package/src/react/fields/DateTimeInput.tsx +42 -0
  1258. package/src/react/fields/FieldShell.tsx +107 -0
  1259. package/src/react/fields/FileUploadInput.tsx +189 -0
  1260. package/src/react/fields/HiddenInput.tsx +17 -0
  1261. package/src/react/fields/KeyValueInput.tsx +200 -0
  1262. package/src/react/fields/MarkdownInput.tsx +333 -0
  1263. package/src/react/fields/RadioInput.tsx +60 -0
  1264. package/src/react/fields/RepeaterInput.test.ts +116 -0
  1265. package/src/react/fields/RepeaterInput.tsx +1313 -0
  1266. package/src/react/fields/SelectFieldInput.tsx +257 -0
  1267. package/src/react/fields/SliderInput.tsx +63 -0
  1268. package/src/react/fields/TagsInput.tsx +265 -0
  1269. package/src/react/fields/TextLikeInput.tsx +54 -0
  1270. package/src/react/fields/ToggleButtonsInput.tsx +60 -0
  1271. package/src/react/fields/ToggleFieldInput.tsx +35 -0
  1272. package/src/react/fields/rowChromeButton.tsx +225 -0
  1273. package/src/react/fields/syncRowGates.test.ts +202 -0
  1274. package/src/react/fields/syncRowGates.ts +66 -0
  1275. package/src/react/formStateHelpers.test.ts +295 -0
  1276. package/src/react/formStateHelpers.ts +218 -0
  1277. package/src/react/hooks/use-mobile.ts +19 -0
  1278. package/src/react/icon-context.tsx +60 -0
  1279. package/src/react/index.ts +85 -0
  1280. package/src/react/layouts/SidebarLayout.tsx +239 -0
  1281. package/src/react/layouts/TopbarLayout.tsx +245 -0
  1282. package/src/react/navigate.tsx +37 -0
  1283. package/src/react/registry.ts +48 -0
  1284. package/src/react/right-panel-registry.tsx +47 -0
  1285. package/src/react/theme-preview/apply.ts +99 -0
  1286. package/src/react/theme-preview/build-html.ts +436 -0
  1287. package/src/react/ui/button.tsx +51 -0
  1288. package/src/react/ui/calendar.tsx +67 -0
  1289. package/src/react/ui/checkbox.tsx +29 -0
  1290. package/src/react/ui/dialog.tsx +108 -0
  1291. package/src/react/ui/dropdown-menu.tsx +97 -0
  1292. package/src/react/ui/input.tsx +20 -0
  1293. package/src/react/ui/label.tsx +21 -0
  1294. package/src/react/ui/popover.tsx +50 -0
  1295. package/src/react/ui/select.tsx +169 -0
  1296. package/src/react/ui/separator.tsx +25 -0
  1297. package/src/react/ui/sheet.tsx +136 -0
  1298. package/src/react/ui/sidebar.tsx +723 -0
  1299. package/src/react/ui/skeleton.tsx +13 -0
  1300. package/src/react/ui/slider.tsx +34 -0
  1301. package/src/react/ui/switch.tsx +28 -0
  1302. package/src/react/ui/table.tsx +105 -0
  1303. package/src/react/ui/tabs.tsx +63 -0
  1304. package/src/react/ui/textarea.tsx +18 -0
  1305. package/src/react/ui/tooltip.tsx +64 -0
  1306. package/src/react/useResizableWidth.ts +139 -0
  1307. package/src/react/utils.ts +6 -0
  1308. package/src/react/widgetRegistry.test.ts +43 -0
  1309. package/src/react/widgetRegistry.ts +50 -0
  1310. package/src/react/widgets/StatsOverviewRenderer.tsx +232 -0
  1311. package/src/react/widgets/TableWidgetRenderer.tsx +231 -0
  1312. package/src/react/widgets/ViewRenderer.tsx +71 -0
  1313. package/src/relationManagerData.test.ts +1146 -0
  1314. package/src/richtext/index.ts +8 -0
  1315. package/src/richtext/registry.ts +89 -0
  1316. package/src/routes-nested-relations.test.ts +676 -0
  1317. package/src/routes-relations.test.ts +972 -0
  1318. package/src/routes.test.ts +1886 -0
  1319. package/src/routes.ts +3262 -0
  1320. package/src/schema/Alert.test.ts +63 -0
  1321. package/src/schema/Alert.ts +49 -0
  1322. package/src/schema/Block.ts +169 -0
  1323. package/src/schema/Breadcrumbs.ts +40 -0
  1324. package/src/schema/Card.ts +35 -0
  1325. package/src/schema/Divider.ts +20 -0
  1326. package/src/schema/Element.ts +219 -0
  1327. package/src/schema/EmptyState.test.ts +37 -0
  1328. package/src/schema/EmptyState.ts +63 -0
  1329. package/src/schema/Fieldset.ts +43 -0
  1330. package/src/schema/Grid.ts +43 -0
  1331. package/src/schema/Group.ts +30 -0
  1332. package/src/schema/Heading.ts +39 -0
  1333. package/src/schema/Html.ts +67 -0
  1334. package/src/schema/Icon.ts +54 -0
  1335. package/src/schema/Image.ts +57 -0
  1336. package/src/schema/LinkTag.ts +41 -0
  1337. package/src/schema/Markdown.ts +85 -0
  1338. package/src/schema/MetaTag.ts +41 -0
  1339. package/src/schema/RelationTabs.ts +71 -0
  1340. package/src/schema/ScriptTag.ts +55 -0
  1341. package/src/schema/Section.ts +143 -0
  1342. package/src/schema/ServerDataElement.test.ts +140 -0
  1343. package/src/schema/ServerDataElement.ts +156 -0
  1344. package/src/schema/Split.ts +50 -0
  1345. package/src/schema/Stat.test.ts +118 -0
  1346. package/src/schema/Stat.ts +154 -0
  1347. package/src/schema/StatsOverview.test.ts +141 -0
  1348. package/src/schema/StatsOverview.ts +119 -0
  1349. package/src/schema/StyleTag.ts +35 -0
  1350. package/src/schema/TableWidget.test.ts +297 -0
  1351. package/src/schema/TableWidget.ts +289 -0
  1352. package/src/schema/Tabs.ts +79 -0
  1353. package/src/schema/Text.ts +58 -0
  1354. package/src/schema/UnorderedList.ts +49 -0
  1355. package/src/schema/View.test.ts +111 -0
  1356. package/src/schema/View.ts +127 -0
  1357. package/src/schema/Wizard.ts +108 -0
  1358. package/src/schema/containers.test.ts +446 -0
  1359. package/src/schema/headTags.test.ts +134 -0
  1360. package/src/schema/index.ts +39 -0
  1361. package/src/schema/primes.test.ts +269 -0
  1362. package/src/schema/resolveSchema.test.ts +329 -0
  1363. package/src/schema/resolveSchema.ts +807 -0
  1364. package/src/schema/sanitize.ts +49 -0
  1365. package/src/search.test.ts +446 -0
  1366. package/src/search.ts +178 -0
  1367. package/src/sessionFilters.test.ts +352 -0
  1368. package/src/sessionFilters.ts +133 -0
  1369. package/src/summarizers/Summarizer.test.ts +84 -0
  1370. package/src/summarizers/Summarizer.ts +123 -0
  1371. package/src/summarizers/index.ts +11 -0
  1372. package/src/theme/base-colors.ts +68 -0
  1373. package/src/theme/chart-colors.ts +50 -0
  1374. package/src/theme/colors.ts +447 -0
  1375. package/src/theme/generate-css.test.ts +139 -0
  1376. package/src/theme/generate-css.ts +44 -0
  1377. package/src/theme/generate-scale.test.ts +106 -0
  1378. package/src/theme/generate-scale.ts +97 -0
  1379. package/src/theme/icon-map.ts +42 -0
  1380. package/src/theme/index.ts +28 -0
  1381. package/src/theme/migrate.ts +81 -0
  1382. package/src/theme/presets.ts +135 -0
  1383. package/src/theme/radius.ts +18 -0
  1384. package/src/theme/resolve.test.ts +238 -0
  1385. package/src/theme/resolve.ts +96 -0
  1386. package/src/theme/spacing.ts +18 -0
  1387. package/src/theme/theme-colors.ts +88 -0
  1388. package/src/theme/types.ts +125 -0
  1389. package/src/uploads/UploadAdapter.ts +35 -0
  1390. package/src/uploads/index.ts +2 -0
  1391. package/src/uploads/localUpload.test.ts +70 -0
  1392. package/src/uploads/localUpload.ts +84 -0
  1393. package/src/validation/Validator.ts +49 -0
  1394. package/src/validation/index.ts +28 -0
  1395. package/src/validation/rules.ts +78 -0
  1396. package/src/validation/runValidators.ts +435 -0
  1397. package/src/validation/uniqueValidator.test.ts +196 -0
  1398. package/src/validation/uniqueValidator.ts +133 -0
  1399. package/src/validation/validators.test.ts +268 -0
  1400. package/src/vite.ts +758 -0
  1401. package/src/widgets/index.ts +10 -0
  1402. package/src/widgets/registry.ts +45 -0
  1403. package/src/widgets.test.ts +592 -0
  1404. package/tsconfig.build.json +11 -0
  1405. package/tsconfig.json +4 -0
  1406. package/tsconfig.test.json +10 -0
  1407. package/views/react/Dashboard.tsx +27 -0
  1408. package/views/react/Resources/Form.tsx +102 -0
  1409. package/views/react/Resources/Index.tsx +49 -0
@@ -0,0 +1,2124 @@
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 {
5
+ safeManagerPolicy,
6
+ type RelationManager,
7
+ type RelationManagerContext,
8
+ } from '../RelationManager.js'
9
+ import {
10
+ computeMorphPayload,
11
+ getMorphRelationDescriptor,
12
+ getParentRelationDescriptor,
13
+ type ModelLike,
14
+ } from '../orm/modelDefaults.js'
15
+ import { resolveM2MAccessor } from '../orm/m2mAccessor.js'
16
+ import { buildImportSchema as buildImportModalSchema } from './importFactory.js'
17
+ import { buildAttachModalSchema } from './attachFactory.js'
18
+
19
+ /**
20
+ * Where an Action renders. `inline` is the default — appears wherever the
21
+ * Action sits in the schema tree (e.g. a button inside a Card). The other
22
+ * three are list-page patterns:
23
+ * - `header` — top-right of a resource list (e.g. "Create new")
24
+ * - `bulk` — appears in the action bar when rows are selected
25
+ * - `row` — per-row dropdown menu entry
26
+ */
27
+ export type ActionPlacement = 'inline' | 'bulk' | 'row' | 'header'
28
+
29
+ /**
30
+ * Context handed to an Action's handler at dispatch time. `record` is set
31
+ * for row/inline actions that operate on a single entity; `records` is set
32
+ * for bulk actions. `values` carries any additional payload submitted with
33
+ * the action (useful when an action has its own confirmation dialog form).
34
+ * `request` is the raw `AppRequest` for handlers that need direct access
35
+ * (auth, headers, etc).
36
+ */
37
+ export interface ActionContext {
38
+ record?: unknown
39
+ records?: unknown[]
40
+ user?: unknown
41
+ values?: Record<string, unknown>
42
+ request?: unknown
43
+ /**
44
+ * Row-scoped context populated when this action was dispatched as a
45
+ * Repeater / Builder `extraItemActions` button. `index` is the row's
46
+ * 0-based position; `id` is the row's stable `__id`; `values` is the
47
+ * row's submitted fields (the row's `data` body inside Builder); for
48
+ * Builder, `blockType` carries the matched block name. `fieldName`
49
+ * is the parent Repeater/Builder field's name — useful when a single
50
+ * handler is shared across multiple repeater fields.
51
+ *
52
+ * Always undefined for non-row actions. Handlers that don't care about
53
+ * row context just ignore this field.
54
+ */
55
+ row?: {
56
+ index: number
57
+ id: string
58
+ values: Record<string, unknown>
59
+ fieldName: string
60
+ blockType?: string
61
+ }
62
+ /**
63
+ * Stamped by the manager-scoped `_action` route when an action is
64
+ * dispatched from a `RelationManager`'s table. Carries the freshly-
65
+ * loaded parent record + relationship key so handlers can call
66
+ * `parent.related(name).attach(...) / detach(...) / sync(...)`
67
+ * (rudder ORM accessor) without re-loading the parent themselves.
68
+ *
69
+ * Always undefined for resource-level / page-level / dashboard
70
+ * dispatch. M2M-aware factories (`relationAttach / relationDetach /
71
+ * relationBulkDetach`) consult this and `notify` an error when it's
72
+ * missing — that means the action got dispatched outside the manager
73
+ * scope (misconfiguration).
74
+ */
75
+ relation?: {
76
+ parent: unknown
77
+ parentId: string
78
+ relationship: string
79
+ }
80
+ }
81
+
82
+ /** Convenience type: handlers can return either a built `Notification`
83
+ * instance, its serialized meta, or arrays of either. */
84
+ export type NotificationLike =
85
+ | Notification
86
+ | NotificationMeta
87
+ | ReadonlyArray<Notification | NotificationMeta>
88
+
89
+ /** Download envelope a handler can return to ask the route layer to
90
+ * stream a file back instead of issuing the standard JSON / 303
91
+ * response. Used by `Action.export / Action.bulkExport`; any handler
92
+ * is free to return one. The route writes `Content-Type` +
93
+ * `Content-Disposition: attachment; filename="…"` and ends with
94
+ * `body`; the client renderer detects the disposition header and
95
+ * triggers a browser download via a synthesized `<a download>`. */
96
+ export interface DownloadEnvelope {
97
+ filename: string
98
+ contentType: string
99
+ body: string
100
+ }
101
+
102
+ /**
103
+ * Result a handler may return to influence the response. `void` is the
104
+ * default — the dispatcher 303-redirects to the page the action was
105
+ * triggered from. Returning `{ redirect }` overrides that with an
106
+ * explicit URL. Returning `{ notify }` flashes one or more toast
107
+ * notifications on the next render. Returning `{ download }` triggers
108
+ * a file download (mutually exclusive with `redirect` — the route
109
+ * prefers the download branch when both are set). Throw an Error to
110
+ * surface as a 500 with the message.
111
+ */
112
+ export type ActionResult =
113
+ | void
114
+ | { redirect?: string; notify?: NotificationLike; download?: DownloadEnvelope }
115
+
116
+ export type ActionHandler = (ctx: ActionContext) => ActionResult | Promise<ActionResult>
117
+
118
+ /**
119
+ * A confirmation prompt shown before the handler runs. A bare string is
120
+ * shorthand for `{ message: string }`; the object form lets callers
121
+ * override the dialog title and confirm-button label.
122
+ */
123
+ export interface ActionConfirm {
124
+ title?: string
125
+ message: string
126
+ confirmLabel?: string
127
+ }
128
+
129
+ /** HTTP method for form-style actions. `'get'` is implied by `.href()`; the
130
+ * others spawn a `<form>`-wrapped submit button at render time. */
131
+ export type ActionMethod = 'post' | 'put' | 'patch' | 'delete'
132
+
133
+ /** Visual color preset. Maps to a tailwind class group at render time.
134
+ * `destructive` is what `Action.destructive()` sugar sets; the other
135
+ * presets exist so users can opt-in explicitly. */
136
+ export type ActionColor = 'primary' | 'destructive' | 'success' | 'warning' | 'info' | 'ghost'
137
+
138
+ /** Visual size preset. Maps to button height + padding + text size. */
139
+ export type ActionSize = 'sm' | 'md' | 'lg'
140
+
141
+ /** Context passed to visibility / disabled callbacks. `record` is set
142
+ * for single-target evaluation (row actions, edit-page header actions);
143
+ * `records` for bulk evaluations; `user` from the request when wired.
144
+ *
145
+ * `values` is populated only when this action is being evaluated inside
146
+ * a Repeater / Builder row at meta-build time (via `extraItemActions`).
147
+ * It mirrors the resolver's row-scoped values map — predicates branch
148
+ * on the row's submitted fields (e.g. `({ values }) => values.status !==
149
+ * 'archived'`). */
150
+ export interface ActionVisibilityContext {
151
+ record?: unknown
152
+ records?: unknown[]
153
+ user?: unknown
154
+ values?: Record<string, unknown>
155
+ }
156
+
157
+ /** Boolean-or-callback rule used by `.visible()` / `.hidden()` /
158
+ * `.disabled()`. Boolean values short-circuit; functions receive the
159
+ * evaluation context and return the result (sync or async).
160
+ *
161
+ * Async support landed with Plan #10 authorization — `Resource.canEdit`
162
+ * etc. return Promise<boolean>, and the `Action.create/edit/view/delete`
163
+ * factories install those predicates as visibility rules. Sync rules
164
+ * keep working unchanged; the awaiter coerces both. */
165
+ export type VisibilityRule =
166
+ | boolean
167
+ | ((ctx: ActionVisibilityContext) => boolean | Promise<boolean>)
168
+
169
+ /** Modal width preset — maps to a max-width class on the Dialog popup. */
170
+ export type ActionModalWidth = 'sm' | 'md' | 'lg' | 'xl'
171
+
172
+ /** Horizontal alignment applied to the modal's header / body / footer
173
+ * text. Matches Filament v5's `modalAlignment()` (start / center / end). */
174
+ export type ActionModalAlignment = 'start' | 'center' | 'end'
175
+
176
+ /** Color preset for the icon shown next to the modal heading. Mirrors
177
+ * `BadgeColor` so userspace doesn't have to learn another scale. */
178
+ export type ActionModalIconColor =
179
+ | 'gray' | 'primary' | 'success' | 'warning' | 'destructive' | 'info'
180
+
181
+ /** Context shape passed to `ReplicateOptions.getCreatedNotificationTitle`.
182
+ * Single-row factories (`replicate`, `relationReplicate`) populate
183
+ * `replica` (the just-created record returned by `M.create`) and
184
+ * `source` (the original row). Bulk factories (`bulkReplicate`,
185
+ * `relationBulkReplicate`) populate `count` (number of successful
186
+ * creates) and `records` (the original selected rows). The opposite
187
+ * fields are always undefined for the other call site so consumers
188
+ * can branch on whichever is set. */
189
+ export interface ReplicateNotificationContext {
190
+ replica?: unknown
191
+ source?: unknown
192
+ count?: number
193
+ records?: unknown[]
194
+ }
195
+
196
+ /** Context shape passed to `ReplicateOptions.getRedirectUrl`. Single-row
197
+ * factories only — bulk variants stay on the list / manager URL
198
+ * regardless. `replica` is the freshly-created record; `source` is
199
+ * the original. */
200
+ export interface ReplicateRedirectContext {
201
+ replica: unknown
202
+ source: unknown
203
+ }
204
+
205
+ /** Options bag for `Action.replicate` and its three siblings
206
+ * (`bulkReplicate`, `relationReplicate`, `relationBulkReplicate`).
207
+ * Every field optional. */
208
+ export interface ReplicateOptions {
209
+ /** Attribute names to drop from the replicated payload IN ADDITION TO
210
+ * the always-stripped primary key + soft-delete column. Useful for
211
+ * unique columns the source row holds (e.g. `slug`, `email`) so the
212
+ * duplicate doesn't trip a unique constraint on save. */
213
+ excludeAttributes?: ReadonlyArray<string>
214
+ /** Mutate the prepared replica before it's persisted. Receives the
215
+ * already-stripped attributes plus the source record; must return the
216
+ * attributes to persist (mutate or replace). Useful for stamping
217
+ * "Copy of" prefixes onto a name column, regenerating slugs, etc. */
218
+ beforeReplicaSaved?: (
219
+ replica: Record<string, unknown>,
220
+ source: unknown,
221
+ ) => Record<string, unknown> | Promise<Record<string, unknown>>
222
+ /** Override the success notification title. Single-row factories
223
+ * receive `{ replica, source }`; bulk factories receive
224
+ * `{ count, records }` (`replica`/`source` undefined). Return a
225
+ * string to use it; return `undefined` to fall back to the default
226
+ * (`"${labelSingular} replicated"` for single-row,
227
+ * `"${count} ${label(s)} replicated"` for bulk). Sync or async. */
228
+ getCreatedNotificationTitle?: (
229
+ ctx: ReplicateNotificationContext,
230
+ ) => string | undefined | Promise<string | undefined>
231
+ /** Override the redirect URL. Single-row factories only — bulk
232
+ * variants don't redirect. Receives `{ replica, source }`. Return a
233
+ * string to use it; return `undefined` to fall back to the default
234
+ * (the new record's edit page for `replicate`; the manager list
235
+ * URL for `relationReplicate`, owned by the route layer). Sync or
236
+ * async. */
237
+ getRedirectUrl?: (
238
+ ctx: ReplicateRedirectContext,
239
+ ) => string | undefined | Promise<string | undefined>
240
+ }
241
+
242
+ /** Structural shape of a Resource class for the factory functions —
243
+ * matches `Resource.ts` exactly but keeps Action.ts free of an import
244
+ * cycle. The optional fields are the Plan #10 policy predicates; their
245
+ * defaults (return `true`) mean missing methods are equivalent to
246
+ * "always allowed." */
247
+ interface ResourceLike {
248
+ labelSingular: string
249
+ /** Plural label. When unset, factories fall back to
250
+ * `${labelSingular}s` (naive). Used by bulk-action notification
251
+ * copy so "1 Post" / "5 Posts" render correctly. */
252
+ label?: string
253
+ getSlug(): string
254
+ /** Cluster the resource belongs to, when registered via `Pilotiq.clusters`.
255
+ * Action factories thread the cluster slug into URL builders so
256
+ * `${basePath}/<cluster>/<slug>/...` round-trips correctly. */
257
+ cluster?: { getSlug(): string }
258
+ /** Plan #13 — soft-delete opt-in flag. When true, `Action.delete`
259
+ * auto-hides on already-trashed rows; `Action.restore` /
260
+ * `Action.forceDelete` auto-show on trashed rows. */
261
+ softDeletes?: boolean
262
+ /** Plan #13 — column name carrying the soft-delete timestamp.
263
+ * Defaults to `'deletedAt'` when undefined. */
264
+ deletedAtColumn?: string
265
+ canCreate?(user: unknown): boolean | Promise<boolean>
266
+ canEdit?(user: unknown, record: unknown): boolean | Promise<boolean>
267
+ canView?(user: unknown, record: unknown): boolean | Promise<boolean>
268
+ canViewAny?(user: unknown): boolean | Promise<boolean>
269
+ canDelete?(user: unknown, record: unknown): boolean | Promise<boolean>
270
+ canRestore?(user: unknown, record: unknown): boolean | Promise<boolean>
271
+ canForceDelete?(user: unknown, record: unknown): boolean | Promise<boolean>
272
+ /** Resource model adapter (set when the resource opts into auto-CRUD
273
+ * via `static model = M`). Import / Export factories need access to
274
+ * `create / update / find / query` to drive their writes / reads. */
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
+ model?: any
277
+ /** Resource table configurator. Export factory calls
278
+ * `R.table(Table.make())` to discover columns + the records handler
279
+ * for the "filtered" / "all" scope. */
280
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
281
+ table?(t: any): any
282
+ }
283
+
284
+ /** Cluster-aware resource base path. Mirrors `clusterPaths.resourceBasePath`
285
+ * but uses the structural `ResourceLike` shape so `Action.ts` stays
286
+ * cycle-free against `Resource.ts`. */
287
+ function resourceBase(basePath: string, R: ResourceLike): string {
288
+ if (R.cluster) return `${basePath}/${R.cluster.getSlug()}/${R.getSlug()}`
289
+ return `${basePath}/${R.getSlug()}`
290
+ }
291
+
292
+ /** Pick the right label form for a count — `labelSingular` for 1,
293
+ * `label` (plural, lowercased) for any other count. Fall back to a
294
+ * naive `${labelSingular}s` when no plural label is set. Used by bulk
295
+ * notification copy so we don't ship "1 posts moved to trash". */
296
+ function labelForCount(R: ResourceLike, n: number): string {
297
+ if (n === 1) return R.labelSingular.toLowerCase()
298
+ const plural = R.label?.toLowerCase()
299
+ return plural ?? `${R.labelSingular.toLowerCase()}s`
300
+ }
301
+
302
+ /** True when a `RelationManagerContext.mode` denotes a pivot-mutation
303
+ * shape — i.e. a many-to-many relation. All three modes share the
304
+ * `attach` / `detach` / `sync` accessor surface (the rudder ORM stamps
305
+ * + filters the polymorphic discriminator transparently for the morph
306
+ * variants). The `relationCreate / Edit / Delete` factories auto-hide
307
+ * under any of these modes because per-pivot-row create / edit / delete
308
+ * is meaningless — users create the related record via its own Resource,
309
+ * then attach via `relationAttach`. */
310
+ function isM2MMode(mode: RelationManagerContext['mode']): boolean {
311
+ return mode === 'belongsToMany' || mode === 'morphToMany' || mode === 'morphedByMany'
312
+ }
313
+
314
+ /**
315
+ * Phase B — build the URL prefix for a relation factory action. Without
316
+ * a `chain` (depth-1 manager), this is the familiar
317
+ * `${base}/${parentSlug}/${parentId}/${relationship}`. With a chain
318
+ * (depth-2 nested manager), it threads the outer record + relationship
319
+ * between the parent slug and the leaf parent id:
320
+ *
321
+ * `${base}/${parentSlug}/${chain[0].recordId}/${chain[0].relationship}/${parentId}/${relationship}`
322
+ *
323
+ * Pure; takes a `RelationManagerContext` and emits a string. The leaf
324
+ * record id (and trailing `/edit`, `/delete`, etc.) gets appended by
325
+ * the caller.
326
+ */
327
+ function relationUrlPrefix(ctx: RelationManagerContext): string {
328
+ const head = `${ctx.basePath}/${ctx.parentSlug}`
329
+ const chain = ctx.chain ?? []
330
+ let mid = ''
331
+ for (const step of chain) {
332
+ mid += `/${step.recordId}/${step.relationship}`
333
+ }
334
+ return `${head}${mid}/${ctx.parentId}/${ctx.relationship}`
335
+ }
336
+
337
+ /**
338
+ * Compute the parent-attachment payload to force-pin onto a relation
339
+ * replica. For `hasMany`, returns `{ [foreignKey]: parentId }` from the
340
+ * parent's `static relations[name]` descriptor. For `morphMany` /
341
+ * `morphOne`, returns `{ <morphName>Id, <morphName>Type }` via
342
+ * `computeMorphPayload(parentRecord)`. Returns `{}` when no descriptor
343
+ * matches — the route dispatcher already auto-hides under M2M / morphTo,
344
+ * so missing descriptors there are a no-op rather than an error. Pure;
345
+ * exported for tests and re-used by both factories.
346
+ */
347
+ function computeRelationPin(
348
+ ctx: RelationManagerContext,
349
+ ): Record<string, unknown> {
350
+ const parentModel = (ctx.parentRecord as { constructor?: ModelLike } | null | undefined)?.constructor
351
+ if (!parentModel) return {}
352
+ const rel = ctx.relationship
353
+ // Polymorphic owner side first — `morphMany` carries no foreignKey
354
+ // and would fail the hasMany descriptor's gate.
355
+ if (ctx.mode === 'morphMany') {
356
+ const morph = getMorphRelationDescriptor(parentModel, rel)
357
+ if (!morph) return {}
358
+ try { return computeMorphPayload(ctx.parentRecord, morph) }
359
+ catch { return {} }
360
+ }
361
+ const desc = getParentRelationDescriptor(parentModel, rel)
362
+ if (!desc) return {}
363
+ return { [desc.foreignKey]: ctx.parentId }
364
+ }
365
+
366
+ /**
367
+ * Build + persist a single relation replica. Runs the strip set
368
+ * (PK + soft-delete column on the **related** Resource +
369
+ * `opts.excludeAttributes`), force-pins the parent attachment columns,
370
+ * runs the optional `beforeReplicaSaved` hook, and calls
371
+ * `Related.model.create(...)`. Returns the model's create result so
372
+ * callers can read its primary key for redirect targeting.
373
+ *
374
+ * Throws when the related Resource has no model — caller (single-row
375
+ * factory) catches and surfaces an error notification; bulk caller
376
+ * checks the model presence ahead of the loop.
377
+ */
378
+ async function persistRelationReplica(
379
+ _M: typeof RelationManager,
380
+ ctx: RelationManagerContext,
381
+ source: unknown,
382
+ opts: ReplicateOptions,
383
+ ): Promise<unknown> {
384
+ const Related = ctx.related
385
+ if (!Related?.model || typeof Related.model.create !== 'function') {
386
+ throw new Error('Related Resource has no model.create')
387
+ }
388
+ const M2 = Related.model as ModelLike
389
+ const pkCol = (M2 as { primaryKey?: string }).primaryKey ?? 'id'
390
+ const trashedCol = Related.deletedAtColumn ?? 'deletedAt'
391
+ const skip = new Set<string>([pkCol, trashedCol, ...(opts.excludeAttributes ?? [])])
392
+ let replica: Record<string, unknown> = {}
393
+ for (const [k, v] of Object.entries(source as Record<string, unknown>)) {
394
+ if (skip.has(k)) continue
395
+ replica[k] = v
396
+ }
397
+ // Force-pin the parent attachment AFTER the strip but BEFORE the
398
+ // user mutator, so `beforeReplicaSaved` can read / override the FK
399
+ // if it really wants to (rare). Tampered source rows can't slip a
400
+ // different parent in by riding their own FK column — the pin
401
+ // overwrites whatever value was there.
402
+ Object.assign(replica, computeRelationPin(ctx))
403
+ if (opts.beforeReplicaSaved) {
404
+ replica = await opts.beforeReplicaSaved(replica, source)
405
+ }
406
+ return M2.create(replica)
407
+ }
408
+
409
+ /**
410
+ * Single-row dispatch for `Action.relationReplicate`. Resolves
411
+ * `ctx.record` (loaded by the route's resolveRecord hook), validates,
412
+ * persists the replica, and shapes the success notification. Errors
413
+ * are caught and surfaced as error toasts.
414
+ */
415
+ async function runRelationReplicateRow(
416
+ M: typeof RelationManager,
417
+ ctx: RelationManagerContext,
418
+ hctx: ActionContext,
419
+ opts: ReplicateOptions,
420
+ ): Promise<ActionResult> {
421
+ const source = hctx.record
422
+ if (!source || typeof source !== 'object') {
423
+ return { notify: { title: 'Replicate failed: source record missing', type: 'error' } as never }
424
+ }
425
+ const Related = ctx.related
426
+ if (!Related?.model || typeof Related.model.create !== 'function') {
427
+ return { notify: { title: 'Replicate not configured (related Resource has no model.create)', type: 'error' } as never }
428
+ }
429
+ let created: unknown
430
+ try {
431
+ created = await persistRelationReplica(M, ctx, source, opts)
432
+ } catch (err) {
433
+ return { notify: { title: `Replicate failed: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
434
+ }
435
+ const overrideTitle = opts.getCreatedNotificationTitle
436
+ ? await opts.getCreatedNotificationTitle({ replica: created, source })
437
+ : undefined
438
+ const title = overrideTitle !== undefined ? overrideTitle : `${M.getLabelSingular()} replicated`
439
+ // The manager-scoped `_action/:actionName` route falls back to the
440
+ // manager list URL when `result.redirect` is undefined, so we only
441
+ // emit `redirect` when the user override returned a string. That
442
+ // way default behavior (route owns the fallback) is unchanged.
443
+ const overrideRedirect = opts.getRedirectUrl
444
+ ? await opts.getRedirectUrl({ replica: created, source })
445
+ : undefined
446
+ return {
447
+ ...(overrideRedirect !== undefined ? { redirect: overrideRedirect } : {}),
448
+ notify: { title, type: 'success' } as never,
449
+ }
450
+ }
451
+
452
+ /** Read `record[R.deletedAtColumn ?? 'deletedAt']` and return true when
453
+ * the row is currently trashed (soft-deleted). Permissive on shape —
454
+ * bare `null` / `undefined` count as live; any other truthy value is
455
+ * trashed. */
456
+ function isTrashed(record: unknown, R: ResourceLike): boolean {
457
+ if (!record || typeof record !== 'object') return false
458
+ const col = R.deletedAtColumn ?? 'deletedAt'
459
+ const v = (record as Record<string, unknown>)[col]
460
+ return v !== null && v !== undefined
461
+ }
462
+
463
+ /** Lazy-load the `Table` class for use inside Action handlers. Direct
464
+ * module-level import would cycle (Table → Action → Table); dynamic
465
+ * import inside a handler runs after both modules have finished
466
+ * loading. Cached after first call so we don't pay the import cost
467
+ * on every dispatched export. */
468
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
469
+ let _TableClass: any | undefined
470
+ async function loadTableClass(): Promise<unknown> {
471
+ if (_TableClass !== undefined) return _TableClass.make()
472
+ const mod = await import('../elements/Table.js')
473
+ _TableClass = mod.Table
474
+ return _TableClass.make()
475
+ }
476
+
477
+ /** Call a (possibly undefined) Resource predicate. When unset, the
478
+ * predicate is treated as "allowed" (returns true) so the factory
479
+ * doesn't hide actions on Resources that haven't opted into Plan #10. */
480
+ function callPredicate(
481
+ fn: ((user: unknown, record?: unknown) => boolean | Promise<boolean>) | undefined,
482
+ user: unknown,
483
+ record?: unknown,
484
+ ): boolean | Promise<boolean> {
485
+ if (!fn) return true
486
+ return fn(user, record)
487
+ }
488
+
489
+ /** Render-time meta for an action that opens a modal (with or without a
490
+ * form schema). When `meta.children` is also populated by the resolver,
491
+ * the modal renders those Elements as a form whose values pass through
492
+ * to the handler as `ctx.values`. */
493
+ export interface ActionModalMeta {
494
+ heading?: string
495
+ description?: string
496
+ submitLabel?: string
497
+ cancelLabel?: string
498
+ icon?: string
499
+ /** Color preset applied to the heading icon (`modalIconColor()`).
500
+ * Renderer maps to a tailwind text-color class. */
501
+ iconColor?: ActionModalIconColor
502
+ width?: ActionModalWidth
503
+ slideOver?: boolean
504
+ /** Default `true`; emitted only when the user passed `false`. The
505
+ * renderer turns this into Base UI's `disablePointerDismissal`. */
506
+ closeByClickingAway?: boolean
507
+ /** Default `true`; emitted only when `false`. Renderer cancels the
508
+ * Base UI `onOpenChange` event when reason is `'escapeKey'`. */
509
+ closeByEscaping?: boolean
510
+ /** Sticky header inside a scrolling modal body. Default `false`. */
511
+ stickyHeader?: boolean
512
+ /** Sticky footer inside a scrolling modal body. Default `false`. */
513
+ stickyFooter?: boolean
514
+ /** Override the default autofocus behaviour. When `false` no element
515
+ * inside the modal receives focus on mount; when `true` the renderer
516
+ * focuses the first form input (or the submit button when there is
517
+ * no form). When omitted, the legacy default applies (the submit
518
+ * button autofocuses for confirm-only modals). */
519
+ autofocus?: boolean
520
+ /** Horizontal alignment for the heading / body / footer text. */
521
+ alignment?: ActionModalAlignment
522
+ /** When `true`, an X close-button renders in the top-right of the
523
+ * popup. Default `false`. */
524
+ closeButton?: boolean
525
+ }
526
+
527
+ export interface ActionMeta extends ElementMeta {
528
+ type: 'action'
529
+ name: string
530
+ label: string
531
+ placement: ActionPlacement
532
+ destructive: boolean
533
+ icon?: string
534
+ confirm?: ActionConfirm
535
+ href?: string
536
+ method?: ActionMethod
537
+ action?: string
538
+ /** POST URL for handler-style actions. Set server-side by the route
539
+ * registrar so the client knows where to dispatch. */
540
+ dispatchUrl?: string
541
+ /** True when this action submits its enclosing `<form>` — renders as
542
+ * `<button type="submit">` and lets the form's `action`/`method`
543
+ * attributes drive the request. */
544
+ submit?: boolean
545
+ /** When `submit` is true and this id is set, the rendered button uses
546
+ * the HTML `form="<id>"` attribute so it can submit a form it lives
547
+ * outside of (e.g. a Save action in the page header). */
548
+ form?: string
549
+ /** When `submit` is true, attach `name`+`value` to the `<button>` so
550
+ * the clicked button's pair lands in the form body. Used by the
551
+ * "Create & create another" pattern: a secondary submit posts a
552
+ * sentinel like `{ _continueCreate: '1' }` that the server reads to
553
+ * route the redirect back to the create page. */
554
+ formField?: { name: string; value: string }
555
+ /** Modal-style action chrome. Present when `.schema()` and/or any of
556
+ * the `modalXxx` builders ran. The fields themselves arrive on
557
+ * `meta.children` via the schema resolver. */
558
+ modal?: ActionModalMeta
559
+ /** Color preset — drives button colors at render time. `destructive`
560
+ * coincides with `destructive: true` (kept for back-compat). */
561
+ color?: ActionColor
562
+ /** Size preset — drives button height/padding/text-size. */
563
+ size?: ActionSize
564
+ /** Hover tooltip text. Wraps the rendered button in a Tooltip. */
565
+ tooltip?: string
566
+ /** Outlined trigger style — replaces the solid color background with
567
+ * a border + transparent bg. */
568
+ outlined?: boolean
569
+ /** Icon-only trigger style — hides the label and renders a square
570
+ * button. Requires `icon` to be set. */
571
+ iconOnly?: boolean
572
+ /** Optional badge shown on the trigger (e.g. unread count). */
573
+ badge?: string | number
574
+ badgeColor?: string
575
+ /** Disabled flag set at evaluation time. The trigger renders greyed-out
576
+ * and skips dispatch when true. */
577
+ disabled?: boolean
578
+ /** True when the action has `.visible()`, `.hidden()`, or `.disabled()`
579
+ * rules — the row renderer uses this to know whether to consult the
580
+ * row's `_visibleActions` / `_disabledActions` lookup. Static actions
581
+ * without rules render unconditionally. */
582
+ conditional?: boolean
583
+ }
584
+
585
+ /**
586
+ * Action — a button-or-menu-entry that performs work when clicked.
587
+ *
588
+ * One class for all four placements; pick one via `.inline()` / `.row()` /
589
+ * `.bulk()` / `.header()` (or `.placement(...)`). Actions can sit inline
590
+ * inside any container Element (Card, Section, etc.) or attach to a
591
+ * Resource's list page.
592
+ *
593
+ * Phase 1.4 ships the shape + serialization. Handler dispatch and
594
+ * confirmation-form support land in Phase 2 alongside Resource lifecycle.
595
+ */
596
+ export class Action extends Element {
597
+ readonly name: string
598
+
599
+ protected _label: string
600
+ protected _icon?: string
601
+ protected _placement: ActionPlacement = 'inline'
602
+ protected _destructive = false
603
+ protected _confirm?: ActionConfirm
604
+ protected _handler?: ActionHandler
605
+ /**
606
+ * Notification-registry handler key — set by `.handler(string)`. The
607
+ * notification-action route looks this up against
608
+ * `Pilotiq.notificationHandlers({…})` at request time. Mutually
609
+ * exclusive with the closure `_handler` — passing a string clears
610
+ * `_handler`, passing a function clears `_handlerName`. Closures can't
611
+ * round-trip through a `data` JSON column; the registry path is the
612
+ * persisted-notification escape hatch.
613
+ */
614
+ protected _handlerName?: string
615
+ /**
616
+ * Per-fire context for registry-handler dispatch. Round-trips through
617
+ * the notification's `data.actions` JSON column verbatim and arrives
618
+ * on the handler's `ctx.payload`.
619
+ */
620
+ protected _payload?: Record<string, unknown>
621
+ /**
622
+ * Filament-style chain modifier — when set on an action used inside a
623
+ * `Notification.actions([…])` slot, firing the action also flips the
624
+ * notification's `read_at`. No-op when the action isn't in a
625
+ * notification context.
626
+ */
627
+ protected _markAsReadOnFire = false
628
+ /**
629
+ * When set, click-through opens in a new tab. Honored by the
630
+ * notification action strip renderers (bell + toast); ignored by
631
+ * everything else (Resource action triggers don't expose this knob —
632
+ * use `target` on the underlying anchor if you need it elsewhere).
633
+ */
634
+ protected _openUrlInNewTab = false
635
+ protected _href?: string
636
+ protected _method?: ActionMethod
637
+ protected _actionUrl?: string
638
+ protected _dispatchUrl?: string
639
+ protected _submit = false
640
+ protected _formTarget?: string
641
+ protected _formField?: { name: string; value: string }
642
+
643
+ // Modal chrome — present whenever `.schema()` or any of the modal
644
+ // builders below have been called.
645
+ protected _hasModal = false
646
+ protected _modalHeading?: string
647
+ protected _modalDescription?: string
648
+ protected _modalSubmitLabel?: string
649
+ protected _modalCancelLabel?: string
650
+ protected _modalIcon?: string
651
+ protected _modalIconColor?: ActionModalIconColor
652
+ protected _modalWidth?: ActionModalWidth
653
+ protected _slideOver = false
654
+ // Defaults match the existing renderer behaviour (closeable both ways,
655
+ // no sticky chrome, no X button). Setters with a default arg of `false`
656
+ // / `true` mirror Filament's call shapes — `closeModalByClickingAway()`
657
+ // disables pointer dismiss, `stickyModalHeader()` enables sticky.
658
+ protected _closeModalByClickingAway = true
659
+ protected _closeModalByEscaping = true
660
+ protected _stickyModalHeader = false
661
+ protected _stickyModalFooter = false
662
+ protected _modalAutofocus?: boolean
663
+ protected _modalAlignment?: ActionModalAlignment
664
+ protected _modalCloseButton = false
665
+
666
+ // Trigger variants & cosmetics
667
+ protected _color?: ActionColor
668
+ protected _size?: ActionSize
669
+ protected _tooltip?: string
670
+ protected _outlined = false
671
+ protected _iconOnly = false
672
+ protected _badge?: string | number
673
+ protected _badgeColor?: string
674
+
675
+ // Conditional visibility / disabled rules
676
+ protected _visible?: VisibilityRule
677
+ protected _hidden?: VisibilityRule
678
+ protected _isDisabled?: VisibilityRule
679
+
680
+ private constructor(name: string) {
681
+ super()
682
+ this.name = name
683
+ this._label = name.charAt(0).toUpperCase() + name.slice(1)
684
+ }
685
+
686
+ static make(name: string): Action {
687
+ return new Action(name)
688
+ }
689
+
690
+ // ─── Resource-aware factories ─────────────────────────
691
+ //
692
+ // Pre-configured Action shapes that target a Resource's standard CRUD
693
+ // pages. Drop into `Table.recordActions([…])`, `headerActions([…])`,
694
+ // or `ViewPage.getActions(...)` — placement is stamped by the slot.
695
+ // Filament-style: explicit, but ergonomic.
696
+ //
697
+ // Each factory uses `:id` template substitution for row context; the
698
+ // renderer fills in the row's id when rendering. Header / view actions
699
+ // ignore the template (no `:id` needed for create / list URLs).
700
+ //
701
+ // Plan #10 — each factory auto-attaches a visibility rule that
702
+ // delegates to the Resource's matching policy method (`R.canCreate`
703
+ // for `Action.create`, etc). When `R.canX` is unset (default returns
704
+ // `true`) the action stays visible. Pass an explicit `.visible(...)`
705
+ // after the factory to override.
706
+
707
+ /** Create-action factory — link to `${basePath}/${R.slug}/create`.
708
+ * Auto-hides when `R.canCreate(user)` returns false. */
709
+ static create(R: ResourceLike, basePath: string): Action {
710
+ return Action.make('create')
711
+ .label(`New ${R.labelSingular}`)
712
+ .href(`${resourceBase(basePath, R)}/create`)
713
+ .visible(({ user }) => callPredicate(R.canCreate, user))
714
+ }
715
+
716
+ /**
717
+ * Edit-action factory — link to the resource's edit page.
718
+ *
719
+ * Pass `recordId` when building actions for a single-record context
720
+ * (e.g. `ViewPage.getActions()`); the URL is baked at config time.
721
+ * Omit `recordId` for row context (`Table.recordActions(...)`); the
722
+ * URL keeps the `:id` template and the renderer substitutes per-row.
723
+ *
724
+ * Auto-hides when `R.canEdit(user, record)` returns false. For row
725
+ * context the per-row record threads in via `loadTableRecords`'s
726
+ * per-row eval; for view-page context, `resolveSchema` provides the
727
+ * resolved record on the eval context.
728
+ */
729
+ static edit(R: ResourceLike, basePath: string, recordId?: string): Action {
730
+ const id = recordId ?? ':id'
731
+ return Action.make('edit')
732
+ .label('Edit')
733
+ .href(`${resourceBase(basePath, R)}/${id}/edit`)
734
+ .visible(({ user, record }) => callPredicate(R.canEdit, user, record))
735
+ }
736
+
737
+ /** View-action factory — link to the resource's view page. See `Action.edit` for the `recordId` semantics.
738
+ * Auto-hides when `R.canView(user, record)` returns false. */
739
+ static view(R: ResourceLike, basePath: string, recordId?: string): Action {
740
+ const id = recordId ?? ':id'
741
+ return Action.make('view')
742
+ .label('View')
743
+ .href(`${resourceBase(basePath, R)}/${id}`)
744
+ .visible(({ user, record }) => callPredicate(R.canView, user, record))
745
+ }
746
+
747
+ /**
748
+ * Delete-action factory — POSTs to the resource's delete route,
749
+ * destructive style, with a confirmation prompt referencing the
750
+ * resource label. Same `recordId` semantics as `Action.edit`.
751
+ * Auto-hides when `R.canDelete(user, record)` returns false.
752
+ *
753
+ * Plan #13 — when `R.softDeletes = true`, additionally hides on
754
+ * rows whose `deletedAtColumn` is set (already-trashed rows get the
755
+ * Restore + ForceDelete pair instead, surfaced via the matching
756
+ * factories below).
757
+ */
758
+ static delete(R: ResourceLike, basePath: string, recordId?: string): Action {
759
+ const id = recordId ?? ':id'
760
+ return Action.make('delete')
761
+ .label('Delete')
762
+ .destructive()
763
+ .method('post')
764
+ .action(`${resourceBase(basePath, R)}/${id}/delete`)
765
+ .confirm(`Delete this ${R.labelSingular.toLowerCase()}?`)
766
+ .visible(async ({ user, record }) => {
767
+ if (R.softDeletes && isTrashed(record, R)) return false
768
+ return callPredicate(R.canDelete, user, record)
769
+ })
770
+ }
771
+
772
+ /**
773
+ * Replicate-action factory — handler-style. Loads the source record
774
+ * from `ctx.record` (the `_action/:actionName` route already resolves
775
+ * it through `R.query(ctx)` for row + single-target placements),
776
+ * strips PK + soft-delete column + any `opts.excludeAttributes`,
777
+ * optionally runs `opts.beforeReplicaSaved`, and creates a new row
778
+ * via `R.model.create(...)`. Redirects to the new record's edit page
779
+ * on success so the user can review + tweak before saving again.
780
+ *
781
+ * `recordId` kept in the signature for parity with `delete / edit /
782
+ * view` so users can swap factories without rewriting call sites; the
783
+ * dispatcher resolves the source record from the URL and hands it to
784
+ * the handler as `ctx.record`, so we don't reference `recordId` here.
785
+ *
786
+ * Auto-hides when `R.canCreate(user)` returns false — replicating
787
+ * writes a new row, so the gate is `canCreate`, not `canView`.
788
+ */
789
+ static replicate(
790
+ R: ResourceLike,
791
+ basePath: string,
792
+ recordId?: string,
793
+ opts: ReplicateOptions = {},
794
+ ): Action {
795
+ void recordId
796
+ return Action.make('replicate')
797
+ .label('Replicate')
798
+ .handler(async (ctx) => {
799
+ const source = ctx.record
800
+ if (!source || typeof source !== 'object') {
801
+ return { notify: { title: 'Replicate failed: source record missing', type: 'error' } as never }
802
+ }
803
+ const M = R.model
804
+ if (!M || typeof M.create !== 'function') {
805
+ return { notify: { title: 'Replicate not configured (resource has no model.create)', type: 'error' } as never }
806
+ }
807
+
808
+ const pkCol = (M as { primaryKey?: string }).primaryKey ?? 'id'
809
+ const trashedCol = R.deletedAtColumn ?? 'deletedAt'
810
+ const skip = new Set<string>([pkCol, trashedCol, ...(opts.excludeAttributes ?? [])])
811
+ let replica: Record<string, unknown> = {}
812
+ for (const [k, v] of Object.entries(source as Record<string, unknown>)) {
813
+ if (skip.has(k)) continue
814
+ replica[k] = v
815
+ }
816
+ if (opts.beforeReplicaSaved) {
817
+ try { replica = await opts.beforeReplicaSaved(replica, source) }
818
+ catch (err) {
819
+ return { notify: { title: `Replicate failed: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
820
+ }
821
+ }
822
+
823
+ let created: unknown
824
+ try {
825
+ created = await M.create(replica)
826
+ } catch (err) {
827
+ return { notify: { title: `Replicate failed: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
828
+ }
829
+
830
+ const newId = (created as Record<string, unknown> | null | undefined)?.[pkCol]
831
+ const defaultRedirect = newId !== undefined && newId !== null
832
+ ? `${resourceBase(basePath, R)}/${String(newId)}/edit`
833
+ : `${resourceBase(basePath, R)}`
834
+ // `!== undefined` rather than `??` so an override returning
835
+ // `null`/empty-string isn't silently swallowed (see
836
+ // feedback_nullish_swallows_explicit_null).
837
+ const overrideRedirect = opts.getRedirectUrl
838
+ ? await opts.getRedirectUrl({ replica: created, source })
839
+ : undefined
840
+ const redirect = overrideRedirect !== undefined ? overrideRedirect : defaultRedirect
841
+ const overrideTitle = opts.getCreatedNotificationTitle
842
+ ? await opts.getCreatedNotificationTitle({ replica: created, source })
843
+ : undefined
844
+ const title = overrideTitle !== undefined ? overrideTitle : `${R.labelSingular} replicated`
845
+ return {
846
+ redirect,
847
+ notify: { title, type: 'success' } as never,
848
+ }
849
+ })
850
+ .visible(({ user }) => callPredicate(R.canCreate, user))
851
+ }
852
+
853
+ /**
854
+ * Plan #13 — Restore factory. POSTs to the resource's restore route,
855
+ * success-styled, no confirm prompt (restoration is reversible).
856
+ * Auto-hides on live (non-trashed) rows AND when `R.canRestore(user,
857
+ * record)` returns false. Same `recordId` semantics as `Action.edit`.
858
+ */
859
+ static restore(R: ResourceLike, basePath: string, recordId?: string): Action {
860
+ const id = recordId ?? ':id'
861
+ return Action.make('restore')
862
+ .label('Restore')
863
+ .color('success')
864
+ .method('post')
865
+ .action(`${resourceBase(basePath, R)}/${id}/restore`)
866
+ .visible(async ({ user, record }) => {
867
+ if (!isTrashed(record, R)) return false
868
+ return callPredicate(R.canRestore, user, record)
869
+ })
870
+ }
871
+
872
+ /**
873
+ * Plan #13 — Force-delete factory. POSTs to the resource's
874
+ * force-delete route, destructive-styled, with a stricter confirm
875
+ * prompt referencing permanence. Auto-hides on live (non-trashed)
876
+ * rows AND when `R.canForceDelete(user, record)` returns false.
877
+ */
878
+ static forceDelete(R: ResourceLike, basePath: string, recordId?: string): Action {
879
+ const id = recordId ?? ':id'
880
+ return Action.make('forceDelete')
881
+ .label('Delete forever')
882
+ .destructive()
883
+ .method('post')
884
+ .action(`${resourceBase(basePath, R)}/${id}/force-delete`)
885
+ .confirm(`Permanently delete this ${R.labelSingular.toLowerCase()}? This cannot be undone.`)
886
+ .visible(async ({ user, record }) => {
887
+ if (!isTrashed(record, R)) return false
888
+ return callPredicate(R.canForceDelete, user, record)
889
+ })
890
+ }
891
+
892
+ // ─── Notification factories ───────────────────────────────────
893
+ //
894
+ // Pre-configured Action shapes that target the bell-table read /
895
+ // unread endpoints mounted by `Pilotiq.databaseNotifications()`. Use
896
+ // inside a custom notification inbox page's `recordActions(...)` (or
897
+ // alongside a `Notification.actions([…])` slot when one ships) to
898
+ // give end-users explicit "Mark as read" / "Mark as unread" buttons.
899
+ //
900
+ // Filament-style chain modifier `Action::make('view')->markAsRead()`
901
+ // is a separate concern — it'd add an implicit mark-read side-effect
902
+ // to a custom action. v1 ships only the explicit factory; the chain
903
+ // modifier is deferred until a consumer asks.
904
+
905
+ /**
906
+ * Mark-as-read factory — POSTs to the panel's notification read
907
+ * endpoint for the given notification id. The endpoint
908
+ * (`${base}/_notifications/:id/read`) is mounted by
909
+ * `Pilotiq.databaseNotifications()`, so calling this without
910
+ * opting into the bell surface produces an Action whose POST 404s.
911
+ *
912
+ * `notificationId` is baked at config time. For row context where
913
+ * the id varies per row, omit it and the URL keeps the `:id`
914
+ * template; the renderer substitutes per-row at render time
915
+ * (parallel to `Action.edit`'s row form).
916
+ *
917
+ * No auto-visibility. Wrap in `.visible(({ record }) => !record.readAt)`
918
+ * to hide on already-read rows.
919
+ */
920
+ static markAsRead(basePath: string, notificationId?: string): Action {
921
+ const id = notificationId ?? ':id'
922
+ return Action.make('markAsRead')
923
+ .label('Mark as read')
924
+ .method('post')
925
+ .action(`${basePath}/_notifications/${id}/read`)
926
+ }
927
+
928
+ // ─── Bulk factories (Plan #13) ────────────────────────────────
929
+ //
930
+ // Handler-style bulk actions that iterate `ctx.records`, run policy
931
+ // per-row, and call the matching Resource / Model method. No new
932
+ // routes — the existing `/_action/:actionName` dispatcher already
933
+ // handles bulk via `ctx.records`. Drop into `bulkActions([...])`
934
+ // from inside `Resource.table()`.
935
+ //
936
+ // Each returns a notification with the count succeeded; rows whose
937
+ // policy denied (or whose call threw) are silently skipped — surface
938
+ // them via your own logging if needed.
939
+
940
+ /** Bulk delete — calls `R.deleteRecord(id)` per row. On a
941
+ * soft-delete resource that hits `Model.delete()` which writes
942
+ * `deletedAt`. Notification: "N posts moved to trash" / "N posts
943
+ * deleted" depending on `R.softDeletes`. */
944
+ static bulkDelete(R: ResourceLike, _basePath: string): Action {
945
+ return Action.make('bulkDelete')
946
+ .label('Delete selected')
947
+ .destructive()
948
+ .bulk()
949
+ .confirm(`Delete the selected ${labelForCount(R, 0)}?`)
950
+ .handler(async (ctx) => {
951
+ const records = ctx.records ?? []
952
+ const Rfull = R as ResourceLike & { deleteRecord(id: string): Promise<void> }
953
+ let n = 0
954
+ for (const record of records) {
955
+ const id = String((record as { id?: unknown }).id ?? '')
956
+ if (!id) continue
957
+ const allowed = await callPredicate(R.canDelete, ctx.user, record)
958
+ if (!allowed) continue
959
+ try { await Rfull.deleteRecord(id); n++ } catch { /* skip — agg notify shows total */ }
960
+ }
961
+ const verb = R.softDeletes ? 'moved to trash' : 'deleted'
962
+ return { notify: { title: `${n} ${labelForCount(R, n)} ${verb}`, type: 'success' } as never }
963
+ })
964
+ }
965
+
966
+ /** Bulk restore — calls `R.model.restore(id)` per row. Visible only
967
+ * on soft-delete resources (the entire bulk-restore concept is
968
+ * specific to them). */
969
+ static bulkRestore(R: ResourceLike, _basePath: string): Action {
970
+ return Action.make('bulkRestore')
971
+ .label('Restore selected')
972
+ .color('success')
973
+ .bulk()
974
+ .confirm(`Restore the selected ${labelForCount(R, 0)}?`)
975
+ .handler(async (ctx) => {
976
+ const records = ctx.records ?? []
977
+ const Rfull = R as ResourceLike & { model?: { restore?(id: string | number): Promise<unknown> } }
978
+ const restore = Rfull.model?.restore
979
+ if (!restore) {
980
+ return { notify: { title: 'Restore not configured', type: 'error' } as never }
981
+ }
982
+ let n = 0
983
+ for (const record of records) {
984
+ const id = String((record as { id?: unknown }).id ?? '')
985
+ if (!id) continue
986
+ const allowed = await callPredicate(R.canRestore, ctx.user, record)
987
+ if (!allowed) continue
988
+ try { await restore(id); n++ } catch { /* skip */ }
989
+ }
990
+ return { notify: { title: `${n} ${labelForCount(R, n)} restored`, type: 'success' } as never }
991
+ })
992
+ }
993
+
994
+ /** Bulk force-delete — calls `R.model.forceDelete(id)` per row. Same
995
+ * destructive confirm as the per-row variant. Visible only on
996
+ * soft-delete resources. */
997
+ static bulkForceDelete(R: ResourceLike, _basePath: string): Action {
998
+ return Action.make('bulkForceDelete')
999
+ .label('Delete forever')
1000
+ .destructive()
1001
+ .bulk()
1002
+ .confirm(`Permanently delete the selected ${labelForCount(R, 0)}? This cannot be undone.`)
1003
+ .handler(async (ctx) => {
1004
+ const records = ctx.records ?? []
1005
+ const Rfull = R as ResourceLike & { model?: { forceDelete?(id: string | number): Promise<void> } }
1006
+ const forceDelete = Rfull.model?.forceDelete
1007
+ if (!forceDelete) {
1008
+ return { notify: { title: 'Force-delete not configured', type: 'error' } as never }
1009
+ }
1010
+ let n = 0
1011
+ for (const record of records) {
1012
+ const id = String((record as { id?: unknown }).id ?? '')
1013
+ if (!id) continue
1014
+ const allowed = await callPredicate(R.canForceDelete, ctx.user, record)
1015
+ if (!allowed) continue
1016
+ try { await forceDelete(id); n++ } catch { /* skip */ }
1017
+ }
1018
+ return { notify: { title: `${n} ${labelForCount(R, n)} permanently deleted`, type: 'success' } as never }
1019
+ })
1020
+ }
1021
+
1022
+ /**
1023
+ * Bulk replicate — calls `R.model.create(...)` once per selected row
1024
+ * with the source row's attributes minus PK / soft-delete column /
1025
+ * `opts.excludeAttributes`. Optional `opts.beforeReplicaSaved(replica,
1026
+ * source)` runs per-row. Rows that throw during create are skipped
1027
+ * silently so a single bad row doesn't abort the batch (the user sees
1028
+ * the success count on the toast). Visibility delegates to
1029
+ * `R.canCreate(user)`.
1030
+ *
1031
+ * Sibling of `Action.replicate` — same options bag, same strip set,
1032
+ * same authorization gate. Stays on the list page (no per-row
1033
+ * redirect possible for N rows).
1034
+ */
1035
+ static bulkReplicate(
1036
+ R: ResourceLike,
1037
+ _basePath: string,
1038
+ opts: ReplicateOptions = {},
1039
+ ): Action {
1040
+ return Action.make('bulkReplicate')
1041
+ .label('Replicate selected')
1042
+ .bulk()
1043
+ .confirm(`Replicate the selected ${labelForCount(R, 0)}?`)
1044
+ .handler(async (ctx) => {
1045
+ const M = R.model
1046
+ if (!M || typeof M.create !== 'function') {
1047
+ return { notify: { title: 'Replicate not configured (resource has no model.create)', type: 'error' } as never }
1048
+ }
1049
+ const records = ctx.records ?? []
1050
+ const pkCol = (M as { primaryKey?: string }).primaryKey ?? 'id'
1051
+ const trashedCol = R.deletedAtColumn ?? 'deletedAt'
1052
+ const skip = new Set<string>([pkCol, trashedCol, ...(opts.excludeAttributes ?? [])])
1053
+ let n = 0
1054
+ for (const source of records) {
1055
+ if (!source || typeof source !== 'object') continue
1056
+ const allowed = await callPredicate(R.canCreate, ctx.user)
1057
+ if (!allowed) continue
1058
+ let replica: Record<string, unknown> = {}
1059
+ for (const [k, v] of Object.entries(source as Record<string, unknown>)) {
1060
+ if (skip.has(k)) continue
1061
+ replica[k] = v
1062
+ }
1063
+ if (opts.beforeReplicaSaved) {
1064
+ try { replica = await opts.beforeReplicaSaved(replica, source) }
1065
+ catch { continue }
1066
+ }
1067
+ try { await M.create(replica); n++ } catch { /* skip — agg notify shows total */ }
1068
+ }
1069
+ const defaultTitle = `${n} ${labelForCount(R, n)} replicated`
1070
+ const overrideTitle = opts.getCreatedNotificationTitle
1071
+ ? await opts.getCreatedNotificationTitle({ count: n, records })
1072
+ : undefined
1073
+ const title = overrideTitle !== undefined ? overrideTitle : defaultTitle
1074
+ return { notify: { title, type: 'success' } as never }
1075
+ })
1076
+ .visible(({ user }) => callPredicate(R.canCreate, user))
1077
+ }
1078
+
1079
+ // ─── Import / Export factories ────────────────────────────────
1080
+ //
1081
+ // Pre-built CSV / JSON in/out for any Resource that has `R.model`.
1082
+ // `Action.export` walks the configured `R.table()` records handler
1083
+ // in pages (so it picks up the active filter/search/sort by default,
1084
+ // and stays consistent with what the user is looking at). The bulk
1085
+ // variant exports `ctx.records` instead. `Action.import` opens a
1086
+ // form-modal with a `FileUpload`, parses the uploaded file, and runs
1087
+ // create / upsert through `R.model`.
1088
+ //
1089
+ // Internals live in `./exportFactory.ts` and `./importFactory.ts`
1090
+ // — kept out of this file because they need to import `Table` /
1091
+ // field types that themselves import `Action`. Lazy-import inside
1092
+ // the handler avoids the cycle (handlers run at request-time, well
1093
+ // after module load).
1094
+
1095
+ /** Header-placement export — downloads the table as CSV (default) or
1096
+ * JSON. Visibility defaults to `R.canViewAny(user)`. Drop into
1097
+ * `headerActions([...])` from inside `Resource.table()`. See
1098
+ * `docs/plans/import-export-actions.md` for the full options bag. */
1099
+ static export(
1100
+ R: ResourceLike,
1101
+ _basePath: string,
1102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1103
+ opts: import('./exportFactory.js').ExportOptions = {},
1104
+ ): Action {
1105
+ return Action.make('export')
1106
+ .label('Export')
1107
+ .handler(async (ctx) => {
1108
+ const ef = await import('./exportFactory.js')
1109
+ const tableInst = R.table?.(await loadTableClass())
1110
+ if (!tableInst) {
1111
+ return { notify: { title: 'Export not configured (resource has no table())', type: 'error' } as never }
1112
+ }
1113
+ const cols = ef.resolveExportColumns(opts.columns, R)
1114
+ if (cols.length === 0) {
1115
+ return { notify: { title: 'Export has no columns', type: 'error' } as never }
1116
+ }
1117
+ const maxRows = opts.maxRows ?? 50_000
1118
+ const records = await ef.collectExportRows(tableInst, ctx, opts.scope ?? 'filtered', maxRows, opts.chunkSize)
1119
+ if (records.length > maxRows) {
1120
+ return { notify: { title: `Export exceeded ${maxRows} rows`, type: 'error' } as never }
1121
+ }
1122
+ const rows = records.map(r => ef.buildExportRow(r, cols))
1123
+ const format = opts.format ?? 'csv'
1124
+ const { body, contentType } = ef.encodeExport(rows, cols, format)
1125
+ const filename = typeof opts.filename === 'function'
1126
+ ? opts.filename(ctx)
1127
+ : (opts.filename ?? ef.defaultExportFilename(R.getSlug(), format))
1128
+ return { download: { filename, contentType, body } }
1129
+ })
1130
+ .visible(({ user }) => callPredicate(R.canViewAny, user))
1131
+ }
1132
+
1133
+ /** Bulk-placement export — downloads the rows the user selected via
1134
+ * the bulk-select checkboxes. Same options as `Action.export` minus
1135
+ * `scope` (always operates on `ctx.records`). Drop into
1136
+ * `bulkActions([...])`. */
1137
+ static bulkExport(
1138
+ R: ResourceLike,
1139
+ _basePath: string,
1140
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1141
+ opts: Omit<import('./exportFactory.js').ExportOptions, 'scope'> = {},
1142
+ ): Action {
1143
+ return Action.make('bulkExport')
1144
+ .label('Export selected')
1145
+ .bulk()
1146
+ .handler(async (ctx) => {
1147
+ const ef = await import('./exportFactory.js')
1148
+ const cols = ef.resolveExportColumns(opts.columns, R)
1149
+ if (cols.length === 0) {
1150
+ return { notify: { title: 'Export has no columns', type: 'error' } as never }
1151
+ }
1152
+ const records = ctx.records ?? []
1153
+ const maxRows = opts.maxRows ?? 50_000
1154
+ if (records.length > maxRows) {
1155
+ return { notify: { title: `Export exceeded ${maxRows} rows`, type: 'error' } as never }
1156
+ }
1157
+ const rows = records.map(r => ef.buildExportRow(r, cols))
1158
+ const format = opts.format ?? 'csv'
1159
+ const { body, contentType } = ef.encodeExport(rows, cols, format)
1160
+ const filename = typeof opts.filename === 'function'
1161
+ ? opts.filename(ctx)
1162
+ : (opts.filename ?? ef.defaultExportFilename(R.getSlug(), format))
1163
+ return { download: { filename, contentType, body } }
1164
+ })
1165
+ .visible(({ user }) => callPredicate(R.canViewAny, user))
1166
+ }
1167
+
1168
+ /** Header-placement import — opens a modal with a `FileUpload` (and a
1169
+ * Mode select when `upsertBy` is set), parses the uploaded file, and
1170
+ * walks each row through `R.model.create` / `R.model.update`.
1171
+ * Visibility defaults to `R.canCreate(user)`. Drop into
1172
+ * `headerActions([...])` from inside `Resource.table()`. See
1173
+ * `docs/plans/import-export-actions.md` for the full options bag. */
1174
+ static import(
1175
+ R: ResourceLike,
1176
+ _basePath: string,
1177
+ opts: import('./importFactory.js').ImportOptions = {},
1178
+ ): Action {
1179
+ const upsertable = typeof opts.upsertBy === 'string' && opts.upsertBy.length > 0
1180
+ const a = Action.make('import')
1181
+ .label('Import')
1182
+ .modalHeading(`Import ${R.label ?? `${R.labelSingular}s`}`)
1183
+ .modalSubmitLabel('Import')
1184
+ .modalCancelLabel('Cancel')
1185
+ .handler(async (ctx) => {
1186
+ const M = R.model
1187
+ if (!M || typeof M.create !== 'function') {
1188
+ return { notify: { title: 'Import not configured (resource has no model.create)', type: 'error' } as never }
1189
+ }
1190
+ if (upsertable && typeof M.update !== 'function') {
1191
+ return { notify: { title: 'Upsert import requires model.update', type: 'error' } as never }
1192
+ }
1193
+ const fileUrl = String((ctx.values?.['file'] as unknown) ?? '')
1194
+ if (fileUrl.length === 0) {
1195
+ return { notify: { title: 'No file uploaded', type: 'error' } as never }
1196
+ }
1197
+ const ifac = await import('./importFactory.js')
1198
+ let text: string
1199
+ try {
1200
+ const r = await fetch(fileUrl)
1201
+ if (!r.ok) {
1202
+ return { notify: { title: `Failed to fetch upload (${r.status})`, type: 'error' } as never }
1203
+ }
1204
+ text = await r.text()
1205
+ } catch (err) {
1206
+ return { notify: { title: `Failed to read upload: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
1207
+ }
1208
+ const format = opts.format ?? (fileUrl.toLowerCase().endsWith('.json') ? 'json' : 'csv')
1209
+ let rows: Array<Record<string, unknown>>
1210
+ try {
1211
+ rows = ifac.parseImportText(text, format, opts.columns)
1212
+ } catch (err) {
1213
+ return { notify: { title: `Failed to parse ${format.toUpperCase()}: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
1214
+ }
1215
+ const maxRows = opts.maxRows ?? 10_000
1216
+ if (rows.length > maxRows) {
1217
+ return { notify: { title: `Import too large (${rows.length} > ${maxRows})`, type: 'error' } as never }
1218
+ }
1219
+ const mode = upsertable && (ctx.values?.['mode'] === 'upsert') ? 'upsert' : 'create'
1220
+ const summary = await ifac.runImport(rows, M, mode, opts, ctx)
1221
+ return { notify: ifac.buildImportNotification(summary, { upsert: upsertable }) as never }
1222
+ })
1223
+ .visible(({ user }) => callPredicate(R.canCreate, user))
1224
+
1225
+ // Build the modal-form schema synchronously. importFactory has no
1226
+ // runtime import edge back to Action.ts (only a type-import, erased
1227
+ // at compile time), so the top-of-file static import below is
1228
+ // cycle-free.
1229
+ a.schema(buildImportModalSchema(opts))
1230
+ return a
1231
+ }
1232
+
1233
+ // ─── Relation-manager factories (Plan #11 polish) ─────────────
1234
+ //
1235
+ // Mirror `Action.create / edit / delete` but build URLs under the
1236
+ // parent record: `${base}/${parentSlug}/${parentId}/${rel}/...`.
1237
+ // Designed to be called inside `RelationManager.static table()` —
1238
+ // the page-data builder pipes `RelationManagerContext` into that
1239
+ // configurator so users get `basePath`, `parentId`, and the
1240
+ // discovered Related resource without threading them by hand.
1241
+ //
1242
+ // Visibility predicates use `safeManagerPolicy` so the manager's
1243
+ // `canX` (when overridden) wins, otherwise falls through to the
1244
+ // related Resource's `canX`. Throws absorb as `false`.
1245
+ //
1246
+ // `:id` template substitution still happens at render time for row
1247
+ // context — the same mechanism that drives `Action.edit / delete`.
1248
+ // The parent's id is baked into the URL at config time (it's known
1249
+ // upfront from `ctx.parentId`), so `:id` unambiguously refers to
1250
+ // the row's *child* id.
1251
+
1252
+ /** Relation create-action factory — link to
1253
+ * `${base}/${parentSlug}/${parentId}/${relationship}/create`.
1254
+ *
1255
+ * Visibility delegates to `M.canCreate(user, parentRecord)` (or the
1256
+ * related Resource's `canCreate(user)` when the manager hasn't
1257
+ * overridden). Drop into `headerActions([...])` from inside
1258
+ * `RelationManager.table(table, ctx)`.
1259
+ */
1260
+ static relationCreate(
1261
+ M: typeof RelationManager,
1262
+ ctx: RelationManagerContext,
1263
+ ): Action {
1264
+ const labelSingular = M.getLabelSingular()
1265
+ return Action.make('create')
1266
+ .label(`New ${labelSingular}`)
1267
+ .href(`${relationUrlPrefix(ctx)}/create`)
1268
+ .visible(({ user }) => {
1269
+ // M2M managers don't have a per-pivot-row create surface — the
1270
+ // related record is created via its own Resource, then attached
1271
+ // via `relationAttach`. Auto-hide so dropping this factory into
1272
+ // any M2M manager (belongsToMany / morphToMany / morphedByMany)
1273
+ // is a no-op (visible=false) instead of a 404-on-click foot-gun.
1274
+ if (isM2MMode(ctx.mode)) return false
1275
+ return safeManagerPolicy(M, 'canCreate', ctx.related, user, ctx.parentRecord)
1276
+ })
1277
+ }
1278
+
1279
+ /** Relation edit-action factory — link to
1280
+ * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/edit`.
1281
+ *
1282
+ * Same `recordId` semantics as `Action.edit`: omit for row context
1283
+ * so the renderer substitutes `:id` per row; pass explicitly when
1284
+ * building actions for a single-record context. Visibility delegates
1285
+ * to `M.canEdit(user, child, parentRecord)` with fall-through to the
1286
+ * related Resource's `canEdit(user, record)`.
1287
+ */
1288
+ static relationEdit(
1289
+ M: typeof RelationManager,
1290
+ ctx: RelationManagerContext,
1291
+ recordId?: string,
1292
+ ): Action {
1293
+ const id = recordId ?? ':id'
1294
+ return Action.make('edit')
1295
+ .label('Edit')
1296
+ .href(`${relationUrlPrefix(ctx)}/${id}/edit`)
1297
+ .visible(({ user, record }) => {
1298
+ // M2M: per-pivot-row "edit" doesn't exist; users edit the
1299
+ // related record via its own Resource. Auto-hide for every M2M
1300
+ // mode (belongsToMany / morphToMany / morphedByMany).
1301
+ if (isM2MMode(ctx.mode)) return false
1302
+ return safeManagerPolicy(M, 'canEdit', ctx.related, user, ctx.parentRecord, record)
1303
+ })
1304
+ }
1305
+
1306
+ /** Relation delete-action factory — POST to
1307
+ * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/delete`,
1308
+ * destructive style with a labeled confirmation. Visibility delegates
1309
+ * to `M.canDelete(user, child, parentRecord)` with fall-through to the
1310
+ * related Resource's `canDelete(user, record)`.
1311
+ */
1312
+ static relationDelete(
1313
+ M: typeof RelationManager,
1314
+ ctx: RelationManagerContext,
1315
+ recordId?: string,
1316
+ ): Action {
1317
+ const id = recordId ?? ':id'
1318
+ const singular = M.getLabelSingular().toLowerCase()
1319
+ return Action.make('delete')
1320
+ .label('Delete')
1321
+ .destructive()
1322
+ .method('post')
1323
+ .action(`${relationUrlPrefix(ctx)}/${id}/delete`)
1324
+ .confirm(`Delete this ${singular}?`)
1325
+ .visible(async ({ user, record }) => {
1326
+ // M2M: "delete" of the related record is destructive in a way
1327
+ // that "detach" isn't — surface only `relationDetach` on every
1328
+ // M2M manager (belongsToMany / morphToMany / morphedByMany).
1329
+ // Users who genuinely want to delete the related record reach
1330
+ // for `Action.delete(R)` on the related Resource instead.
1331
+ if (isM2MMode(ctx.mode)) return false
1332
+ if (ctx.related?.softDeletes && isTrashed(record, ctx.related as ResourceLike)) return false
1333
+ return safeManagerPolicy(M, 'canDelete', ctx.related, user, ctx.parentRecord, record)
1334
+ })
1335
+ }
1336
+
1337
+ /**
1338
+ * Plan #13 polish — Restore factory for relation managers. POSTs to
1339
+ * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/restore`,
1340
+ * success-styled, no confirm prompt. Auto-hides on live (non-trashed)
1341
+ * rows AND when `M.canRestore` (or related Resource fall-through)
1342
+ * denies. Drop into `recordActions([...])` from `RelationManager.table(table, ctx)`.
1343
+ */
1344
+ static relationRestore(
1345
+ M: typeof RelationManager,
1346
+ ctx: RelationManagerContext,
1347
+ recordId?: string,
1348
+ ): Action {
1349
+ const id = recordId ?? ':id'
1350
+ return Action.make('restore')
1351
+ .label('Restore')
1352
+ .color('success')
1353
+ .method('post')
1354
+ .action(`${relationUrlPrefix(ctx)}/${id}/restore`)
1355
+ .visible(async ({ user, record }) => {
1356
+ if (!ctx.related?.softDeletes) return false
1357
+ if (!isTrashed(record, ctx.related as ResourceLike)) return false
1358
+ return safeManagerPolicy(M, 'canRestore', ctx.related, user, ctx.parentRecord, record)
1359
+ })
1360
+ }
1361
+
1362
+ /**
1363
+ * Plan #13 polish — Force-delete factory for relation managers. POSTs
1364
+ * to `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/force-delete`,
1365
+ * destructive style with a permanence-aware confirmation. Auto-hides on
1366
+ * live (non-trashed) rows and when policy denies.
1367
+ */
1368
+ static relationForceDelete(
1369
+ M: typeof RelationManager,
1370
+ ctx: RelationManagerContext,
1371
+ recordId?: string,
1372
+ ): Action {
1373
+ const id = recordId ?? ':id'
1374
+ const singular = M.getLabelSingular().toLowerCase()
1375
+ return Action.make('forceDelete')
1376
+ .label('Delete forever')
1377
+ .destructive()
1378
+ .method('post')
1379
+ .action(`${relationUrlPrefix(ctx)}/${id}/force-delete`)
1380
+ .confirm(`Permanently delete this ${singular}? This cannot be undone.`)
1381
+ .visible(async ({ user, record }) => {
1382
+ if (!ctx.related?.softDeletes) return false
1383
+ if (!isTrashed(record, ctx.related as ResourceLike)) return false
1384
+ return safeManagerPolicy(M, 'canForceDelete', ctx.related, user, ctx.parentRecord, record)
1385
+ })
1386
+ }
1387
+
1388
+ // ─── Relation-manager replicate factories ─────────────────
1389
+ //
1390
+ // Sibling of `Action.replicate / bulkReplicate` scoped to a
1391
+ // RelationManager. Operates on the **related** Resource's model and
1392
+ // **forces** the parent attachment back onto the replica:
1393
+ //
1394
+ // - `hasMany` — re-stamps `<foreignKey> = ctx.parentId` from the
1395
+ // parent's `static relations[name]` descriptor. Defends against a
1396
+ // tampered POST body (or a source row loaded from a different
1397
+ // parent's children) silently re-attaching the new row to a
1398
+ // different parent.
1399
+ // - `morphMany` — re-stamps `<morphName>Id` + `<morphName>Type` via
1400
+ // `computeMorphPayload(parentRecord, descriptor)`, same auto-fill
1401
+ // the relation-create POST handler uses on the create form.
1402
+ // - M2M (`belongsToMany / morphToMany / morphedByMany`) — auto-hides.
1403
+ // Replicate doesn't fit pivot semantics; users create the related
1404
+ // record via its own Resource, then attach via `relationAttach`.
1405
+ // - `morphTo` — auto-hides. Child-side polymorphic relations don't
1406
+ // have a single owner to pin to (the row's existing morph cols
1407
+ // already point somewhere; cloning is the user's job, not ours).
1408
+ //
1409
+ // Both factories dispatch through the manager-scoped
1410
+ // `_action/:actionName` route already wired in `routes.ts`. The route
1411
+ // resolves `ctx.record` (and `ctx.records` for bulk) via
1412
+ // `Related.model.find(id)`, and stamps `ctx.relation = { parent,
1413
+ // parentId, relationship }` so the handlers can read the live parent
1414
+ // without re-loading.
1415
+ //
1416
+ // Visibility delegates to `safeManagerPolicy(M, 'canCreate', Related,
1417
+ // user, parentRecord)` — the manager's `canCreate` (when overridden)
1418
+ // wins, otherwise falls through to the related Resource's `canCreate`.
1419
+
1420
+ /**
1421
+ * Relation row-replicate factory. Clones the row's child record
1422
+ * inside the manager's parent scope.
1423
+ *
1424
+ * Strips the related model's primary key, soft-delete column, and
1425
+ * `opts.excludeAttributes`. Re-applies the parent attachment columns
1426
+ * after the strip + before the optional `beforeReplicaSaved` hook,
1427
+ * so user code can still mutate non-FK fields without accidentally
1428
+ * unlinking the replica.
1429
+ *
1430
+ * On success the manager-scoped route falls back to the manager
1431
+ * list URL (`${base}/${parentSlug}/${parentId}/${relationship}`)
1432
+ * because no explicit `redirect` is returned — same default as the
1433
+ * other handler-style relation factories.
1434
+ *
1435
+ * `recordId` kept in the signature for parity with the rest of the
1436
+ * relation factory family. The dispatcher resolves the source row
1437
+ * from the request body, so it isn't referenced here.
1438
+ */
1439
+ static relationReplicate(
1440
+ M: typeof RelationManager,
1441
+ ctx: RelationManagerContext,
1442
+ recordId?: string,
1443
+ opts: ReplicateOptions = {},
1444
+ ): Action {
1445
+ void recordId
1446
+ return Action.make('relationReplicate')
1447
+ .label('Replicate')
1448
+ .row()
1449
+ .handler(async (hctx) => {
1450
+ const result = await runRelationReplicateRow(M, ctx, hctx, opts)
1451
+ return result
1452
+ })
1453
+ .visible(({ user }) => {
1454
+ if (isM2MMode(ctx.mode) || ctx.mode === 'morphTo') return false
1455
+ return safeManagerPolicy(M, 'canCreate', ctx.related, user, ctx.parentRecord)
1456
+ })
1457
+ }
1458
+
1459
+ /**
1460
+ * Bulk sibling — replicates every selected child row inside the
1461
+ * manager's parent scope. Same strip + force-pin pipeline applied
1462
+ * per row. Per-row `safeManagerPolicy(M, 'canCreate', …)` runs
1463
+ * inside the loop so a partially-permitted selection still proceeds
1464
+ * for the rows that pass. Rows that throw are skipped silently —
1465
+ * the toast count reflects only successful creates.
1466
+ */
1467
+ static relationBulkReplicate(
1468
+ M: typeof RelationManager,
1469
+ ctx: RelationManagerContext,
1470
+ opts: ReplicateOptions = {},
1471
+ ): Action {
1472
+ return Action.make('relationBulkReplicate')
1473
+ .label('Replicate selected')
1474
+ .bulk()
1475
+ .confirm(`Replicate the selected ${M.getLabel().toLowerCase()}?`)
1476
+ .handler(async (hctx) => {
1477
+ const Related = ctx.related
1478
+ if (!Related?.model || typeof Related.model.create !== 'function') {
1479
+ return { notify: { title: 'Replicate not configured (related Resource has no model.create)', type: 'error' } as never }
1480
+ }
1481
+ const records = hctx.records ?? []
1482
+ let n = 0
1483
+ for (const source of records) {
1484
+ if (!source || typeof source !== 'object') continue
1485
+ const allowed = await safeManagerPolicy(M, 'canCreate', Related, hctx.user, ctx.parentRecord)
1486
+ if (!allowed) continue
1487
+ try {
1488
+ await persistRelationReplica(M, ctx, source, opts)
1489
+ n++
1490
+ } catch { /* skip — agg notify shows total */ }
1491
+ }
1492
+ const labelPlural = M.getLabel().toLowerCase()
1493
+ const labelSingular = M.getLabelSingular().toLowerCase()
1494
+ const defaultTitle = `${n} ${n === 1 ? labelSingular : labelPlural} replicated`
1495
+ const overrideTitle = opts.getCreatedNotificationTitle
1496
+ ? await opts.getCreatedNotificationTitle({ count: n, records })
1497
+ : undefined
1498
+ const title = overrideTitle !== undefined ? overrideTitle : defaultTitle
1499
+ return {
1500
+ notify: { title, type: 'success' } as never,
1501
+ }
1502
+ })
1503
+ .visible(({ user }) => {
1504
+ if (isM2MMode(ctx.mode) || ctx.mode === 'morphTo') return false
1505
+ return safeManagerPolicy(M, 'canCreate', ctx.related, user, ctx.parentRecord)
1506
+ })
1507
+ }
1508
+
1509
+ // ─── M2M relation factories ───────────────────────────────
1510
+ //
1511
+ // Sibling of `relationCreate / Edit / Delete` for every M2M mode
1512
+ // (`belongsToMany`, `morphToMany` (owning polymorphic side),
1513
+ // `morphedByMany` (inverse polymorphic side)). All three modes share
1514
+ // the same `attach` / `detach` / `sync` accessor surface — the rudder
1515
+ // ORM stamps + filters the polymorphic discriminator on the morph
1516
+ // variants automatically, so pilotiq's pivot factories are mode-agnostic
1517
+ // beyond the visibility gate.
1518
+ //
1519
+ // Three factories: `relationAttach` (header, modal-form picker →
1520
+ // POST `_action/relationAttach`), `relationDetach` (row, direct POST
1521
+ // to `_detach/:childId`), `relationBulkDetach` (bulk, handler-
1522
+ // dispatched). The first and third route through the manager-scoped
1523
+ // `_action/:actionName` endpoint (added in routes.ts) so handlers
1524
+ // see `ctx.relation = { parent, parentId, relationship }`.
1525
+ //
1526
+ // All three auto-hide outside any M2M mode so dropping a factory into
1527
+ // a non-M2M manager is a no-op (visible=false) instead of a confusing
1528
+ // 404.
1529
+
1530
+ /** Header-placement attach factory — opens a modal with a SelectField
1531
+ * listing related records that aren't already attached, and POSTs the
1532
+ * selected id to the manager's `_action/relationAttach` endpoint.
1533
+ *
1534
+ * Visibility delegates to `M.canAttach(user, parentRecord)` AND
1535
+ * guards against being dropped into a non-M2M manager. */
1536
+ static relationAttach(
1537
+ M: typeof RelationManager,
1538
+ ctx: RelationManagerContext,
1539
+ ): Action {
1540
+ const labelSingular = M.getLabelSingular()
1541
+ const a = Action.make('relationAttach')
1542
+ .label(`Attach ${labelSingular}`)
1543
+ .header()
1544
+ .modalHeading(`Attach ${labelSingular}`)
1545
+ .modalSubmitLabel('Attach')
1546
+ .modalCancelLabel('Cancel')
1547
+ .handler(async (hctx) => {
1548
+ const rel = hctx.relation
1549
+ if (!rel) {
1550
+ return { notify: { title: 'Attach handler missing parent context — manager-scoped _action route not wired', type: 'error' } as never }
1551
+ }
1552
+ const Related = ctx.related
1553
+ if (!Related?.model) {
1554
+ return { notify: { title: 'Cannot attach: related Resource has no model', type: 'error' } as never }
1555
+ }
1556
+ const idStr = String((hctx.values?.['_attachId'] as unknown) ?? '')
1557
+ if (idStr.length === 0) {
1558
+ return { notify: { title: 'Pick a record to attach', type: 'error' } as never }
1559
+ }
1560
+ const accessor = resolveM2MAccessor(rel.parent, rel.relationship)
1561
+ if (!accessor || typeof accessor.attach !== 'function') {
1562
+ return { notify: { title: `Pivot accessor missing on ${rel.relationship} — wrong relation type or ORM version?`, type: 'error' } as never }
1563
+ }
1564
+ try {
1565
+ await accessor.attach([idStr])
1566
+ } catch (err) {
1567
+ return { notify: { title: `Attach failed: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
1568
+ }
1569
+ return { notify: { title: `${labelSingular} attached`, type: 'success' } as never }
1570
+ })
1571
+ .visible(({ user }) => {
1572
+ if (!isM2MMode(ctx.mode)) return false
1573
+ return safeManagerPolicy(M, 'canAttach', ctx.related, user, ctx.parentRecord)
1574
+ })
1575
+
1576
+ // Build the modal-form schema only when this is actually an M2M
1577
+ // manager — non-M2M drops keep the action hidden via the visibility
1578
+ // predicate, but still need a schema-less Action so the meta walker
1579
+ // doesn't blow up. Static import is fine: `attachFactory` only
1580
+ // depends on `SelectField` + ORM helpers, no cycle back to Action.
1581
+ if (isM2MMode(ctx.mode) && ctx.related?.model) {
1582
+ a.schema(buildAttachModalSchema({
1583
+ Related: ctx.related,
1584
+ relationship: ctx.relationship,
1585
+ recordTitleAttr: M.getRecordTitleAttribute() ?? ctx.related.recordTitleAttribute,
1586
+ labelSingular,
1587
+ }))
1588
+ }
1589
+ return a
1590
+ }
1591
+
1592
+ /** Row-placement detach factory — POSTs to
1593
+ * `${base}/${parentSlug}/${parentId}/${relationship}/${recordId ?? ':id'}/_detach`,
1594
+ * destructive style with a confirmation prompt that says "Detach"
1595
+ * (not "Delete") so users understand the target record stays.
1596
+ * Visibility delegates to `M.canDetach`. */
1597
+ static relationDetach(
1598
+ M: typeof RelationManager,
1599
+ ctx: RelationManagerContext,
1600
+ recordId?: string,
1601
+ ): Action {
1602
+ const id = recordId ?? ':id'
1603
+ const singular = M.getLabelSingular().toLowerCase()
1604
+ return Action.make('relationDetach')
1605
+ .label('Detach')
1606
+ .destructive()
1607
+ .method('post')
1608
+ .action(`${relationUrlPrefix(ctx)}/${id}/_detach`)
1609
+ .confirm(`Detach this ${singular}? The ${singular} record stays in place; only the link is removed.`)
1610
+ .visible(async ({ user, record }) => {
1611
+ if (!isM2MMode(ctx.mode)) return false
1612
+ return safeManagerPolicy(M, 'canDetach', ctx.related, user, ctx.parentRecord, record)
1613
+ })
1614
+ }
1615
+
1616
+ /** Bulk-placement bulk-detach factory — handler-dispatched. Calls
1617
+ * `parent.related(rel).detach(ids)` for the selected rows. Visibility
1618
+ * delegates to `M.canAttach` (acts like a "manager admin" gate; we
1619
+ * intentionally don't enforce per-row `canDetach` on the visibility
1620
+ * side because the bulk button needs to be visible before the user
1621
+ * has selected anything — per-row gating happens inside the handler). */
1622
+ static relationBulkDetach(
1623
+ M: typeof RelationManager,
1624
+ ctx: RelationManagerContext,
1625
+ ): Action {
1626
+ const labelPlural = M.getLabel().toLowerCase()
1627
+ return Action.make('relationBulkDetach')
1628
+ .label('Detach selected')
1629
+ .destructive()
1630
+ .bulk()
1631
+ .confirm(`Detach the selected ${labelPlural}? The records stay in place; only the links are removed.`)
1632
+ .handler(async (hctx) => {
1633
+ const rel = hctx.relation
1634
+ if (!rel) {
1635
+ return { notify: { title: 'Bulk-detach handler missing parent context — manager-scoped _action route not wired', type: 'error' } as never }
1636
+ }
1637
+ const records = hctx.records ?? []
1638
+ const ids: string[] = []
1639
+ for (const r of records) {
1640
+ const id = String((r as { id?: unknown }).id ?? '')
1641
+ if (!id) continue
1642
+ const allowed = await safeManagerPolicy(M, 'canDetach', ctx.related, hctx.user, ctx.parentRecord, r)
1643
+ if (!allowed) continue
1644
+ ids.push(id)
1645
+ }
1646
+ if (ids.length === 0) {
1647
+ return { notify: { title: 'Nothing to detach (no permitted rows)', type: 'warning' } as never }
1648
+ }
1649
+ const accessor = resolveM2MAccessor(rel.parent, rel.relationship)
1650
+ if (!accessor || typeof accessor.detach !== 'function') {
1651
+ return { notify: { title: `Pivot accessor missing on ${rel.relationship} — wrong relation type or ORM version?`, type: 'error' } as never }
1652
+ }
1653
+ try {
1654
+ await accessor.detach(ids)
1655
+ } catch (err) {
1656
+ return { notify: { title: `Bulk detach failed: ${err instanceof Error ? err.message : String(err)}`, type: 'error' } as never }
1657
+ }
1658
+ return { notify: { title: `${ids.length} ${labelPlural} detached`, type: 'success' } as never }
1659
+ })
1660
+ .visible(({ user }) => {
1661
+ if (!isM2MMode(ctx.mode)) return false
1662
+ // Bulk gate uses canAttach as a stand-in for "manager admin" —
1663
+ // per-row canDetach is enforced inside the handler.
1664
+ return safeManagerPolicy(M, 'canAttach', ctx.related, user, ctx.parentRecord)
1665
+ })
1666
+ }
1667
+
1668
+ label(l: string): this { this._label = l; return this }
1669
+ icon(i: string): this { this._icon = i; return this }
1670
+
1671
+ // ─── Placement ────────────────────────────────────────
1672
+
1673
+ placement(p: ActionPlacement): this { this._placement = p; return this }
1674
+ inline(): this { return this.placement('inline') }
1675
+ row(): this { return this.placement('row') }
1676
+ bulk(): this { return this.placement('bulk') }
1677
+ header(): this { return this.placement('header') }
1678
+
1679
+ // ─── Behavior ─────────────────────────────────────────
1680
+
1681
+ destructive(v = true): this {
1682
+ this._destructive = v
1683
+ if (v && this._color === undefined) this._color = 'destructive'
1684
+ return this
1685
+ }
1686
+
1687
+ /** Set the visual color. `destructive` is also set by `.destructive()`. */
1688
+ color(c: ActionColor): this { this._color = c; return this }
1689
+
1690
+ /** Set the size preset (sm | md | lg). Default is `md`. */
1691
+ size(s: ActionSize): this { this._size = s; return this }
1692
+
1693
+ /** Hover tooltip. Wraps the button in a Tooltip primitive. */
1694
+ tooltip(t: string): this { this._tooltip = t; return this }
1695
+
1696
+ /** Outlined trigger style — border + transparent bg instead of solid color. */
1697
+ outlined(v = true): this { this._outlined = v; return this }
1698
+
1699
+ /** Icon-only trigger style. Renders a square button with just the icon;
1700
+ * the label is used as `aria-label`. Requires `.icon()` to be set. */
1701
+ iconButton(v = true): this { this._iconOnly = v; return this }
1702
+
1703
+ /** Show a small badge on the trigger (e.g. unread count). */
1704
+ badge(value: string | number): this { this._badge = value; return this }
1705
+
1706
+ /** Optional color class for the badge (e.g. 'bg-emerald-500'). */
1707
+ badgeColor(c: string): this { this._badgeColor = c; return this }
1708
+
1709
+ // ─── Conditional visibility / disabled ───────────────
1710
+
1711
+ /** Show the action only when `rule` is truthy. Pair with a function for
1712
+ * record-aware visibility (e.g. `({ record }) => !record.archived`).
1713
+ * Row-placement actions are evaluated per-row at table-load time;
1714
+ * other placements are evaluated at schema-resolve time with the
1715
+ * page-level context. */
1716
+ visible(rule: VisibilityRule): this { this._visible = rule; return this }
1717
+
1718
+ /** Inverse of `visible` — hide the action when `rule` is truthy.
1719
+ * Both rules combine via AND: visible if `visible !== false` AND
1720
+ * `hidden !== true`. */
1721
+ hidden(rule: VisibilityRule): this { this._hidden = rule; return this }
1722
+
1723
+ /** Disable (render greyed-out and skip dispatch) when `rule` is truthy.
1724
+ * Disabled actions still appear in the UI, unlike hidden ones. */
1725
+ disabled(rule: VisibilityRule): this { this._isDisabled = rule; return this }
1726
+
1727
+ /** Policy-style alias for `.visible(fn)` — semantically identical
1728
+ * but reads better when guarding by user permissions. */
1729
+ authorize(rule: VisibilityRule): this { return this.visible(rule) }
1730
+
1731
+ /** Evaluate the visibility / disabled rules with the given context.
1732
+ * Defaults: visible = true, disabled = false. Both `visible` and
1733
+ * `hidden` are folded in: `visible: visible !== false && hidden !== true`.
1734
+ *
1735
+ * Async to support Plan #10 — visibility rules can return Promise<bool>
1736
+ * (for `Resource.canX(user, record)` integration). Throwing rules are
1737
+ * treated as fail-closed (`visible: false` / `disabled: true`). */
1738
+ async evaluate(ctx: ActionVisibilityContext = {}): Promise<{ visible: boolean; disabled: boolean }> {
1739
+ const evalRule = async (rule: VisibilityRule | undefined, fallback: boolean): Promise<boolean> => {
1740
+ if (rule === undefined) return fallback
1741
+ if (typeof rule !== 'function') return rule
1742
+ try {
1743
+ return await rule(ctx)
1744
+ } catch {
1745
+ // Fail closed — a throwing rule shouldn't accidentally show a
1746
+ // destructive action.
1747
+ return !fallback
1748
+ }
1749
+ }
1750
+ const [visibleRaw, hiddenRaw, disabledRaw] = await Promise.all([
1751
+ evalRule(this._visible, true),
1752
+ evalRule(this._hidden, false),
1753
+ evalRule(this._isDisabled, false),
1754
+ ])
1755
+ return {
1756
+ visible: visibleRaw && !hiddenRaw,
1757
+ disabled: disabledRaw,
1758
+ }
1759
+ }
1760
+
1761
+ /** True when any visibility / hidden / disabled rule is set. Useful for
1762
+ * the resolver to know whether per-row evaluation is needed for a
1763
+ * row-placement action. */
1764
+ hasVisibilityRules(): boolean {
1765
+ return this._visible !== undefined || this._hidden !== undefined || this._isDisabled !== undefined
1766
+ }
1767
+
1768
+ /**
1769
+ * Prompt the user before running the handler. Pass a string for a simple
1770
+ * "are you sure?" message, or an object for full control.
1771
+ */
1772
+ confirm(prompt: string | ActionConfirm): this {
1773
+ this._confirm = typeof prompt === 'string' ? { message: prompt } : prompt
1774
+ return this
1775
+ }
1776
+
1777
+ /**
1778
+ * Server-side handler. Two shapes:
1779
+ *
1780
+ * - **Closure** — `handler(async ctx => …)`. Dispatched against the
1781
+ * current page's `_action/:name` endpoint. Closures don't survive
1782
+ * serialization, so they can't ride a persisted notification —
1783
+ * `Notification.toDatabase()` rejects them with a clear error.
1784
+ *
1785
+ * - **Registry key** — `handler('archive-project')`. Looked up against
1786
+ * `Pilotiq.notificationHandlers({…})` at request time. Round-trips
1787
+ * through a `Notification`'s `data.actions` JSON column, so this is
1788
+ * the path to take when an action lives on a row that may be
1789
+ * clicked days later.
1790
+ *
1791
+ * Mutually exclusive — setting one clears the other.
1792
+ */
1793
+ handler(fnOrName: ActionHandler | string): this {
1794
+ if (typeof fnOrName === 'string') {
1795
+ this._handlerName = fnOrName
1796
+ delete this._handler
1797
+ } else {
1798
+ this._handler = fnOrName
1799
+ delete this._handlerName
1800
+ }
1801
+ return this
1802
+ }
1803
+
1804
+ /**
1805
+ * Per-fire context for registry-handler dispatch. JSON-serializable
1806
+ * keys only — values flow through the notification's `data.actions`
1807
+ * column and arrive on the handler's `ctx.payload`.
1808
+ *
1809
+ * Action.make('archive')
1810
+ * .handler('archive-project')
1811
+ * .payload({ projectId: 123 })
1812
+ */
1813
+ payload(data: Record<string, unknown>): this {
1814
+ this._payload = data
1815
+ return this
1816
+ }
1817
+
1818
+ /**
1819
+ * Filament-style chain modifier — when this action lives inside a
1820
+ * `Notification.actions([…])` slot, firing it also flips the
1821
+ * notification's `read_at`. No-op for actions used outside the
1822
+ * notification context (Resource actions, etc).
1823
+ *
1824
+ * Action.make('view').url('/projects/123').markAsRead()
1825
+ *
1826
+ * On url/post actions the bell client fires the read POST as a
1827
+ * client-side side-effect *before* navigating/submitting; on
1828
+ * registry-handler actions the route flips `read_at` server-side
1829
+ * after the handler runs (one round-trip, not two).
1830
+ */
1831
+ markAsRead(v = true): this {
1832
+ this._markAsReadOnFire = v
1833
+ return this
1834
+ }
1835
+
1836
+ /**
1837
+ * Open the action's url in a new tab. Honored by the notification
1838
+ * action-strip renderers; equivalent to `target="_blank"` on the
1839
+ * underlying anchor.
1840
+ */
1841
+ openUrlInNewTab(v = true): this {
1842
+ this._openUrlInNewTab = v
1843
+ return this
1844
+ }
1845
+
1846
+ // ─── Modal / form-modal action ────────────────────────
1847
+
1848
+ /**
1849
+ * Attach a form schema that opens in a modal Dialog when the action is
1850
+ * triggered. The submitted values flow through validation + coercion
1851
+ * server-side and arrive on the handler's `ctx.values`. Triggers modal
1852
+ * chrome (heading / submit button / cancel button) if not configured
1853
+ * via the other modal builders below.
1854
+ */
1855
+ schema(elements: Element[]): this {
1856
+ this._children = elements
1857
+ this._hasModal = true
1858
+ return this
1859
+ }
1860
+
1861
+ modalHeading(s: string): this { this._modalHeading = s; this._hasModal = true; return this }
1862
+ modalDescription(s: string): this { this._modalDescription = s; this._hasModal = true; return this }
1863
+ modalSubmitLabel(s: string): this { this._modalSubmitLabel = s; this._hasModal = true; return this }
1864
+ modalCancelLabel(s: string): this { this._modalCancelLabel = s; this._hasModal = true; return this }
1865
+ /** Filament v5 alias for `modalSubmitLabel`. Reads more naturally
1866
+ * alongside `modalCancelActionLabel` when both are set. */
1867
+ modalSubmitActionLabel(s: string): this { return this.modalSubmitLabel(s) }
1868
+ /** Filament v5 alias for `modalCancelLabel`. */
1869
+ modalCancelActionLabel(s: string): this { return this.modalCancelLabel(s) }
1870
+ modalIcon(i: string): this { this._modalIcon = i; this._hasModal = true; return this }
1871
+ modalIconColor(c: ActionModalIconColor): this {
1872
+ this._modalIconColor = c
1873
+ this._hasModal = true
1874
+ return this
1875
+ }
1876
+ modalWidth(w: ActionModalWidth): this { this._modalWidth = w; this._hasModal = true; return this }
1877
+ modalAlignment(a: ActionModalAlignment): this {
1878
+ this._modalAlignment = a
1879
+ this._hasModal = true
1880
+ return this
1881
+ }
1882
+ slideOver(v = true): this { this._slideOver = v; this._hasModal = true; return this }
1883
+
1884
+ /**
1885
+ * Disable / re-enable closing the modal by clicking outside the popup.
1886
+ * Default: enabled. Calling without an arg disables (matches Filament's
1887
+ * `->closeModalByClickingAway(false)` shape — most uses are to gate
1888
+ * accidental dismissal of important modals).
1889
+ */
1890
+ closeModalByClickingAway(v: boolean = false): this {
1891
+ this._closeModalByClickingAway = v
1892
+ this._hasModal = true
1893
+ return this
1894
+ }
1895
+
1896
+ /**
1897
+ * Disable / re-enable closing the modal with the Escape key. Default:
1898
+ * enabled. Same shape as `closeModalByClickingAway`. Useful when a
1899
+ * partially-completed multi-step modal would lose work on a stray Esc.
1900
+ */
1901
+ closeModalByEscaping(v: boolean = false): this {
1902
+ this._closeModalByEscaping = v
1903
+ this._hasModal = true
1904
+ return this
1905
+ }
1906
+
1907
+ /** Sticky header inside the modal body. Useful when the body scrolls
1908
+ * past the heading on long forms. Default: off. */
1909
+ stickyModalHeader(v: boolean = true): this {
1910
+ this._stickyModalHeader = v
1911
+ this._hasModal = true
1912
+ return this
1913
+ }
1914
+
1915
+ /** Sticky footer inside the modal body. Pairs naturally with
1916
+ * `stickyModalHeader` on long forms — keeps the Submit / Cancel
1917
+ * buttons visible while the user scrolls. Default: off. */
1918
+ stickyModalFooter(v: boolean = true): this {
1919
+ this._stickyModalFooter = v
1920
+ this._hasModal = true
1921
+ return this
1922
+ }
1923
+
1924
+ /**
1925
+ * Override the default autofocus behaviour. `true` focuses the first
1926
+ * form input on mount (or the submit button when there is no form);
1927
+ * `false` disables autofocus entirely. Omit to keep the legacy default
1928
+ * (submit button autofocuses for confirm-only modals).
1929
+ */
1930
+ modalAutofocus(v: boolean = true): this {
1931
+ this._modalAutofocus = v
1932
+ this._hasModal = true
1933
+ return this
1934
+ }
1935
+
1936
+ /** Render an X close button in the top-right corner of the popup.
1937
+ * Default: off (the Cancel button in the footer is the documented
1938
+ * affordance). Useful for slide-over panels where the footer is
1939
+ * far from the top of the viewport. */
1940
+ modalCloseButton(v: boolean = true): this {
1941
+ this._modalCloseButton = v
1942
+ this._hasModal = true
1943
+ return this
1944
+ }
1945
+
1946
+ // ─── Link / form modes ────────────────────────────────
1947
+
1948
+ /**
1949
+ * Render this action as a link to `url`. Mutually exclusive with
1950
+ * `.method()` — setting `href` clears any prior method/action URL.
1951
+ */
1952
+ href(url: string): this {
1953
+ this._href = url
1954
+ delete this._method
1955
+ delete this._actionUrl
1956
+ return this
1957
+ }
1958
+
1959
+ /**
1960
+ * Sugar alias for `.href(url)` — matches Filament's `->url(...)` API
1961
+ * and reads more naturally inside `Notification.actions([…])`.
1962
+ */
1963
+ url(href: string): this { return this.href(href) }
1964
+
1965
+ /**
1966
+ * Render this action as a form-style submit button using `method`. Pair
1967
+ * with `.action(url)` to set the form's action URL — falls back to the
1968
+ * current page URL otherwise.
1969
+ */
1970
+ method(m: ActionMethod): this {
1971
+ this._method = m
1972
+ delete this._href
1973
+ return this
1974
+ }
1975
+
1976
+ /** Form action URL — only meaningful when `.method()` is set. */
1977
+ action(url: string): this {
1978
+ this._actionUrl = url
1979
+ delete this._href
1980
+ return this
1981
+ }
1982
+
1983
+ /**
1984
+ * Render-time URL the client should POST to when invoking this
1985
+ * action's handler. Set by the route registrar — users don't normally
1986
+ * call this directly. Format: `${pageUrl}/_action/${action.name}`.
1987
+ */
1988
+ dispatchUrl(url: string): this {
1989
+ this._dispatchUrl = url
1990
+ return this
1991
+ }
1992
+
1993
+ /**
1994
+ * Mark this action as the form-submit button for its enclosing
1995
+ * `<form>`. Renders as `<button type="submit">` and relies on the form
1996
+ * itself to carry `action` + `method`. Mutually exclusive with
1997
+ * `.href()` / `.method()` / handler-style.
1998
+ */
1999
+ submit(): this {
2000
+ this._submit = true
2001
+ delete this._href
2002
+ delete this._method
2003
+ delete this._actionUrl
2004
+ delete this._dispatchUrl
2005
+ return this
2006
+ }
2007
+
2008
+ /**
2009
+ * Target a specific `<form id="">` when this is a submit action — uses
2010
+ * the HTML `form` attribute so the button can submit a form it doesn't
2011
+ * live inside. Required when the submit action sits in the page
2012
+ * header (outside the form's DOM subtree).
2013
+ */
2014
+ form(formId: string): this {
2015
+ this._formTarget = formId
2016
+ return this
2017
+ }
2018
+
2019
+ /**
2020
+ * Attach a `name`+`value` pair to a submit button so it round-trips
2021
+ * through the form body when this specific button is clicked. Wires
2022
+ * the "Create & create another" pattern: the secondary submit posts
2023
+ * `{ _continueCreate: '1' }` and the server routes the redirect back
2024
+ * to the create page. Browsers natively only include the clicked
2025
+ * submit button's name/value in `FormData`; the FormRenderer threads
2026
+ * `event.submitter` into the `FormData` constructor to preserve that.
2027
+ */
2028
+ formField(name: string, value: string = '1'): this {
2029
+ this._formField = { name, value }
2030
+ return this
2031
+ }
2032
+
2033
+ // ─── Getters ──────────────────────────────────────────
2034
+
2035
+ getLabel(): string { return this._label }
2036
+ getPlacement(): ActionPlacement { return this._placement }
2037
+ isDestructive(): boolean { return this._destructive }
2038
+ getHandler(): ActionHandler | undefined { return this._handler }
2039
+ /** Registry key set by `.handler(string)`; undefined when the handler
2040
+ * is a closure (or unset). Mutually exclusive with `getHandler()`. */
2041
+ getHandlerName(): string | undefined { return this._handlerName }
2042
+ /** Per-fire context set by `.payload({…})`. Empty `{}` when unset
2043
+ * (callers can spread without a null check). */
2044
+ getPayload(): Record<string, unknown> { return this._payload ?? {} }
2045
+ /** Filament chain modifier — true when `.markAsRead()` was called.
2046
+ * Read by `Notification.actions([…])` serializer; ignored elsewhere. */
2047
+ isMarkAsReadOnFire(): boolean { return this._markAsReadOnFire }
2048
+ /** Honored by the notification action-strip renderers; equivalent to
2049
+ * `target="_blank"` on the underlying anchor. */
2050
+ isOpenUrlInNewTab(): boolean { return this._openUrlInNewTab }
2051
+ getHref(): string | undefined { return this._href }
2052
+ getMethod(): ActionMethod | undefined { return this._method }
2053
+ getActionUrl(): string | undefined { return this._actionUrl }
2054
+ getDispatchUrl(): string | undefined { return this._dispatchUrl }
2055
+ isSubmit(): boolean { return this._submit }
2056
+ getFormTarget(): string | undefined { return this._formTarget }
2057
+ getFormField(): { name: string; value: string } | undefined { return this._formField }
2058
+ hasModal(): boolean { return this._hasModal }
2059
+ /** Schema fields stored as children; `getChildren()` returns the same. */
2060
+ getSchema(): Element[] { return this._children ?? [] }
2061
+ getColor(): ActionColor | undefined { return this._color }
2062
+ getIcon(): string | undefined { return this._icon }
2063
+ getSize(): ActionSize | undefined { return this._size }
2064
+ getTooltip(): string | undefined { return this._tooltip }
2065
+ isOutlined(): boolean { return this._outlined }
2066
+ isIconOnly(): boolean { return this._iconOnly }
2067
+ getBadge(): string | number | undefined { return this._badge }
2068
+
2069
+ // ─── Element contract ────────────────────────────────
2070
+
2071
+ getType(): string { return 'action' }
2072
+
2073
+ override toMeta(): ActionMeta {
2074
+ const modal: ActionModalMeta | undefined = this._hasModal ? {
2075
+ ...(this._modalHeading !== undefined ? { heading: this._modalHeading } : {}),
2076
+ ...(this._modalDescription !== undefined ? { description: this._modalDescription } : {}),
2077
+ ...(this._modalSubmitLabel !== undefined ? { submitLabel: this._modalSubmitLabel } : {}),
2078
+ ...(this._modalCancelLabel !== undefined ? { cancelLabel: this._modalCancelLabel } : {}),
2079
+ ...(this._modalIcon !== undefined ? { icon: this._modalIcon } : {}),
2080
+ ...(this._modalIconColor !== undefined ? { iconColor: this._modalIconColor } : {}),
2081
+ ...(this._modalWidth !== undefined ? { width: this._modalWidth } : {}),
2082
+ ...(this._modalAlignment !== undefined ? { alignment: this._modalAlignment } : {}),
2083
+ ...(this._slideOver ? { slideOver: true } : {}),
2084
+ // Boolean defaults are emitted only when the user has flipped them
2085
+ // away from the default — keeps the wire-shape unchanged for the
2086
+ // common case (no setter ever called).
2087
+ ...(this._closeModalByClickingAway === false ? { closeByClickingAway: false } : {}),
2088
+ ...(this._closeModalByEscaping === false ? { closeByEscaping: false } : {}),
2089
+ ...(this._stickyModalHeader ? { stickyHeader: true } : {}),
2090
+ ...(this._stickyModalFooter ? { stickyFooter: true } : {}),
2091
+ ...(this._modalCloseButton ? { closeButton: true } : {}),
2092
+ ...(this._modalAutofocus !== undefined ? { autofocus: this._modalAutofocus } : {}),
2093
+ } : undefined
2094
+ return {
2095
+ type: 'action',
2096
+ name: this.name,
2097
+ label: this._label,
2098
+ placement: this._placement,
2099
+ destructive: this._destructive,
2100
+ ...(this._icon ? { icon: this._icon } : {}),
2101
+ ...(this._confirm ? { confirm: this._confirm } : {}),
2102
+ ...(this._href ? { href: this._href } : {}),
2103
+ ...(this._method ? { method: this._method } : {}),
2104
+ ...(this._actionUrl ? { action: this._actionUrl } : {}),
2105
+ ...(this._dispatchUrl ? { dispatchUrl: this._dispatchUrl } : {}),
2106
+ ...(this._submit ? { submit: true } : {}),
2107
+ ...(this._formTarget ? { form: this._formTarget } : {}),
2108
+ ...(this._formField ? { formField: this._formField } : {}),
2109
+ ...(modal ? { modal } : {}),
2110
+ ...(this._color ? { color: this._color } : {}),
2111
+ ...(this._size ? { size: this._size } : {}),
2112
+ ...(this._tooltip ? { tooltip: this._tooltip } : {}),
2113
+ ...(this._outlined ? { outlined: true } : {}),
2114
+ ...(this._iconOnly ? { iconOnly: true } : {}),
2115
+ ...(this._badge !== undefined ? { badge: this._badge } : {}),
2116
+ ...(this._badgeColor ? { badgeColor: this._badgeColor } : {}),
2117
+ ...(this.hasVisibilityRules() ? { conditional: true } : {}),
2118
+ }
2119
+ }
2120
+ }
2121
+
2122
+ /** Re-export for routes/dispatch consumers that need to type-narrow on
2123
+ * action validation failures. */
2124
+ export type { ValidationErrors }