@pilotiq/pilotiq 0.7.2 → 0.8.1

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 (371) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +208 -0
  3. package/CLAUDE.md +59 -3
  4. package/dist/Pilotiq.d.ts +83 -0
  5. package/dist/Pilotiq.d.ts.map +1 -1
  6. package/dist/Pilotiq.js +39 -0
  7. package/dist/Pilotiq.js.map +1 -1
  8. package/dist/actions/Action.d.ts +27 -99
  9. package/dist/actions/Action.d.ts.map +1 -1
  10. package/dist/actions/Action.js +52 -754
  11. package/dist/actions/Action.js.map +1 -1
  12. package/dist/actions/bulkFactories.d.ts +46 -0
  13. package/dist/actions/bulkFactories.d.ts.map +1 -0
  14. package/dist/actions/bulkFactories.js +144 -0
  15. package/dist/actions/bulkFactories.js.map +1 -0
  16. package/dist/actions/crudFactories.d.ts +94 -0
  17. package/dist/actions/crudFactories.d.ts.map +1 -0
  18. package/dist/actions/crudFactories.js +209 -0
  19. package/dist/actions/crudFactories.js.map +1 -0
  20. package/dist/actions/factoryHelpers.d.ts +108 -0
  21. package/dist/actions/factoryHelpers.d.ts.map +1 -0
  22. package/dist/actions/factoryHelpers.js +138 -0
  23. package/dist/actions/factoryHelpers.js.map +1 -0
  24. package/dist/actions/m2mFactories.d.ts +47 -0
  25. package/dist/actions/m2mFactories.d.ts.map +1 -0
  26. package/dist/actions/m2mFactories.js +173 -0
  27. package/dist/actions/m2mFactories.js.map +1 -0
  28. package/dist/actions/relationFactories.d.ts +93 -0
  29. package/dist/actions/relationFactories.d.ts.map +1 -0
  30. package/dist/actions/relationFactories.js +321 -0
  31. package/dist/actions/relationFactories.js.map +1 -0
  32. package/dist/elements/dispatchForm.js +1 -1
  33. package/dist/elements/dispatchForm.js.map +1 -1
  34. package/dist/elements/dispatchTable.js +1 -1
  35. package/dist/elements/dispatchTable.js.map +1 -1
  36. package/dist/fields/Field.d.ts +31 -0
  37. package/dist/fields/Field.d.ts.map +1 -1
  38. package/dist/fields/Field.js +25 -0
  39. package/dist/fields/Field.js.map +1 -1
  40. package/dist/pageData/breadcrumbs.d.ts +42 -0
  41. package/dist/pageData/breadcrumbs.d.ts.map +1 -0
  42. package/dist/pageData/breadcrumbs.js +172 -0
  43. package/dist/pageData/breadcrumbs.js.map +1 -0
  44. package/dist/pageData/forms.d.ts +137 -0
  45. package/dist/pageData/forms.d.ts.map +1 -0
  46. package/dist/pageData/forms.js +427 -0
  47. package/dist/pageData/forms.js.map +1 -0
  48. package/dist/pageData/helpers.d.ts +239 -0
  49. package/dist/pageData/helpers.d.ts.map +1 -0
  50. package/dist/pageData/helpers.js +703 -0
  51. package/dist/pageData/helpers.js.map +1 -0
  52. package/dist/pageData/misc.d.ts +76 -0
  53. package/dist/pageData/misc.d.ts.map +1 -0
  54. package/dist/pageData/misc.js +263 -0
  55. package/dist/pageData/misc.js.map +1 -0
  56. package/dist/pageData/navigation.d.ts +292 -0
  57. package/dist/pageData/navigation.d.ts.map +1 -0
  58. package/dist/pageData/navigation.js +591 -0
  59. package/dist/pageData/navigation.js.map +1 -0
  60. package/dist/pageData/relationPages.d.ts +172 -0
  61. package/dist/pageData/relationPages.d.ts.map +1 -0
  62. package/dist/pageData/relationPages.js +867 -0
  63. package/dist/pageData/relationPages.js.map +1 -0
  64. package/dist/pageData/relationTabs.d.ts +65 -0
  65. package/dist/pageData/relationTabs.d.ts.map +1 -0
  66. package/dist/pageData/relationTabs.js +258 -0
  67. package/dist/pageData/relationTabs.js.map +1 -0
  68. package/dist/pageData/resourcePages.d.ts +48 -0
  69. package/dist/pageData/resourcePages.d.ts.map +1 -0
  70. package/dist/pageData/resourcePages.js +504 -0
  71. package/dist/pageData/resourcePages.js.map +1 -0
  72. package/dist/pageData.d.ts +12 -792
  73. package/dist/pageData.d.ts.map +1 -1
  74. package/dist/pageData.js +24 -3797
  75. package/dist/pageData.js.map +1 -1
  76. package/dist/react/AppShell.d.ts +8 -0
  77. package/dist/react/AppShell.d.ts.map +1 -1
  78. package/dist/react/AppShell.js +11 -1
  79. package/dist/react/AppShell.js.map +1 -1
  80. package/dist/react/CollabExtensionFactoryRegistry.d.ts +47 -0
  81. package/dist/react/CollabExtensionFactoryRegistry.d.ts.map +1 -0
  82. package/dist/react/CollabExtensionFactoryRegistry.js +14 -0
  83. package/dist/react/CollabExtensionFactoryRegistry.js.map +1 -0
  84. package/dist/react/CollabRoomContext.d.ts +37 -0
  85. package/dist/react/CollabRoomContext.d.ts.map +1 -0
  86. package/dist/react/CollabRoomContext.js +12 -0
  87. package/dist/react/CollabRoomContext.js.map +1 -0
  88. package/dist/react/FormCollabBindingRegistry.d.ts +62 -0
  89. package/dist/react/FormCollabBindingRegistry.d.ts.map +1 -0
  90. package/dist/react/FormCollabBindingRegistry.js +14 -0
  91. package/dist/react/FormCollabBindingRegistry.js.map +1 -0
  92. package/dist/react/FormStateContext.d.ts.map +1 -1
  93. package/dist/react/FormStateContext.js +87 -0
  94. package/dist/react/FormStateContext.js.map +1 -1
  95. package/dist/react/RecordWrapperGate.d.ts +25 -0
  96. package/dist/react/RecordWrapperGate.d.ts.map +1 -0
  97. package/dist/react/RecordWrapperGate.js +30 -0
  98. package/dist/react/RecordWrapperGate.js.map +1 -0
  99. package/dist/react/RecordWrapperRegistry.d.ts +31 -0
  100. package/dist/react/RecordWrapperRegistry.d.ts.map +1 -0
  101. package/dist/react/RecordWrapperRegistry.js +15 -0
  102. package/dist/react/RecordWrapperRegistry.js.map +1 -0
  103. package/dist/react/SchemaRenderer.d.ts +17 -23
  104. package/dist/react/SchemaRenderer.d.ts.map +1 -1
  105. package/dist/react/SchemaRenderer.js +71 -3647
  106. package/dist/react/SchemaRenderer.js.map +1 -1
  107. package/dist/react/component-slots.d.ts +103 -0
  108. package/dist/react/component-slots.d.ts.map +1 -0
  109. package/dist/react/component-slots.js +18 -0
  110. package/dist/react/component-slots.js.map +1 -0
  111. package/dist/react/fields/BuilderInput.d.ts.map +1 -1
  112. package/dist/react/fields/BuilderInput.js +21 -117
  113. package/dist/react/fields/BuilderInput.js.map +1 -1
  114. package/dist/react/fields/MarkdownInput.d.ts.map +1 -1
  115. package/dist/react/fields/MarkdownInput.js +1 -3
  116. package/dist/react/fields/MarkdownInput.js.map +1 -1
  117. package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
  118. package/dist/react/fields/RepeaterInput.js +22 -127
  119. package/dist/react/fields/RepeaterInput.js.map +1 -1
  120. package/dist/react/fields/rowState.d.ts +40 -0
  121. package/dist/react/fields/rowState.d.ts.map +1 -0
  122. package/dist/react/fields/rowState.js +60 -0
  123. package/dist/react/fields/rowState.js.map +1 -0
  124. package/dist/react/fields/useRowReorderDnd.d.ts +28 -0
  125. package/dist/react/fields/useRowReorderDnd.d.ts.map +1 -0
  126. package/dist/react/fields/useRowReorderDnd.js +51 -0
  127. package/dist/react/fields/useRowReorderDnd.js.map +1 -0
  128. package/dist/react/index.d.ts +9 -0
  129. package/dist/react/index.d.ts.map +1 -1
  130. package/dist/react/index.js +8 -0
  131. package/dist/react/index.js.map +1 -1
  132. package/dist/react/layouts/SidebarLayout.d.ts +1 -1
  133. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  134. package/dist/react/layouts/SidebarLayout.js +10 -2
  135. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  136. package/dist/react/layouts/TopbarLayout.d.ts +1 -1
  137. package/dist/react/layouts/TopbarLayout.d.ts.map +1 -1
  138. package/dist/react/layouts/TopbarLayout.js +19 -11
  139. package/dist/react/layouts/TopbarLayout.js.map +1 -1
  140. package/dist/react/parseRecordEditUrl.d.ts +29 -0
  141. package/dist/react/parseRecordEditUrl.d.ts.map +1 -0
  142. package/dist/react/parseRecordEditUrl.js +25 -0
  143. package/dist/react/parseRecordEditUrl.js.map +1 -0
  144. package/dist/react/persistedState.d.ts +19 -0
  145. package/dist/react/persistedState.d.ts.map +1 -0
  146. package/dist/react/persistedState.js +51 -0
  147. package/dist/react/persistedState.js.map +1 -0
  148. package/dist/react/schemaRenderer/AlertRenderer.d.ts +12 -0
  149. package/dist/react/schemaRenderer/AlertRenderer.d.ts.map +1 -0
  150. package/dist/react/schemaRenderer/AlertRenderer.js +61 -0
  151. package/dist/react/schemaRenderer/AlertRenderer.js.map +1 -0
  152. package/dist/react/schemaRenderer/EntryRenderer.d.ts +13 -0
  153. package/dist/react/schemaRenderer/EntryRenderer.d.ts.map +1 -0
  154. package/dist/react/schemaRenderer/EntryRenderer.js +277 -0
  155. package/dist/react/schemaRenderer/EntryRenderer.js.map +1 -0
  156. package/dist/react/schemaRenderer/SectionRenderer.d.ts +16 -0
  157. package/dist/react/schemaRenderer/SectionRenderer.d.ts.map +1 -0
  158. package/dist/react/schemaRenderer/SectionRenderer.js +62 -0
  159. package/dist/react/schemaRenderer/SectionRenderer.js.map +1 -0
  160. package/dist/react/schemaRenderer/SimpleElements.d.ts +25 -0
  161. package/dist/react/schemaRenderer/SimpleElements.d.ts.map +1 -0
  162. package/dist/react/schemaRenderer/SimpleElements.js +147 -0
  163. package/dist/react/schemaRenderer/SimpleElements.js.map +1 -0
  164. package/dist/react/schemaRenderer/TabsRenderer.d.ts +17 -0
  165. package/dist/react/schemaRenderer/TabsRenderer.d.ts.map +1 -0
  166. package/dist/react/schemaRenderer/TabsRenderer.js +31 -0
  167. package/dist/react/schemaRenderer/TabsRenderer.js.map +1 -0
  168. package/dist/react/schemaRenderer/WizardRenderer.d.ts +34 -0
  169. package/dist/react/schemaRenderer/WizardRenderer.d.ts.map +1 -0
  170. package/dist/react/schemaRenderer/WizardRenderer.js +208 -0
  171. package/dist/react/schemaRenderer/WizardRenderer.js.map +1 -0
  172. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts +21 -0
  173. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts.map +1 -0
  174. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js +82 -0
  175. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js.map +1 -0
  176. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts +30 -0
  177. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts.map +1 -0
  178. package/dist/react/schemaRenderer/action/ActionModalDialog.js +182 -0
  179. package/dist/react/schemaRenderer/action/ActionModalDialog.js.map +1 -0
  180. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts +17 -0
  181. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts.map +1 -0
  182. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js +19 -0
  183. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js.map +1 -0
  184. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts +16 -0
  185. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts.map +1 -0
  186. package/dist/react/schemaRenderer/action/HandlerActionButton.js +16 -0
  187. package/dist/react/schemaRenderer/action/HandlerActionButton.js.map +1 -0
  188. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts +22 -0
  189. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts.map +1 -0
  190. package/dist/react/schemaRenderer/action/MethodActionButton.js +26 -0
  191. package/dist/react/schemaRenderer/action/MethodActionButton.js.map +1 -0
  192. package/dist/react/schemaRenderer/action/buttons.d.ts +18 -0
  193. package/dist/react/schemaRenderer/action/buttons.d.ts.map +1 -0
  194. package/dist/react/schemaRenderer/action/buttons.js +74 -0
  195. package/dist/react/schemaRenderer/action/buttons.js.map +1 -0
  196. package/dist/react/schemaRenderer/action/helpers.d.ts +26 -0
  197. package/dist/react/schemaRenderer/action/helpers.d.ts.map +1 -0
  198. package/dist/react/schemaRenderer/action/helpers.js +126 -0
  199. package/dist/react/schemaRenderer/action/helpers.js.map +1 -0
  200. package/dist/react/schemaRenderer/action/renderAction.d.ts +21 -0
  201. package/dist/react/schemaRenderer/action/renderAction.d.ts.map +1 -0
  202. package/dist/react/schemaRenderer/action/renderAction.js +102 -0
  203. package/dist/react/schemaRenderer/action/renderAction.js.map +1 -0
  204. package/dist/react/schemaRenderer/columnFormat.d.ts +10 -0
  205. package/dist/react/schemaRenderer/columnFormat.d.ts.map +1 -0
  206. package/dist/react/schemaRenderer/columnFormat.js +76 -0
  207. package/dist/react/schemaRenderer/columnFormat.js.map +1 -0
  208. package/dist/react/schemaRenderer/constants.d.ts +8 -0
  209. package/dist/react/schemaRenderer/constants.d.ts.map +1 -0
  210. package/dist/react/schemaRenderer/constants.js +45 -0
  211. package/dist/react/schemaRenderer/constants.js.map +1 -0
  212. package/dist/react/schemaRenderer/form/FormRenderer.d.ts +29 -0
  213. package/dist/react/schemaRenderer/form/FormRenderer.d.ts.map +1 -0
  214. package/dist/react/schemaRenderer/form/FormRenderer.js +163 -0
  215. package/dist/react/schemaRenderer/form/FormRenderer.js.map +1 -0
  216. package/dist/react/schemaRenderer/form/renderField.d.ts +6 -0
  217. package/dist/react/schemaRenderer/form/renderField.d.ts.map +1 -0
  218. package/dist/react/schemaRenderer/form/renderField.js +239 -0
  219. package/dist/react/schemaRenderer/form/renderField.js.map +1 -0
  220. package/dist/react/schemaRenderer/helpers.d.ts +32 -0
  221. package/dist/react/schemaRenderer/helpers.d.ts.map +1 -0
  222. package/dist/react/schemaRenderer/helpers.js +52 -0
  223. package/dist/react/schemaRenderer/helpers.js.map +1 -0
  224. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts +60 -0
  225. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts.map +1 -0
  226. package/dist/react/schemaRenderer/table/CardsLayoutBody.js +189 -0
  227. package/dist/react/schemaRenderer/table/CardsLayoutBody.js.map +1 -0
  228. package/dist/react/schemaRenderer/table/TableRenderer.d.ts +29 -0
  229. package/dist/react/schemaRenderer/table/TableRenderer.d.ts.map +1 -0
  230. package/dist/react/schemaRenderer/table/TableRenderer.js +85 -0
  231. package/dist/react/schemaRenderer/table/TableRenderer.js.map +1 -0
  232. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts +18 -0
  233. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts.map +1 -0
  234. package/dist/react/schemaRenderer/table/TableRendererBody.js +555 -0
  235. package/dist/react/schemaRenderer/table/TableRendererBody.js.map +1 -0
  236. package/dist/react/schemaRenderer/table/filters.d.ts +263 -0
  237. package/dist/react/schemaRenderer/table/filters.d.ts.map +1 -0
  238. package/dist/react/schemaRenderer/table/filters.js +497 -0
  239. package/dist/react/schemaRenderer/table/filters.js.map +1 -0
  240. package/dist/react/schemaRenderer/table/formatCell.d.ts +11 -0
  241. package/dist/react/schemaRenderer/table/formatCell.d.ts.map +1 -0
  242. package/dist/react/schemaRenderer/table/formatCell.js +172 -0
  243. package/dist/react/schemaRenderer/table/formatCell.js.map +1 -0
  244. package/dist/react/schemaRenderer/table/links.d.ts +42 -0
  245. package/dist/react/schemaRenderer/table/links.d.ts.map +1 -0
  246. package/dist/react/schemaRenderer/table/links.js +55 -0
  247. package/dist/react/schemaRenderer/table/links.js.map +1 -0
  248. package/dist/react/schemaRenderer/table/renderRowActions.d.ts +13 -0
  249. package/dist/react/schemaRenderer/table/renderRowActions.d.ts.map +1 -0
  250. package/dist/react/schemaRenderer/table/renderRowActions.js +25 -0
  251. package/dist/react/schemaRenderer/table/renderRowActions.js.map +1 -0
  252. package/dist/react/schemaRenderer/table/url.d.ts +41 -0
  253. package/dist/react/schemaRenderer/table/url.d.ts.map +1 -0
  254. package/dist/react/schemaRenderer/table/url.js +114 -0
  255. package/dist/react/schemaRenderer/table/url.js.map +1 -0
  256. package/dist/routes/globals.d.ts +13 -0
  257. package/dist/routes/globals.d.ts.map +1 -0
  258. package/dist/routes/globals.js +131 -0
  259. package/dist/routes/globals.js.map +1 -0
  260. package/dist/routes/helpers.d.ts +217 -0
  261. package/dist/routes/helpers.d.ts.map +1 -0
  262. package/dist/routes/helpers.js +498 -0
  263. package/dist/routes/helpers.js.map +1 -0
  264. package/dist/routes/pages.d.ts +15 -0
  265. package/dist/routes/pages.d.ts.map +1 -0
  266. package/dist/routes/pages.js +145 -0
  267. package/dist/routes/pages.js.map +1 -0
  268. package/dist/routes/panel.d.ts +19 -0
  269. package/dist/routes/panel.d.ts.map +1 -0
  270. package/dist/routes/panel.js +191 -0
  271. package/dist/routes/panel.js.map +1 -0
  272. package/dist/routes/relations.d.ts +21 -0
  273. package/dist/routes/relations.d.ts.map +1 -0
  274. package/dist/routes/relations.js +1239 -0
  275. package/dist/routes/relations.js.map +1 -0
  276. package/dist/routes/resources.d.ts +28 -0
  277. package/dist/routes/resources.d.ts.map +1 -0
  278. package/dist/routes/resources.js +741 -0
  279. package/dist/routes/resources.js.map +1 -0
  280. package/dist/routes/theme.d.ts +12 -0
  281. package/dist/routes/theme.d.ts.map +1 -0
  282. package/dist/routes/theme.js +82 -0
  283. package/dist/routes/theme.js.map +1 -0
  284. package/dist/routes.d.ts.map +1 -1
  285. package/dist/routes.js +64 -3078
  286. package/dist/routes.js.map +1 -1
  287. package/dist/vite.d.ts +1 -0
  288. package/dist/vite.d.ts.map +1 -1
  289. package/dist/vite.js +26 -5
  290. package/dist/vite.js.map +1 -1
  291. package/package.json +2 -1
  292. package/src/Pilotiq.ts +95 -0
  293. package/src/actions/Action.ts +79 -723
  294. package/src/actions/bulkFactories.ts +168 -0
  295. package/src/actions/crudFactories.ts +220 -0
  296. package/src/actions/factoryHelpers.ts +177 -0
  297. package/src/actions/m2mFactories.ts +193 -0
  298. package/src/actions/relationFactories.ts +372 -0
  299. package/src/elements/dispatchForm.ts +1 -1
  300. package/src/elements/dispatchTable.ts +1 -1
  301. package/src/fields/Field.ts +39 -0
  302. package/src/pageData/breadcrumbs.ts +288 -0
  303. package/src/pageData/forms.ts +578 -0
  304. package/src/pageData/helpers.ts +764 -0
  305. package/src/pageData/misc.ts +347 -0
  306. package/src/pageData/navigation.ts +779 -0
  307. package/src/pageData/relationPages.ts +1246 -0
  308. package/src/pageData/relationTabs.ts +286 -0
  309. package/src/pageData/resourcePages.ts +593 -0
  310. package/src/pageData.ts +122 -4731
  311. package/src/react/AppShell.tsx +27 -1
  312. package/src/react/CollabExtensionFactoryRegistry.ts +55 -0
  313. package/src/react/CollabRoomContext.ts +42 -0
  314. package/src/react/FormCollabBindingRegistry.ts +72 -0
  315. package/src/react/FormStateContext.tsx +91 -0
  316. package/src/react/RecordWrapperGate.tsx +40 -0
  317. package/src/react/RecordWrapperRegistry.ts +39 -0
  318. package/src/react/SchemaRenderer.tsx +230 -6479
  319. package/src/react/component-slots.test.ts +103 -0
  320. package/src/react/component-slots.ts +116 -0
  321. package/src/react/fields/BuilderInput.tsx +29 -117
  322. package/src/react/fields/MarkdownInput.tsx +0 -1
  323. package/src/react/fields/RepeaterInput.tsx +29 -130
  324. package/src/react/fields/rowState.ts +106 -0
  325. package/src/react/fields/useRowReorderDnd.ts +78 -0
  326. package/src/react/index.ts +38 -0
  327. package/src/react/layouts/SidebarLayout.tsx +39 -28
  328. package/src/react/layouts/TopbarLayout.tsx +70 -57
  329. package/src/react/parseRecordEditUrl.test.ts +75 -0
  330. package/src/react/parseRecordEditUrl.ts +55 -0
  331. package/src/react/persistedState.ts +40 -0
  332. package/src/react/schemaRenderer/AlertRenderer.tsx +112 -0
  333. package/src/react/schemaRenderer/EntryRenderer.tsx +501 -0
  334. package/src/react/schemaRenderer/SectionRenderer.tsx +120 -0
  335. package/src/react/schemaRenderer/SimpleElements.tsx +306 -0
  336. package/src/react/schemaRenderer/TabsRenderer.tsx +62 -0
  337. package/src/react/schemaRenderer/WizardRenderer.tsx +338 -0
  338. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +177 -0
  339. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +273 -0
  340. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +61 -0
  341. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +43 -0
  342. package/src/react/schemaRenderer/action/MethodActionButton.tsx +64 -0
  343. package/src/react/schemaRenderer/action/buttons.tsx +99 -0
  344. package/src/react/schemaRenderer/action/helpers.ts +140 -0
  345. package/src/react/schemaRenderer/action/renderAction.tsx +245 -0
  346. package/src/react/schemaRenderer/columnFormat.ts +65 -0
  347. package/src/react/schemaRenderer/constants.ts +50 -0
  348. package/src/react/schemaRenderer/form/FormRenderer.tsx +245 -0
  349. package/src/react/schemaRenderer/form/renderField.tsx +511 -0
  350. package/src/react/schemaRenderer/helpers.tsx +81 -0
  351. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +308 -0
  352. package/src/react/schemaRenderer/table/TableRenderer.tsx +123 -0
  353. package/src/react/schemaRenderer/table/TableRendererBody.tsx +974 -0
  354. package/src/react/schemaRenderer/table/filters.tsx +1233 -0
  355. package/src/react/schemaRenderer/table/formatCell.tsx +264 -0
  356. package/src/react/schemaRenderer/table/links.tsx +112 -0
  357. package/src/react/schemaRenderer/table/renderRowActions.tsx +52 -0
  358. package/src/react/schemaRenderer/table/url.tsx +143 -0
  359. package/src/routes/globals.ts +154 -0
  360. package/src/routes/helpers.ts +668 -0
  361. package/src/routes/pages.ts +173 -0
  362. package/src/routes/panel.ts +204 -0
  363. package/src/routes/relations.ts +1219 -0
  364. package/src/routes/resources.ts +786 -0
  365. package/src/routes/theme.ts +109 -0
  366. package/src/routes.test.ts +1 -1
  367. package/src/routes.ts +64 -3176
  368. package/src/schema/TableWidget.test.ts +2 -2
  369. package/src/theme/migrate.test.ts +178 -0
  370. package/src/vite.test.ts +184 -0
  371. package/src/vite.ts +26 -4
@@ -0,0 +1,555 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useState } from 'react';
3
+ import { ChevronDownIcon, GripVerticalIcon, InboxIcon } from 'lucide-react';
4
+ import { useNavigate } from '../../navigate.js';
5
+ import { readStoredFlag, writeStoredFlag } from '../../persistedState.js';
6
+ import { useToast } from '../../Toaster.js';
7
+ import { Checkbox } from '../../ui/checkbox.js';
8
+ import { Input } from '../../ui/input.js';
9
+ import { Table as DataTable, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, } from '../../ui/table.js';
10
+ import { pickEditableCell } from '../../cells/EditableCell.js';
11
+ import { resolveIcon } from '../helpers.js';
12
+ import { buildTableQuery, nextSortDir, prefixK, SearchFormHiddenInputs, } from './url.js';
13
+ import { formatCell, rowId } from './formatCell.js';
14
+ import { ActiveFiltersBar, ColumnsToggleDropdown, FilterPopover, FilterStrip, FilterStripToggle, GroupHeaderText, resolveColumnUrl, SortByPicker, TableGroupPicker, } from './filters.js';
15
+ import { ActiveGroupKeyChip, GroupHeadingLink, RecordCellLink } from './links.js';
16
+ import { CardsLayoutBody } from './CardsLayoutBody.js';
17
+ import { renderRowActions } from './renderRowActions.js';
18
+ import { useRowReorderDnd } from '../../fields/useRowReorderDnd.js';
19
+ export function TableRendererBody({ el, deps }) {
20
+ const { renderElement, renderActionLike, renderFormChild } = deps;
21
+ const navigate = useNavigate();
22
+ const children = el.children ?? [];
23
+ const columns = children.filter(c => c.type === 'column');
24
+ // `Column.toggleable()` columns — sourced from the resolved meta. The
25
+ // user's per-table visibility map is owned + persisted below; the full
26
+ // `columns` list stays available for the toolbar dropdown so hidden
27
+ // columns can be re-shown without a roundtrip.
28
+ const toggleableColumns = columns.filter(c => c['toggleable'] !== undefined);
29
+ // Actions and ActionGroups share placement — both show up in the
30
+ // header/bulk/row toolbars depending on their `placement` field.
31
+ const actionLike = children.filter(c => c.type === 'action' || c.type === 'actionGroup' || c.type === 'slotComponent');
32
+ const filters = children.filter(c => c.type === 'filter');
33
+ const hasRecordUrl = Boolean(el['recordUrl']);
34
+ const hasRecordClasses = Boolean(el['recordClasses']);
35
+ const pollInterval = typeof el['pollInterval'] === 'number' ? el['pollInterval'] : undefined;
36
+ const defaultGroup = typeof el['defaultGroup'] === 'string' ? el['defaultGroup'] : undefined;
37
+ const activeGroupKey = typeof el['activeGroupKey'] === 'string' ? el['activeGroupKey'] : undefined;
38
+ const summaries = el['summaries'];
39
+ const groupSummaries = el['groupSummaries'];
40
+ const groupOptions = el['groups'] ?? [];
41
+ // Active group's registered metadata (if any). Falls back to a synth
42
+ // for the bare-column form so the heading row still has a label.
43
+ const activeGroupMeta = defaultGroup
44
+ ? (groupOptions.find(g => g.column === defaultGroup) ?? {
45
+ column: defaultGroup,
46
+ label: (() => {
47
+ const col = columns.find(c => c['name'] === defaultGroup);
48
+ return col ? String(col['label'] ?? defaultGroup) : defaultGroup;
49
+ })(),
50
+ })
51
+ : undefined;
52
+ const groupColumnLabel = activeGroupMeta?.label;
53
+ // Heading text becomes a real `<a href>` when the active group opts in
54
+ // via `.scopable()`. Synthesized bare-column groups can't be scopable
55
+ // (no builder call ran).
56
+ const groupHeadingScopable = activeGroupMeta !== undefined
57
+ && activeGroupMeta.scopable === true;
58
+ // Auto-refresh: re-visit current URL on a timer so sort/filter/pagination
59
+ // state survives. Pause while the document is hidden — background tabs
60
+ // shouldn't keep hammering the server.
61
+ useEffect(() => {
62
+ if (!pollInterval || pollInterval <= 0)
63
+ return;
64
+ if (typeof document === 'undefined')
65
+ return;
66
+ let timerId;
67
+ const tick = () => navigate(window.location.pathname + window.location.search);
68
+ const start = () => {
69
+ if (timerId === undefined)
70
+ timerId = setInterval(tick, pollInterval * 1000);
71
+ };
72
+ const stop = () => {
73
+ if (timerId !== undefined) {
74
+ clearInterval(timerId);
75
+ timerId = undefined;
76
+ }
77
+ };
78
+ if (document.visibilityState === 'visible')
79
+ start();
80
+ const onVis = () => {
81
+ if (document.visibilityState === 'visible')
82
+ start();
83
+ else
84
+ stop();
85
+ };
86
+ document.addEventListener('visibilitychange', onVis);
87
+ return () => {
88
+ document.removeEventListener('visibilitychange', onVis);
89
+ stop();
90
+ };
91
+ }, [pollInterval, navigate]);
92
+ // Group actions by placement. `inline` defaults to header so it shows up
93
+ // somewhere visible — explicit placements always win.
94
+ const placementOf = (a) => String(a['placement'] ?? 'inline');
95
+ const headerActions = actionLike.filter(a => { const p = placementOf(a); return p === 'header' || p === 'inline'; });
96
+ const bulkActions = actionLike.filter(a => placementOf(a) === 'bulk');
97
+ const rowActions = actionLike.filter(a => placementOf(a) === 'row');
98
+ const rawRows = el['rows'] ?? [];
99
+ const total = el['total'] ?? rawRows.length;
100
+ const search = el['search'];
101
+ const currentSort = el['currentSort'];
102
+ const currentPage = el['currentPage'] ?? 1;
103
+ const perPage = el['perPage'];
104
+ const searchable = Boolean(el['searchable']);
105
+ const currentPath = el['currentPath'] ?? '';
106
+ // `Column.toggleable()` user-visibility map. Persisted per-table at
107
+ // `pilotiq.table.<currentPath>.columns.<name>` ('1' = hidden,
108
+ // '0' = visible). On first paint, fall back to `meta.toggleable.initiallyHidden`.
109
+ // SSR returns the meta default — the localStorage hydrate happens
110
+ // inside the effect so server + first client render match.
111
+ const columnsVisibilityKey = (name) => `pilotiq.table.${currentPath}.columns.${name}`;
112
+ const initialHidden = () => {
113
+ const out = new Set();
114
+ for (const col of toggleableColumns) {
115
+ const cfg = col['toggleable'];
116
+ if (cfg?.initiallyHidden)
117
+ out.add(String(col['name']));
118
+ }
119
+ return out;
120
+ };
121
+ const [hiddenColumns, setHiddenColumns] = useState(initialHidden);
122
+ useEffect(() => {
123
+ if (toggleableColumns.length === 0)
124
+ return;
125
+ const next = new Set();
126
+ for (const col of toggleableColumns) {
127
+ const name = String(col['name']);
128
+ const cfg = col['toggleable'];
129
+ if (readStoredFlag(columnsVisibilityKey(name), Boolean(cfg?.initiallyHidden))) {
130
+ next.add(name);
131
+ }
132
+ }
133
+ setHiddenColumns(next);
134
+ // eslint-disable-next-line react-hooks/exhaustive-deps
135
+ }, [currentPath, toggleableColumns.length]);
136
+ const toggleColumnHidden = (name, nextHidden) => {
137
+ setHiddenColumns(prev => {
138
+ const next = new Set(prev);
139
+ if (nextHidden)
140
+ next.add(name);
141
+ else
142
+ next.delete(name);
143
+ writeStoredFlag(columnsVisibilityKey(name), nextHidden);
144
+ return next;
145
+ });
146
+ };
147
+ // Filtered column list used by every render path (header, body cells,
148
+ // group + footer summaries, empty-state colSpan). Non-toggleable
149
+ // columns always survive.
150
+ const visibleColumns = columns.filter(c => !hiddenColumns.has(String(c['name'])));
151
+ // Tier-3 — when the table opts into `Table.queryStringIdentifier(...)`,
152
+ // every URL key (search / sort / page / perPage / group / filter names)
153
+ // gets prefixed with `${id}_` so multiple tables on one page don't
154
+ // collide on `?search=` etc. Bare keys still apply when unset.
155
+ const queryPrefix = typeof el['queryStringIdentifier'] === 'string'
156
+ ? el['queryStringIdentifier']
157
+ : undefined;
158
+ // Reorderable rows — grip column + HTML5 DnD wiring. Rows live in
159
+ // local state during a drag so the optimistic reorder happens
160
+ // immediately; on POST failure we roll back to the server's order.
161
+ const reorderableColumn = typeof el['reorderableColumn'] === 'string' ? el['reorderableColumn'] : undefined;
162
+ const reorderUrl = typeof el['reorderUrl'] === 'string' ? el['reorderUrl'] : undefined;
163
+ const [reorderRowsLocal, setReorderRowsLocal] = useState(null);
164
+ const rows = reorderRowsLocal ?? rawRows;
165
+ const { notify } = useToast();
166
+ // Read the explicit `?group=` value out of the URL so sort/pagination
167
+ // links preserve "None" overrides (`?group=`). Server render: no URL,
168
+ // so we fall back to `defaultGroup` from the meta — which is already
169
+ // the reconciled active column.
170
+ const urlGroup = typeof window === 'undefined'
171
+ ? undefined
172
+ : (() => {
173
+ const sp = new URLSearchParams(window.location.search);
174
+ const k = prefixK(queryPrefix, 'group');
175
+ return sp.has(k) ? sp.get(k) : undefined;
176
+ })();
177
+ // Collapsible groups — per-group fold state. Keyed by `_groupValue`
178
+ // (the raw column value, NOT the resolved title) so rows that share a
179
+ // group key fold together. Persisted in localStorage at
180
+ // `pilotiq.table.<currentPath>.groups.<column>.<value>`. Default-
181
+ // collapsed groups derive their initial state from `meta.collapsed`.
182
+ const groupCollapsible = activeGroupMeta?.collapsible === true;
183
+ const groupDefaultCollapsed = activeGroupMeta?.collapsed === true;
184
+ const groupStorageKey = (groupValue) => `pilotiq.table.${currentPath}.groups.${defaultGroup ?? ''}.${groupValue}`;
185
+ // Lazy-init from localStorage on mount; SSR returns the meta default.
186
+ const [collapsedGroups, setCollapsedGroups] = useState({});
187
+ useEffect(() => {
188
+ if (!groupCollapsible || !defaultGroup)
189
+ return;
190
+ // Walk the rendered rows once on mount, picking up persisted state.
191
+ const next = {};
192
+ const seen = new Set();
193
+ for (const row of rows) {
194
+ const v = String(row['_groupValue'] ?? '');
195
+ if (seen.has(v))
196
+ continue;
197
+ seen.add(v);
198
+ next[v] = readStoredFlag(groupStorageKey(v), groupDefaultCollapsed);
199
+ }
200
+ setCollapsedGroups(next);
201
+ // Re-run if the active group changes — different values, different
202
+ // localStorage namespace.
203
+ // eslint-disable-next-line react-hooks/exhaustive-deps
204
+ }, [defaultGroup, groupCollapsible, groupDefaultCollapsed, currentPath]);
205
+ const toggleGroupCollapsed = (groupValue) => {
206
+ setCollapsedGroups(prev => {
207
+ const nextOpen = !prev[groupValue];
208
+ const next = { ...prev, [groupValue]: nextOpen };
209
+ writeStoredFlag(groupStorageKey(groupValue), nextOpen);
210
+ return next;
211
+ });
212
+ };
213
+ const state = {
214
+ ...(search !== undefined ? { search } : {}),
215
+ ...(currentSort !== undefined ? { sort: currentSort } : {}),
216
+ page: currentPage,
217
+ ...(urlGroup !== undefined ? { group: urlGroup }
218
+ : defaultGroup !== undefined ? { group: defaultGroup }
219
+ : {}),
220
+ ...(activeGroupKey !== undefined ? { groupKey: activeGroupKey } : {}),
221
+ };
222
+ // Snapshot active filter values for sort/pagination href construction.
223
+ // Filter form submits already carry these (selects are inside the
224
+ // form); `<a href>` links don't, so we re-emit them here.
225
+ const activeFilters = {};
226
+ for (const f of filters) {
227
+ const v = f['value'];
228
+ if (typeof v === 'string' && v !== '')
229
+ activeFilters[String(f['name'])] = v;
230
+ }
231
+ // Drill-in / drill-out URL builders for the group heading link and the
232
+ // active-key chip's clear button. Drill-in sets `?<prefix>groupKey=v`
233
+ // and resets `page`; drill-out clears it. Both round-trip foreign
234
+ // params (other tables' state) through `buildTableQuery`.
235
+ const buildGroupKeyHref = (value) => buildTableQuery(state, { groupKey: value, page: 1 }, currentPath, activeFilters, queryPrefix);
236
+ const drillOutHref = () => buildTableQuery(state, { groupKey: '', page: 1 }, currentPath, activeFilters, queryPrefix);
237
+ // Track which row ids are currently checked. Keyed by id (string), not
238
+ // by index, so pagination and re-renders don't drop selection state.
239
+ const [selected, setSelected] = useState(() => new Set());
240
+ const visibleIds = rows.map((row, i) => rowId(row, i));
241
+ const allChecked = visibleIds.length > 0 && visibleIds.every(id => selected.has(id));
242
+ const someChecked = selected.size > 0;
243
+ const toggleRow = (id) => {
244
+ setSelected(prev => {
245
+ const next = new Set(prev);
246
+ if (next.has(id))
247
+ next.delete(id);
248
+ else
249
+ next.add(id);
250
+ return next;
251
+ });
252
+ };
253
+ const toggleAll = () => {
254
+ setSelected(prev => {
255
+ if (visibleIds.every(id => prev.has(id))) {
256
+ const next = new Set(prev);
257
+ for (const id of visibleIds)
258
+ next.delete(id);
259
+ return next;
260
+ }
261
+ const next = new Set(prev);
262
+ for (const id of visibleIds)
263
+ next.add(id);
264
+ return next;
265
+ });
266
+ };
267
+ if (columns.length === 0) {
268
+ return (_jsx("div", { className: "rounded-xl border bg-card p-6 text-sm text-muted-foreground", children: "No columns configured for this table." }));
269
+ }
270
+ const isCardsLayout = el['contentLayout'] === 'cards';
271
+ const cardsPerRow = el['cardsPerRow'];
272
+ const totalPages = perPage && perPage > 0 ? Math.max(1, Math.ceil(total / perPage)) : 1;
273
+ const showPagination = totalPages > 1;
274
+ const hasFilters = filters.length > 0;
275
+ // Filter layout positions (Filament v5). `'modal'` (default) keeps the
276
+ // toolbar Filters button + popover. The three inline modes lay every
277
+ // filter widget out as a wrapping strip in the matching slot. The
278
+ // collapsible variant adds a toolbar toggle + per-table-path persisted
279
+ // open state.
280
+ const filtersLayout = el['filtersLayout'] ?? 'modal';
281
+ const filtersInModal = filtersLayout === 'modal';
282
+ const filtersAbove = filtersLayout === 'above-content'
283
+ || filtersLayout === 'above-content-collapsible';
284
+ const filtersBelow = filtersLayout === 'below-content';
285
+ const filtersCollapsible = filtersLayout === 'above-content-collapsible';
286
+ const filtersStripStorageKey = `pilotiq.table.${currentPath}.filters.open`;
287
+ const [filtersOpen, setFiltersOpen] = useState(() => {
288
+ if (!filtersCollapsible)
289
+ return true;
290
+ // Default to OPEN when filters are active (URL carried filter values
291
+ // in) so the user can see what's filtering — same UX cue as the
292
+ // active-filters pill row.
293
+ return readStoredFlag(filtersStripStorageKey, Object.keys(activeFilters).length > 0);
294
+ });
295
+ const toggleFiltersOpen = () => {
296
+ setFiltersOpen(prev => {
297
+ const next = !prev;
298
+ writeStoredFlag(filtersStripStorageKey, next);
299
+ return next;
300
+ });
301
+ };
302
+ // Show the "Group by" dropdown when 2+ groups are registered, or 1
303
+ // group with rich metadata (label/collapsible/etc.). A single bare
304
+ // `defaultGroup('col')` with no `groups([...])` registration shouldn't
305
+ // render the picker — there's nothing to pick.
306
+ const hasGroupPicker = groupOptions.length >= 2
307
+ || (groupOptions.length === 1 && Boolean(groupOptions[0].collapsible
308
+ || groupOptions[0].collapsed
309
+ || groupOptions[0].date));
310
+ const sortableColumns = isCardsLayout ? columns.filter(c => Boolean(c['sortable'])) : [];
311
+ const hasSortPicker = isCardsLayout && sortableColumns.length > 0;
312
+ // Only modal + collapsible mount a toolbar widget; the always-visible
313
+ // strip modes don't add anything to the header bar.
314
+ const showFiltersInToolbar = hasFilters && (filtersInModal || filtersCollapsible);
315
+ const hasColumnsToggle = toggleableColumns.length > 0;
316
+ const showHeaderBar = searchable || headerActions.length > 0 || showFiltersInToolbar || hasGroupPicker || hasSortPicker || hasColumnsToggle;
317
+ const hasBulkActions = bulkActions.length > 0;
318
+ const hasRowActions = rowActions.length > 0;
319
+ // Drag-to-reorder is enabled only when the visible rows ARE the
320
+ // canonical sort. Filters / search / non-default sort / pagination
321
+ // beyond page 1 all break that invariant; we render the grip column
322
+ // greyed-out instead of letting the user reorder a slice that won't
323
+ // round-trip cleanly. `reorderableColumn` is set server-side when
324
+ // `Table.reorderable()` opts in.
325
+ const sortMatchesReorder = currentSort?.column === reorderableColumn &&
326
+ currentSort?.direction === 'asc';
327
+ const filtersActive = Object.keys(activeFilters).length > 0;
328
+ const searchActive = typeof search === 'string' && search !== '';
329
+ const reorderEnabled = reorderableColumn !== undefined &&
330
+ reorderUrl !== undefined &&
331
+ sortMatchesReorder &&
332
+ !filtersActive &&
333
+ !searchActive &&
334
+ currentPage === 1;
335
+ const reorderColumnVisible = reorderableColumn !== undefined;
336
+ // ── Reorder DnD state + handlers ──────────────────────
337
+ const { dragId, dropAt, onDragStart: onRowDragStart, onDragOver: onRowDragOver, onDrop: onRowDrop, onDragEnd: onRowDragEnd, } = useRowReorderDnd({
338
+ enabled: reorderEnabled,
339
+ onDrop: async (fromId, at) => {
340
+ if (!reorderUrl)
341
+ return;
342
+ const fromIdx = visibleIds.findIndex(id => id === fromId);
343
+ if (fromIdx < 0)
344
+ return;
345
+ const target = at > fromIdx ? at - 1 : at;
346
+ if (target === fromIdx)
347
+ return;
348
+ const reordered = rows.slice();
349
+ const moved = reordered.splice(fromIdx, 1)[0];
350
+ reordered.splice(target, 0, moved);
351
+ const newIds = reordered.map((row, i) => rowId(row, i));
352
+ const previousLocal = reorderRowsLocal;
353
+ setReorderRowsLocal(reordered);
354
+ try {
355
+ const res = await fetch(reorderUrl, {
356
+ method: 'POST',
357
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
358
+ body: JSON.stringify({ ids: newIds }),
359
+ });
360
+ if (!res.ok)
361
+ throw new Error(`Reorder failed (${res.status})`);
362
+ }
363
+ catch (err) {
364
+ // Roll back to server order. The toast surfaces the failure;
365
+ // next page render fetches the persisted column.
366
+ setReorderRowsLocal(previousLocal);
367
+ notify({
368
+ type: 'error',
369
+ title: 'Could not save new order',
370
+ body: err instanceof Error ? err.message : 'Reorder failed',
371
+ });
372
+ }
373
+ },
374
+ });
375
+ const totalCols = visibleColumns.length
376
+ + (hasBulkActions ? 1 : 0)
377
+ + (hasRowActions ? 1 : 0)
378
+ + (reorderColumnVisible ? 1 : 0);
379
+ // Top-bar chrome (heading / description / striped / emptyState).
380
+ const tableHeading = el['heading'];
381
+ const tableDescription = el['description'];
382
+ const striped = Boolean(el['striped']);
383
+ const emptyState = el['emptyState'];
384
+ const filteredEmptyState = el['filteredEmptyState'];
385
+ const hasFilterOrSearch = (search !== undefined && search !== '') ||
386
+ Object.keys(activeFilters).length > 0;
387
+ // Distinct copy when a query / filter is active. Falls back to
388
+ // `emptyState` when `filteredEmptyState` is not set, preserving the
389
+ // pre-2026-05-04 behavior for tables that haven't opted in.
390
+ const activeEmpty = (hasFilterOrSearch && filteredEmptyState) ? filteredEmptyState : emptyState;
391
+ const EmptyIcon = activeEmpty?.icon ? (resolveIcon(activeEmpty.icon) ?? InboxIcon) : InboxIcon;
392
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [(tableHeading || tableDescription) && (_jsxs("div", { className: "flex flex-col gap-1", children: [tableHeading && _jsx("h2", { className: "text-lg font-semibold", children: tableHeading }), tableDescription && _jsx("p", { className: "text-sm text-muted-foreground", children: tableDescription })] })), showHeaderBar && (_jsxs("div", { className: "flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between", children: [(searchable || showFiltersInToolbar || hasGroupPicker || hasSortPicker || hasColumnsToggle) ? (_jsxs("div", { className: "flex items-center gap-2", children: [searchable && (_jsxs("form", { method: "get", action: currentPath || undefined, className: "flex items-end gap-2", children: [_jsx(SearchFormHiddenInputs, { prefix: queryPrefix }), _jsx(Input, { type: "search", name: prefixK(queryPrefix, 'search'), defaultValue: search ?? '', placeholder: "Search\u2026", className: "h-9 w-64" }), _jsx("button", { type: "submit", className: "sr-only", tabIndex: -1, "aria-hidden": "true", children: "Apply" })] })), hasFilters && filtersInModal && (_jsx(FilterPopover, { filters: filters, prefix: queryPrefix, renderFormChild: renderFormChild })), hasFilters && filtersCollapsible && (_jsx(FilterStripToggle, { filters: filters, open: filtersOpen, onToggle: toggleFiltersOpen })), hasGroupPicker && (_jsx(TableGroupPicker, { options: groupOptions, active: defaultGroup, onChange: (value) => {
393
+ // value === '' → explicit "None" (clears defaultGroup);
394
+ // value !== '' → switch to that column.
395
+ const href = buildTableQuery(state, { page: 1, group: value }, currentPath, activeFilters, queryPrefix);
396
+ navigate(href);
397
+ } })), hasSortPicker && (_jsx(SortByPicker, { columns: sortableColumns, active: currentSort, onChange: (column, direction) => {
398
+ const href = buildTableQuery(state, { sort: { column, direction }, page: 1 }, currentPath, activeFilters, queryPrefix);
399
+ navigate(href);
400
+ } })), toggleableColumns.length > 0 && (_jsx(ColumnsToggleDropdown, { columns: toggleableColumns, hidden: hiddenColumns, onToggle: toggleColumnHidden }))] })) : _jsx("span", {}), headerActions.length > 0 && (_jsx("div", { className: "flex items-center gap-2", children: headerActions.map((a, i) => renderActionLike(a, i)) }))] })), hasFilters && filtersInModal && _jsx(ActiveFiltersBar, { filters: filters, prefix: queryPrefix }), hasFilters && filtersAbove && filtersOpen && (_jsx(FilterStrip, { filters: filters, prefix: queryPrefix, renderFormChild: renderFormChild })), activeGroupKey !== undefined && (_jsx(ActiveGroupKeyChip, { label: groupColumnLabel ?? defaultGroup ?? '', value: activeGroupKey, displayValue: (() => {
401
+ // Prefer a row-resolved `_groupTitle` (server stamped via
402
+ // `getTitleFromRecordUsing`) so the chip reads the same as
403
+ // a banded heading. Falls back to the raw bucket key when
404
+ // no row matched — empty drilled-in pages still show what
405
+ // they're drilled into.
406
+ for (const r of rows) {
407
+ const obj = r;
408
+ if (String(obj['_groupValue'] ?? '') !== activeGroupKey)
409
+ continue;
410
+ const t = obj['_groupTitle'];
411
+ if (typeof t === 'string' && t !== '')
412
+ return t;
413
+ break;
414
+ }
415
+ return activeGroupKey;
416
+ })(), clearHref: drillOutHref(), navigate: navigate })), hasBulkActions && someChecked && (_jsxs("div", { className: "flex items-center justify-between gap-2 rounded-md border bg-muted/40 px-3 py-2 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [selected.size, " selected"] }), _jsxs("div", { className: "flex items-center gap-2", children: [bulkActions.map((a, i) => renderActionLike(a, i, { ids: Array.from(selected) })), _jsx("button", { type: "button", onClick: () => setSelected(new Set()), className: "text-xs text-muted-foreground hover:text-foreground", children: "Clear" })] })] })), isCardsLayout ? (_jsx(CardsLayoutBody, { rows: rows, visibleIds: visibleIds, selected: selected, toggleRow: toggleRow, hasBulkActions: hasBulkActions, hasRowActions: hasRowActions, rowActions: rowActions, hasRecordUrl: hasRecordUrl, hasRecordClasses: hasRecordClasses, activeEmpty: activeEmpty, EmptyIcon: EmptyIcon, hasFilterOrSearch: hasFilterOrSearch, defaultGroup: defaultGroup, groupColumnLabel: groupColumnLabel, groupCollapsible: groupCollapsible, collapsedGroups: collapsedGroups, toggleGroupCollapsed: toggleGroupCollapsed, cardsPerRow: cardsPerRow, navigate: navigate, groupHeadingScopable: groupHeadingScopable, buildGroupKeyHref: buildGroupKeyHref, renderElement: renderElement, renderRowActions: (id, recordObj, actions) => renderRowActions(id, recordObj, actions, renderActionLike) })) : (_jsx("div", { className: "rounded-xl border bg-card overflow-hidden", children: _jsxs(DataTable, { children: [_jsx(TableHeader, { className: "bg-muted", children: _jsxs(TableRow, { children: [reorderColumnVisible && (_jsx(TableHead, { className: "w-9 px-2", "aria-label": "Reorder" })), hasBulkActions && (_jsx(TableHead, { className: "w-9 px-3", children: _jsx(Checkbox, { "aria-label": "Select all rows", checked: allChecked, onCheckedChange: () => toggleAll() }) })), visibleColumns.map((col, i) => {
417
+ const name = String(col['name'] ?? '');
418
+ const label = String(col['label'] ?? name);
419
+ const sortable = Boolean(col['sortable']);
420
+ const isActive = currentSort?.column === name;
421
+ if (!sortable) {
422
+ return (_jsx(TableHead, { className: "text-xs uppercase tracking-wider", children: label }, i));
423
+ }
424
+ const next = nextSortDir(currentSort, name);
425
+ const href = buildTableQuery(state, { sort: next, page: 1 }, currentPath, activeFilters, queryPrefix);
426
+ return (_jsx(TableHead, { className: "text-xs uppercase tracking-wider", children: _jsxs("a", { href: href, className: "inline-flex items-center gap-1 hover:text-foreground", children: [label, _jsx("span", { className: "text-muted-foreground/70", children: isActive ? (currentSort.direction === 'asc' ? '↑' : '↓') : '↕' })] }) }, i));
427
+ }), hasRowActions && (_jsx(TableHead, { className: "w-px text-right text-xs uppercase tracking-wider", children: _jsx("span", { className: "sr-only", children: "Actions" }) }))] }) }), _jsx(TableBody, { children: rows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: totalCols, className: "py-12 text-center", children: _jsxs("div", { className: "flex flex-col items-center gap-2 text-muted-foreground", children: [_jsx(EmptyIcon, { className: "size-8 opacity-60" }), _jsx("p", { className: "text-base font-medium text-foreground", children: activeEmpty?.heading
428
+ ?? (hasFilterOrSearch ? 'No matching records' : 'No records yet') }), (activeEmpty?.description ||
429
+ (hasFilterOrSearch && !activeEmpty?.description)) && (_jsx("p", { className: "text-sm", children: activeEmpty?.description
430
+ ?? 'Try clearing filters or adjusting your search.' }))] }) }) })) : rows.map((row, ri) => {
431
+ const id = visibleIds[ri];
432
+ const recordObj = row;
433
+ const isSelected = selected.has(id);
434
+ const stripedClass = striped && ri % 2 === 1 ? 'bg-muted/30' : '';
435
+ // Group banding — emit a heading row whenever `_groupValue`
436
+ // differs from the previous row. The first row in any group
437
+ // gets the heading; rows within keep their normal chrome.
438
+ const groupValue = defaultGroup
439
+ ? String(recordObj['_groupValue'] ?? '')
440
+ : undefined;
441
+ const groupTitle = defaultGroup
442
+ ? recordObj['_groupTitle']
443
+ : undefined;
444
+ const groupDescription = defaultGroup
445
+ ? recordObj['_groupDescription']
446
+ : undefined;
447
+ const prevGroupValue = defaultGroup && ri > 0
448
+ ? String((rows[ri - 1]['_groupValue'] ?? ''))
449
+ : undefined;
450
+ const showGroupHeader = defaultGroup !== undefined && groupValue !== prevGroupValue;
451
+ // Hide data rows whose group is collapsed. The heading row
452
+ // for that group still renders (so the user can re-expand).
453
+ const isInCollapsedGroup = groupCollapsible && groupValue !== undefined && collapsedGroups[groupValue] === true;
454
+ // Filament-style per-cell linking. Each data cell wraps
455
+ // its content in a real `<a href>` when the column resolves
456
+ // to a record URL — column override (`Column.recordUrl(fn)`)
457
+ // beats inheritance from the table (`Table.recordUrl(fn)`),
458
+ // and `Column.recordUrl(false)` opts out. Action and bulk
459
+ // cells are never wrapped, so clicks there fire only their
460
+ // own handlers — no event-bubbling gymnastics.
461
+ const tableUrl = hasRecordUrl ? recordObj['_recordUrl'] : undefined;
462
+ const colUrls = recordObj['_columnRecordUrls'] ?? {};
463
+ const rowHasAnyLink = tableUrl !== undefined || Object.keys(colUrls).length > 0;
464
+ const customRowClasses = hasRecordClasses
465
+ ? recordObj['_recordClasses'] ?? ''
466
+ : '';
467
+ const rowClassName = [stripedClass, rowHasAnyLink ? 'cursor-pointer' : '', customRowClasses]
468
+ .filter(Boolean)
469
+ .join(' ')
470
+ .trim();
471
+ return (_jsxs(React.Fragment, { children: [showGroupHeader && (_jsx(TableRow, { className: "bg-muted/40 hover:bg-muted/40", children: _jsx(TableCell, { colSpan: totalCols, className: "px-3 py-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: (() => {
472
+ const drillable = groupHeadingScopable
473
+ && groupValue !== undefined
474
+ && groupValue !== '';
475
+ const headingText = (_jsx(GroupHeaderText, { label: groupColumnLabel, value: groupValue, title: groupTitle, description: groupDescription }));
476
+ const headingNode = drillable
477
+ ? _jsx(GroupHeadingLink, { href: buildGroupKeyHref(groupValue), navigate: navigate, children: headingText })
478
+ : headingText;
479
+ if (groupCollapsible) {
480
+ return (_jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx("button", { type: "button", className: "inline-flex items-center", onClick: () => toggleGroupCollapsed(groupValue), "aria-expanded": !isInCollapsedGroup, "aria-label": isInCollapsedGroup ? 'Expand group' : 'Collapse group', children: _jsx(ChevronDownIcon, { className: [
481
+ 'size-4 transition-transform',
482
+ isInCollapsedGroup ? '-rotate-90' : '',
483
+ ].filter(Boolean).join(' ') }) }), headingNode] }));
484
+ }
485
+ return headingNode;
486
+ })() }) }, `group-${id}`)), isInCollapsedGroup ? null : (_jsxs(TableRow, { "data-state": isSelected ? 'selected' : undefined, className: [
487
+ rowClassName,
488
+ dragId === id ? 'opacity-50' : '',
489
+ dropAt === ri && dragId !== null ? 'border-t-2 border-t-primary' : '',
490
+ ].filter(Boolean).join(' ') || undefined, draggable: reorderEnabled || undefined, onDragStart: reorderEnabled ? onRowDragStart(id) : undefined, onDragOver: reorderEnabled ? onRowDragOver(ri) : undefined, onDrop: reorderEnabled ? onRowDrop : undefined, onDragEnd: reorderEnabled ? onRowDragEnd : undefined, children: [reorderColumnVisible && (_jsx(TableCell, { className: "w-9 px-2", children: _jsx("span", { "aria-label": reorderEnabled ? 'Drag to reorder' : 'Reorder paused — clear filters and sort to enable', className: reorderEnabled
491
+ ? 'inline-flex cursor-grab text-muted-foreground hover:text-foreground active:cursor-grabbing'
492
+ : 'inline-flex cursor-not-allowed text-muted-foreground/40', children: _jsx(GripVerticalIcon, { className: "size-4" }) }) })), hasBulkActions && (_jsx(TableCell, { className: "w-9 px-3", children: _jsx(Checkbox, { "aria-label": `Select row ${id}`, checked: isSelected, onCheckedChange: () => toggleRow(id) }) })), visibleColumns.map((col, ci) => {
493
+ const name = String(col['name'] ?? '');
494
+ const value = recordObj[name];
495
+ const align = col['alignment'] === 'center' ? 'text-center'
496
+ : col['alignment'] === 'end' ? 'text-right'
497
+ : 'text-left';
498
+ const widthStyle = col['width']
499
+ ? { width: String(col['width']) }
500
+ : undefined;
501
+ // Inline-edit cells take priority over read-only chrome.
502
+ // `_cellEditable[name]` is set per row by `loadTableRecords`
503
+ // only when `R.canEdit(user, row)` passed; the URL was
504
+ // stamped by `tagCellEditUrls` immediately after.
505
+ const editableMap = recordObj['_cellEditable'];
506
+ const editUrlMap = recordObj['_cellEditUrls'];
507
+ const cellDisabledMap = recordObj['_cellDisabled'];
508
+ const editUrl = editableMap?.[name] ? editUrlMap?.[name] : undefined;
509
+ const EditableComp = editUrl !== undefined
510
+ ? pickEditableCell(String(col['columnType'] ?? 'text'))
511
+ : null;
512
+ if (EditableComp && editUrl !== undefined) {
513
+ const cellDisabled = col['disabled'] === true || cellDisabledMap?.[name] === true;
514
+ const cellSelectOptionsMap = recordObj['_cellSelectOptions'];
515
+ const rowOptions = cellSelectOptionsMap?.[name];
516
+ return (_jsx(TableCell, { className: `text-sm text-foreground ${align} p-0`, style: widthStyle, children: _jsx(EditableComp, { url: editUrl, col: col, value: value, disabled: cellDisabled, ...(rowOptions ? { rowOptions } : {}) }) }, ci));
517
+ }
518
+ const cellContent = formatCell(value, col, recordObj);
519
+ const colUrl = resolveColumnUrl(col, tableUrl, colUrls);
520
+ return (_jsx(TableCell, { className: `text-sm text-foreground ${align} p-0`, style: widthStyle, children: colUrl !== undefined
521
+ ? _jsx(RecordCellLink, { href: colUrl, navigate: navigate, children: cellContent })
522
+ : _jsx("div", { className: "px-2 py-2", children: cellContent }) }, ci));
523
+ }), hasRowActions && (_jsx(TableCell, { className: "w-px text-right", children: renderRowActions(id, recordObj, rowActions, renderActionLike) }))] })), (() => {
524
+ if (!groupSummaries)
525
+ return null;
526
+ if (groupValue === undefined)
527
+ return null;
528
+ if (isInCollapsedGroup)
529
+ return null;
530
+ const isLastInGroup = ri === rows.length - 1
531
+ || String((rows[ri + 1]['_groupValue'] ?? '')) !== groupValue;
532
+ if (!isLastInGroup)
533
+ return null;
534
+ const perCol = groupSummaries[groupValue];
535
+ if (!perCol || Object.keys(perCol).length === 0)
536
+ return null;
537
+ return (_jsxs(TableRow, { className: "bg-muted/20 hover:bg-muted/20", children: [reorderColumnVisible && _jsx(TableCell, {}), hasBulkActions && _jsx(TableCell, {}), visibleColumns.map((col, ci) => {
538
+ const name = String(col['name'] ?? '');
539
+ const align = col['alignment'] === 'center' ? 'text-center'
540
+ : col['alignment'] === 'end' ? 'text-right'
541
+ : 'text-left';
542
+ const items = perCol[name];
543
+ return (_jsx(TableCell, { className: `text-xs font-medium ${align} px-2 py-1.5`, children: items?.map((s, i) => (_jsxs("div", { className: "leading-tight", children: [s.label && _jsxs("span", { className: "text-muted-foreground", children: [s.label, ": "] }), _jsx("span", { children: s.value })] }, i))) }, ci));
544
+ }), hasRowActions && _jsx(TableCell, {})] }, `group-summary-${id}`));
545
+ })()] }, id));
546
+ }) }), summaries && Object.keys(summaries).length > 0 && (_jsx(TableFooter, { children: _jsxs(TableRow, { children: [reorderColumnVisible && _jsx(TableCell, {}), hasBulkActions && _jsx(TableCell, {}), visibleColumns.map((col, ci) => {
547
+ const name = String(col['name'] ?? '');
548
+ const align = col['alignment'] === 'center' ? 'text-center'
549
+ : col['alignment'] === 'end' ? 'text-right'
550
+ : 'text-left';
551
+ const items = summaries[name];
552
+ return (_jsx(TableCell, { className: `text-sm font-medium ${align}`, children: items?.map((s, i) => (_jsxs("div", { className: "leading-tight", children: [s.label && _jsxs("span", { className: "text-muted-foreground", children: [s.label, ": "] }), _jsx("span", { children: s.value })] }, i))) }, ci));
553
+ }), hasRowActions && _jsx(TableCell, {})] }) }))] }) })), showPagination && (_jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsxs("span", { children: ["Page ", currentPage, " of ", totalPages, total > 0 ? ` · ${total} record${total === 1 ? '' : 's'}` : ''] }), _jsxs("div", { className: "flex items-center gap-2", children: [currentPage > 1 && (_jsx("a", { href: buildTableQuery(state, { page: currentPage - 1 }, currentPath, activeFilters, queryPrefix), className: "rounded-md border px-3 py-1 text-xs hover:bg-muted", children: "\u2190 Previous" })), currentPage < totalPages && (_jsx("a", { href: buildTableQuery(state, { page: currentPage + 1 }, currentPath, activeFilters, queryPrefix), className: "rounded-md border px-3 py-1 text-xs hover:bg-muted", children: "Next \u2192" }))] })] })), hasFilters && filtersBelow && (_jsx(FilterStrip, { filters: filters, prefix: queryPrefix, renderFormChild: renderFormChild }))] }));
554
+ }
555
+ //# sourceMappingURL=TableRendererBody.js.map