@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,3458 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { getFieldRenderer } from './registry.js';
4
+ import { FormStateProvider, useFormState, FormIdContext } from './FormStateContext.js';
5
+ import { Checkbox } from './ui/checkbox.js';
6
+ import { Input } from './ui/input.js';
7
+ import { Popover, PopoverTrigger, PopoverContent } from './ui/popover.js';
8
+ import { FieldShell } from './fields/FieldShell.js';
9
+ import { TextLikeInput } from './fields/TextLikeInput.js';
10
+ import { SelectFieldInput } from './fields/SelectFieldInput.js';
11
+ import { ToggleFieldInput } from './fields/ToggleFieldInput.js';
12
+ import { DateFieldInput } from './fields/DateFieldInput.js';
13
+ import { HiddenInput } from './fields/HiddenInput.js';
14
+ import { CheckboxInput } from './fields/CheckboxInput.js';
15
+ import { RadioInput } from './fields/RadioInput.js';
16
+ import { ToggleButtonsInput } from './fields/ToggleButtonsInput.js';
17
+ import { CheckboxListInput } from './fields/CheckboxListInput.js';
18
+ import { SliderInput } from './fields/SliderInput.js';
19
+ import { ColorInput } from './fields/ColorInput.js';
20
+ import { DateTimeInput } from './fields/DateTimeInput.js';
21
+ import { KeyValueInput } from './fields/KeyValueInput.js';
22
+ import { TagsInput } from './fields/TagsInput.js';
23
+ import { FileUploadInput } from './fields/FileUploadInput.js';
24
+ import { MarkdownInput } from './fields/MarkdownInput.js';
25
+ import { RepeaterInput } from './fields/RepeaterInput.js';
26
+ import { BuilderInput } from './fields/BuilderInput.js';
27
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from './ui/dialog.js';
28
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from './ui/tabs.js';
29
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from './ui/select.js';
30
+ import { Table as DataTable, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, } from './ui/table.js';
31
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from './ui/dropdown-menu.js';
32
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from './ui/tooltip.js';
33
+ import { FilterIcon, CircleIcon, InboxIcon, GripVerticalIcon, ChevronDownIcon, CopyIcon, CheckIcon, XIcon, } from 'lucide-react';
34
+ import { useNavigate } from './navigate.js';
35
+ import { parseDateRangeValue, encodeDateRangeValue, } from '../filters/DateRangeFilter.js';
36
+ import { parseMultiSelectValue, encodeMultiSelectValue, } from '../filters/MultiSelectFilter.js';
37
+ import { encodeFormFilterValue } from '../filters/FormFilter.js';
38
+ import { parseQueryBuilderValue, encodeQueryBuilderValue, isQueryBuilderTree, } from '../filters/QueryBuilderFilter.js';
39
+ import { useIconFor } from './icon-context.js';
40
+ import { useToast } from './Toaster.js';
41
+ import { getIcon } from '../icons/registry.js';
42
+ import { pickEditableCell } from './cells/EditableCell.js';
43
+ import { WidgetDataProvider } from './WidgetDataContext.js';
44
+ import { StatsOverviewRenderer } from './widgets/StatsOverviewRenderer.js';
45
+ import { TableWidgetRenderer } from './widgets/TableWidgetRenderer.js';
46
+ import { ViewRenderer } from './widgets/ViewRenderer.js';
47
+ import { getEntryComponent } from '../entries/registry.js';
48
+ import { getWidgetRenderer } from './widgetRegistry.js';
49
+ function resolveIcon(name) {
50
+ if (!name)
51
+ return undefined;
52
+ return getIcon(name);
53
+ }
54
+ const alertStyles = {
55
+ info: 'border-blue-200 bg-blue-50 text-blue-800 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200',
56
+ warning: 'border-amber-200 bg-amber-50 text-amber-800 dark:border-amber-800 dark:bg-amber-950 dark:text-amber-200',
57
+ success: 'border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200',
58
+ danger: 'border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-200',
59
+ };
60
+ export function FormFields({ elements, values }) {
61
+ return (_jsx(_Fragment, { children: elements.map((el, i) => {
62
+ if (el['type'] !== 'field')
63
+ return null;
64
+ const name = String(el['name'] ?? '');
65
+ const merged = values && name in values
66
+ ? { ...el, defaultValue: values[name] }
67
+ : el;
68
+ return renderField(merged, i);
69
+ }) }));
70
+ }
71
+ // ─── Field rendering ────────────────────────────────────────
72
+ //
73
+ // Each input lives in its own file under `react/fields/`. This file
74
+ // stays a thin dispatcher: parse meta → pick component → wrap in
75
+ // `<FieldShell>`.
76
+ function renderField(el, index) {
77
+ const fieldType = String(el['fieldType'] ?? 'text');
78
+ const name = String(el['name'] ?? '');
79
+ const label = String(el['label'] ?? name);
80
+ const required = Boolean(el['required']);
81
+ const disabled = Boolean(el['disabled']);
82
+ const placeholder = el['placeholder'] ? String(el['placeholder']) : undefined;
83
+ const defaultValue = el['defaultValue'];
84
+ const defaultStr = defaultValue !== undefined && defaultValue !== null ? String(defaultValue) : undefined;
85
+ // Hidden fields render bare — no label, no shell, no chrome. Bail
86
+ // before the renderField switch + FieldShell wrap.
87
+ if (fieldType === 'hidden') {
88
+ return _jsx(HiddenInput, { name: name, defaultValue: defaultValue }, index);
89
+ }
90
+ const autofocus = el['autofocus'] === true;
91
+ const extraInput = el['extraInputAttributes'];
92
+ const common = {
93
+ id: name,
94
+ name,
95
+ disabled,
96
+ placeholder,
97
+ required,
98
+ ...(defaultStr !== undefined ? { defaultValue: defaultStr } : {}),
99
+ ...(autofocus ? { autoFocus: true } : {}),
100
+ ...(extraInput ?? {}),
101
+ };
102
+ // External packages (e.g. @pilotiq/tiptap) register custom renderers
103
+ // for non-built-in fieldTypes. The registry wins over the built-in
104
+ // switch so consumers can override built-ins too if they want.
105
+ const Custom = getFieldRenderer(fieldType);
106
+ if (Custom) {
107
+ return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, children: _jsx(Custom, { el: el, name: name, defaultValue: defaultValue, required: required, disabled: disabled, placeholder: placeholder }) }, index));
108
+ }
109
+ const input = renderFieldInput(fieldType, el, name, defaultValue, defaultStr, common, disabled, required, placeholder);
110
+ return (_jsx(FieldShell, { el: el, name: name, label: label, required: required, children: input }, index));
111
+ }
112
+ function renderFieldInput(fieldType, el, name, defaultValue, defaultStr, common, disabled, required, placeholder) {
113
+ switch (fieldType) {
114
+ case 'textarea': {
115
+ const autosize = el['autosize'] === true;
116
+ const cols = typeof el['cols'] === 'number' ? Number(el['cols']) : undefined;
117
+ const extra = {};
118
+ // `field-sizing-content` on the Textarea component already grows
119
+ // the box with content; `autosize()` just unsets the explicit
120
+ // `rows` so the browser doesn't reserve a fixed minimum height.
121
+ if (!autosize)
122
+ extra['rows'] = Number(el['rows']) || 4;
123
+ if (cols !== undefined)
124
+ extra['cols'] = cols;
125
+ if (el['disableGrammarly'] === true) {
126
+ extra['data-gramm'] = 'false';
127
+ extra['data-gramm_editor'] = 'false';
128
+ extra['data-enable-grammarly'] = 'false';
129
+ }
130
+ return (_jsx(TextLikeInput, { el: el, name: name, common: common, type: "text", extraProps: extra, multiline: true }));
131
+ }
132
+ case 'select': {
133
+ const options = el['options'] ?? [];
134
+ const createOption = el['createOption'];
135
+ const fieldLabel = String(el['label'] ?? name);
136
+ return (_jsx(SelectFieldInput, { name: name, defaultValue: defaultStr, disabled: disabled, required: required, placeholder: placeholder, options: options, fieldLabel: fieldLabel, ...(createOption ? { createOption } : {}) }));
137
+ }
138
+ case 'toggle': {
139
+ const initialChecked = defaultValue === true || defaultValue === 'true' || defaultValue === 1 || defaultValue === '1';
140
+ return _jsx(ToggleFieldInput, { name: name, defaultChecked: initialChecked, disabled: disabled });
141
+ }
142
+ case 'checkbox': {
143
+ const initialChecked = defaultValue === true || defaultValue === 'true' || defaultValue === 1 || defaultValue === '1';
144
+ return _jsx(CheckboxInput, { name: name, defaultChecked: initialChecked, disabled: disabled });
145
+ }
146
+ case 'radio': {
147
+ const options = el['options'] ?? [];
148
+ const inline = Boolean(el['inline']);
149
+ return (_jsx(RadioInput, { name: name, defaultValue: defaultStr, disabled: disabled, options: options, inline: inline }));
150
+ }
151
+ case 'toggleButtons': {
152
+ const options = el['options'] ?? [];
153
+ return (_jsx(ToggleButtonsInput, { name: name, defaultValue: defaultStr, disabled: disabled, options: options }));
154
+ }
155
+ case 'checkboxList': {
156
+ const options = el['options'] ?? [];
157
+ const columns = Number(el['columns']) || 1;
158
+ return (_jsx(CheckboxListInput, { name: name, defaultValue: defaultValue, disabled: disabled, options: options, columns: columns }));
159
+ }
160
+ case 'slider': {
161
+ return (_jsx(SliderInput, { name: name, defaultValue: defaultValue, disabled: disabled, min: Number(el['min']) || 0, max: Number(el['max']) || 100, step: Number(el['step']) || 1, showValue: Boolean(el['showValue']) }));
162
+ }
163
+ case 'color': {
164
+ return (_jsx(ColorInput, { name: name, defaultValue: defaultValue, disabled: disabled }));
165
+ }
166
+ case 'keyValue': {
167
+ return (_jsx(KeyValueInput, { name: name, defaultValue: defaultValue, disabled: disabled, keyLabel: String(el['keyLabel'] ?? 'Key'), valueLabel: String(el['valueLabel'] ?? 'Value'), addLabel: String(el['addLabel'] ?? 'Add row'), reorderable: Boolean(el['reorderable']) }));
168
+ }
169
+ case 'tagsInput': {
170
+ const suggestions = el['suggestions'] ?? [];
171
+ // separator: omitted → ',' (default); explicit null → null (disabled).
172
+ const separator = 'separator' in el
173
+ ? el['separator']
174
+ : ',';
175
+ const splitKeys = el['splitKeys'] ?? ['Enter'];
176
+ const maxTags = typeof el['maxTags'] === 'number' ? el['maxTags'] : null;
177
+ const reorderable = Boolean(el['reorderable']);
178
+ return (_jsx(TagsInput, { name: name, defaultValue: defaultValue, disabled: disabled, placeholder: placeholder, suggestions: suggestions, separator: separator, splitKeys: splitKeys, maxTags: maxTags, reorderable: reorderable }));
179
+ }
180
+ case 'fileUpload': {
181
+ return (_jsx(FileUploadInput, { name: name, defaultValue: defaultValue, disabled: disabled, accept: el['accept'], maxSize: typeof el['maxSize'] === 'number' ? el['maxSize'] : undefined, multiple: Boolean(el['multiple']), preview: el['preview'] !== false, directory: typeof el['directory'] === 'string' ? el['directory'] : undefined, uploadUrl: typeof el['uploadUrl'] === 'string' ? el['uploadUrl'] : undefined }));
182
+ }
183
+ case 'markdown': {
184
+ const toolbarButtons = el['toolbarButtons'] ?? [];
185
+ return (_jsx(MarkdownInput, { name: name, defaultValue: defaultValue, disabled: disabled, placeholder: placeholder, toolbarButtons: toolbarButtons, minHeight: typeof el['minHeight'] === 'string' ? el['minHeight'] : undefined, maxHeight: typeof el['maxHeight'] === 'string' ? el['maxHeight'] : undefined, fileAttachmentsDirectory: typeof el['fileAttachmentsDirectory'] === 'string' ? el['fileAttachmentsDirectory'] : undefined, fileAttachmentsVisibility: typeof el['fileAttachmentsVisibility'] === 'string' ? el['fileAttachmentsVisibility'] : undefined, uploadUrl: typeof el['uploadUrl'] === 'string' ? el['uploadUrl'] : undefined }));
186
+ }
187
+ case 'repeater':
188
+ return _jsx(RepeaterInput, { el: el, name: name, disabled: disabled });
189
+ case 'builder':
190
+ return _jsx(BuilderInput, { el: el, name: name, disabled: disabled });
191
+ case 'dateTime': {
192
+ // Normalize various input shapes to YYYY-MM-DDTHH:mm.
193
+ let local;
194
+ if (defaultValue instanceof Date) {
195
+ local = isNaN(defaultValue.getTime())
196
+ ? undefined
197
+ : defaultValue.toISOString().slice(0, 16);
198
+ }
199
+ else if (typeof defaultValue === 'string' && defaultValue) {
200
+ const parsed = new Date(defaultValue);
201
+ local = isNaN(parsed.getTime()) ? undefined : parsed.toISOString().slice(0, 16);
202
+ }
203
+ return (_jsx(DateTimeInput, { name: name, defaultValue: local, disabled: disabled, placeholder: placeholder }));
204
+ }
205
+ case 'number': {
206
+ const numProps = {};
207
+ if (el['min'] !== undefined)
208
+ numProps['min'] = Number(el['min']);
209
+ if (el['max'] !== undefined)
210
+ numProps['max'] = Number(el['max']);
211
+ if (el['step'] !== undefined)
212
+ numProps['step'] = Number(el['step']);
213
+ return (_jsx(TextLikeInput, { el: el, name: name, common: common, type: "number", extraProps: numProps, multiline: false }));
214
+ }
215
+ case 'email':
216
+ return (_jsx(TextLikeInput, { el: el, name: name, common: common, type: "email", extraProps: {}, multiline: false }));
217
+ case 'date': {
218
+ // SSR may hand us a JS Date object directly; SPA JSON nav arrives as
219
+ // an ISO string. Normalize both into a `YYYY-MM-DD` slice — naive
220
+ // string slicing on `Date.toString()` ("Mon Apr 27 2026 ...") gives
221
+ // garbage when re-parsed, so handle the Date branch explicitly.
222
+ let iso;
223
+ if (defaultValue instanceof Date) {
224
+ iso = isNaN(defaultValue.getTime())
225
+ ? undefined
226
+ : defaultValue.toISOString().slice(0, 10);
227
+ }
228
+ else if (typeof defaultValue === 'string' && defaultValue) {
229
+ const parsed = new Date(defaultValue);
230
+ iso = isNaN(parsed.getTime())
231
+ ? undefined
232
+ : parsed.toISOString().slice(0, 10);
233
+ }
234
+ return (_jsx(DateFieldInput, { name: name, defaultValue: iso, disabled: disabled, placeholder: placeholder }));
235
+ }
236
+ case 'slug':
237
+ case 'text':
238
+ default: {
239
+ const textExtra = {};
240
+ if (el['maxLength'] !== undefined)
241
+ textExtra['maxLength'] = Number(el['maxLength']);
242
+ return (_jsx(TextLikeInput, { el: el, name: name, common: common, type: "text", extraProps: textExtra, multiline: false }));
243
+ }
244
+ }
245
+ }
246
+ /** Drain `notifications[]` from a JSON response into `useToast().notify`. */
247
+ function dispatchNotifications(data, notify) {
248
+ const notifs = data.notifications;
249
+ if (!notifs || notifs.length === 0)
250
+ return;
251
+ for (const n of notifs)
252
+ notify(n);
253
+ }
254
+ /**
255
+ * Fetch + JSON dispatch for form-method actions (Delete-style — no
256
+ * server-rendered <form>, no 303 redirect, no full page reload). Sends
257
+ * `_method` as a body field so Hono's POST handler dispatches the
258
+ * intended verb. On success: drain notifications, SPA-navigate to the
259
+ * server-supplied redirect (or stay on current path if none).
260
+ *
261
+ * Failure modes:
262
+ * - 4xx/5xx with `{ error }`: surfaced as an error toast.
263
+ * - Network errors: error toast with the exception message.
264
+ */
265
+ async function dispatchMethodAction(url, method, navigate, notify) {
266
+ try {
267
+ const fd = new FormData();
268
+ if (method !== 'post')
269
+ fd.append('_method', method);
270
+ const res = await fetch(url, {
271
+ method: 'POST',
272
+ headers: { 'Accept': 'application/json' },
273
+ body: fd,
274
+ });
275
+ const data = await res.json().catch(() => ({}));
276
+ if (!res.ok) {
277
+ const message = String(data.error ?? `Request failed (${res.status})`);
278
+ notify({ type: 'error', title: 'Action failed', body: message });
279
+ return;
280
+ }
281
+ dispatchNotifications(data, notify);
282
+ const redirect = String(data.redirect ?? '');
283
+ if (redirect)
284
+ navigate(redirect);
285
+ else if (typeof window !== 'undefined')
286
+ navigate(window.location.pathname + window.location.search);
287
+ }
288
+ catch (err) {
289
+ notify({ type: 'error', title: 'Action failed', body: err instanceof Error ? err.message : String(err) });
290
+ }
291
+ }
292
+ /**
293
+ * Fetch + JSON dispatch for handler-style actions (no schema, no modal,
294
+ * just a button). Sends `ids[]` plus arbitrary `values` fields. Server
295
+ * returns `{ ok, redirect, notifications }` (or `{ ok: false, error }` on
296
+ * failure). On success: drain notifications, SPA-navigate; on failure:
297
+ * surface the error as a toast. No full page reload in any case.
298
+ */
299
+ export async function dispatchHandlerAction(url, ids, navigate, notify, values = {}, formSnapshot) {
300
+ try {
301
+ // When `formSnapshot` is set (Repeater / Builder `extraItemActions`
302
+ // dispatch), the snapshot already carries the form's full state — we
303
+ // just append `ids` / `values` on top so the server sees both the
304
+ // form body (for coerceFormValues + row hydration) and the action's
305
+ // own meta keys.
306
+ const fd = formSnapshot ?? new FormData();
307
+ for (const id of ids)
308
+ fd.append('ids', id);
309
+ for (const [k, v] of Object.entries(values))
310
+ fd.append(k, v);
311
+ const res = await fetch(url, {
312
+ method: 'POST',
313
+ headers: { 'Accept': 'application/json' },
314
+ body: fd,
315
+ });
316
+ // Download branch — handlers that return `{ download }` ask the server
317
+ // to write the body inline with `Content-Disposition: attachment`. Trip
318
+ // a browser download via a synthetic `<a download>` and exit early
319
+ // (no notify drain / no SPA-nav — the file IS the success signal).
320
+ if (res.ok && triggerDownloadIfAttachment(res)) {
321
+ await res.blob().then(triggerBlobDownload(res));
322
+ return;
323
+ }
324
+ const data = await res.json().catch(() => ({}));
325
+ if (!res.ok) {
326
+ const message = String(data.error ?? `Request failed (${res.status})`);
327
+ notify({ type: 'error', title: 'Action failed', body: message });
328
+ return;
329
+ }
330
+ dispatchNotifications(data, notify);
331
+ const redirect = String(data.redirect ?? '');
332
+ if (redirect)
333
+ navigate(redirect);
334
+ else if (typeof window !== 'undefined')
335
+ navigate(window.location.pathname + window.location.search);
336
+ }
337
+ catch (err) {
338
+ notify({ type: 'error', title: 'Action failed', body: err instanceof Error ? err.message : String(err) });
339
+ }
340
+ }
341
+ /** Returns true when the response carries `Content-Disposition: attachment`,
342
+ * which is how the route layer signals a download payload. The header
343
+ * match is case-insensitive (different runtimes normalize differently). */
344
+ function triggerDownloadIfAttachment(res) {
345
+ const cd = res.headers.get('Content-Disposition') ?? res.headers.get('content-disposition') ?? '';
346
+ return cd.toLowerCase().includes('attachment');
347
+ }
348
+ /** Returns a closure that converts the blob into a download by clicking
349
+ * a synthetic `<a download="…">`. Filename is parsed from
350
+ * `Content-Disposition`'s `filename="…"` parameter; falls back to
351
+ * `'download'` when missing. Only mounted when `document` is present
352
+ * (no-op in SSR). */
353
+ function triggerBlobDownload(res) {
354
+ const cd = res.headers.get('Content-Disposition') ?? res.headers.get('content-disposition') ?? '';
355
+ const match = cd.match(/filename\*?=(?:UTF-8'')?["']?([^"';\r\n]+)["']?/i);
356
+ const filename = (match?.[1] ?? 'download').trim();
357
+ return (blob) => {
358
+ if (typeof document === 'undefined' || typeof URL === 'undefined')
359
+ return;
360
+ const objUrl = URL.createObjectURL(blob);
361
+ const a = document.createElement('a');
362
+ a.href = objUrl;
363
+ a.download = filename;
364
+ a.style.display = 'none';
365
+ document.body.appendChild(a);
366
+ a.click();
367
+ a.remove();
368
+ URL.revokeObjectURL(objUrl);
369
+ };
370
+ }
371
+ /**
372
+ * Modal-form action dialog. Opens a Dialog with an optional form schema
373
+ * (rendered from `meta.children`) plus header/footer chrome from
374
+ * `meta.modal`. On submit, fetches the dispatchUrl with `Accept:
375
+ * application/json` so the server can return:
376
+ * - 200 `{ ok: true, redirect }` → navigate (SPA via useNavigate)
377
+ * - 422 `{ ok: false, errors: { field: string[] } }` → inline errors
378
+ * - 500 `{ ok: false, error }` → server error banner
379
+ *
380
+ * Used for handler-style actions that have a schema and/or a modal config.
381
+ * Replaces the older ConfirmActionDialog for that path; confirm-only
382
+ * actions without a schema also flow through here (no fields rendered,
383
+ * just header + footer = same UX as the old confirm dialog).
384
+ */
385
+ function ActionModalDialog({ trigger, meta, ids, initialValues = {}, open: controlledOpen, onOpenChange, }) {
386
+ const [internalOpen, setInternalOpen] = useState(false);
387
+ const isControlled = controlledOpen !== undefined;
388
+ const open = isControlled ? controlledOpen : internalOpen;
389
+ const setOpen = (o) => {
390
+ if (isControlled)
391
+ onOpenChange?.(o);
392
+ else
393
+ setInternalOpen(o);
394
+ };
395
+ const [errors, setErrors] = useState({});
396
+ const [serverError, setServerError] = useState(null);
397
+ const [submitting, setSubmitting] = useState(false);
398
+ const navigate = useNavigate();
399
+ const { notify } = useToast();
400
+ const modal = meta['modal'];
401
+ const confirm = meta['confirm'];
402
+ const destructive = Boolean(meta['destructive']);
403
+ const dispatchUrl = meta['dispatchUrl'];
404
+ const fields = (meta.children ?? []);
405
+ const hasForm = fields.length > 0;
406
+ const heading = modal?.heading ?? confirm?.title ?? (hasForm ? String(meta['label'] ?? 'Submit') : 'Are you sure?');
407
+ const description = modal?.description ?? confirm?.message;
408
+ const submitLabel = modal?.submitLabel ?? (destructive ? 'Delete' : (hasForm ? 'Submit' : 'Confirm'));
409
+ const cancelLabel = modal?.cancelLabel ?? 'Cancel';
410
+ const widthClass = { sm: 'sm:max-w-sm', md: 'sm:max-w-lg', lg: 'sm:max-w-2xl', xl: 'sm:max-w-4xl' }[modal?.width ?? 'md'];
411
+ // Modal chrome extras (Tier-2 audit gap #2). Defaults match the
412
+ // previous renderer behaviour exactly — sparse meta keys round-trip
413
+ // as `undefined` so existing modals are byte-identical.
414
+ const closeByClickingAway = modal?.closeByClickingAway !== false;
415
+ const closeByEscaping = modal?.closeByEscaping !== false;
416
+ const stickyHeader = modal?.stickyHeader === true;
417
+ const stickyFooter = modal?.stickyFooter === true;
418
+ const showCloseButton = modal?.closeButton === true;
419
+ const alignmentClass = { start: 'text-left', center: 'text-center sm:text-left', end: 'text-right' }[modal?.alignment ?? 'center'];
420
+ const iconColorClass = modal?.iconColor
421
+ ? {
422
+ gray: 'text-muted-foreground',
423
+ primary: 'text-primary',
424
+ success: 'text-emerald-600 dark:text-emerald-300',
425
+ warning: 'text-amber-600 dark:text-amber-300',
426
+ destructive: 'text-destructive',
427
+ info: 'text-blue-600 dark:text-blue-300',
428
+ }[modal.iconColor]
429
+ : undefined;
430
+ // Existing default: only the submit button autofocuses (and only for
431
+ // confirm-only modals). When `modalAutofocus(false)` is set the user
432
+ // wants nothing to autofocus; `modalAutofocus(true)` shifts focus to
433
+ // the first form input via a mount-effect ref.
434
+ const explicitAutofocus = modal?.autofocus;
435
+ const submitAutofocus = explicitAutofocus === false ? false
436
+ : explicitAutofocus === true ? !hasForm
437
+ : !hasForm;
438
+ const formRef = useRef(null);
439
+ useEffect(() => {
440
+ if (!open || explicitAutofocus !== true || !hasForm)
441
+ return;
442
+ // Wait for the popup to mount + fields to render. Microtask is enough
443
+ // because Base UI's mount transition is decoupled from our render.
444
+ const id = window.requestAnimationFrame(() => {
445
+ const form = formRef.current;
446
+ if (!form)
447
+ return;
448
+ const target = form.querySelector('input:not([type="hidden"]):not([disabled]), textarea:not([disabled]), select:not([disabled])');
449
+ if (target)
450
+ target.focus();
451
+ });
452
+ return () => window.cancelAnimationFrame(id);
453
+ }, [open, explicitAutofocus, hasForm]);
454
+ const reset = () => { setErrors({}); setServerError(null); setSubmitting(false); };
455
+ const onSubmit = async (e) => {
456
+ e.preventDefault();
457
+ if (!dispatchUrl)
458
+ return;
459
+ setSubmitting(true);
460
+ setServerError(null);
461
+ setErrors({});
462
+ const fd = new FormData(e.currentTarget);
463
+ for (const id of ids)
464
+ fd.append('ids', id);
465
+ try {
466
+ const res = await fetch(dispatchUrl, {
467
+ method: 'POST',
468
+ headers: { 'Accept': 'application/json' },
469
+ body: fd,
470
+ });
471
+ const data = await res.json().catch(() => ({}));
472
+ if (res.status === 422) {
473
+ setErrors(data.errors ?? {});
474
+ setSubmitting(false);
475
+ return;
476
+ }
477
+ if (!res.ok) {
478
+ setServerError(String(data.error ?? `Request failed (${res.status})`));
479
+ setSubmitting(false);
480
+ return;
481
+ }
482
+ setOpen(false);
483
+ reset();
484
+ // Server-emitted notifications come through the JSON response;
485
+ // surface them via the Toaster before navigating so the user
486
+ // sees the success/error toast even when navigation re-renders.
487
+ const notifs = data.notifications;
488
+ if (notifs && notifs.length > 0) {
489
+ for (const n of notifs)
490
+ notify(n);
491
+ }
492
+ const redirect = String(data.redirect ?? '');
493
+ if (redirect)
494
+ navigate(redirect);
495
+ else if (typeof window !== 'undefined')
496
+ navigate(window.location.pathname + window.location.search);
497
+ }
498
+ catch (err) {
499
+ setServerError(err instanceof Error ? err.message : 'Submit failed');
500
+ setSubmitting(false);
501
+ }
502
+ };
503
+ const cancelClass = 'inline-flex items-center justify-center rounded-md border border-input bg-background px-3 h-9 text-sm font-medium hover:bg-accent hover:text-accent-foreground';
504
+ const confirmClass = destructive
505
+ ? 'inline-flex items-center justify-center rounded-md bg-destructive px-3 h-9 text-sm font-medium text-destructive-foreground hover:bg-destructive/90 disabled:opacity-50'
506
+ : 'inline-flex items-center justify-center rounded-md bg-primary px-3 h-9 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50';
507
+ // Resolved icon component for the modal header (Filament-style chrome
508
+ // — leading glyph next to the heading). Passed through `useIconFor`
509
+ // for the same registry lookup used by Resource / Page / Action icons.
510
+ const HeaderIcon = useIconFor(modal?.icon);
511
+ // Build a className for the popup that respects width + sticky-chrome
512
+ // + slideOver. Sticky modes give the popup a max height + overflow so
513
+ // the inner scroll surface exists for sticky to bite onto.
514
+ const stickyMode = stickyHeader || stickyFooter;
515
+ const popupClass = [
516
+ widthClass,
517
+ stickyMode ? 'max-h-[90vh] overflow-hidden p-0' : '',
518
+ ].filter(Boolean).join(' ');
519
+ // Inner scroll body (only used in sticky mode). When inactive the
520
+ // existing flat layout applies (header / fields / footer flow).
521
+ const headerCls = `${alignmentClass} ${stickyHeader ? 'sticky top-0 bg-background z-10 px-6 pt-6 pb-3 border-b' : ''}`.trim();
522
+ const footerCls = stickyFooter ? 'sticky bottom-0 bg-background z-10 px-6 py-3 border-t' : '';
523
+ const bodyCls = stickyMode ? 'flex-1 overflow-y-auto px-6 py-3' : '';
524
+ const formCls = stickyMode ? 'flex flex-col h-full' : '';
525
+ return (_jsxs(_Fragment, { children: [trigger?.(() => { reset(); setOpen(true); }), _jsx(Dialog, { open: open, disablePointerDismissal: !closeByClickingAway, onOpenChange: (o, details) => {
526
+ // Cancel Esc-triggered closes when the user has opted out.
527
+ // Base UI's `details.cancel()` aborts the open-state change.
528
+ if (!o && !closeByEscaping && details && details.reason === 'escapeKey') {
529
+ const cancel = details.cancel;
530
+ if (typeof cancel === 'function')
531
+ cancel();
532
+ return;
533
+ }
534
+ if (!o)
535
+ reset();
536
+ setOpen(o);
537
+ }, children: _jsxs(DialogContent, { className: popupClass, children: [showCloseButton && (_jsx("button", { type: "button", "aria-label": "Close", onClick: () => setOpen(false), className: "absolute top-3 right-3 z-20 inline-flex items-center justify-center rounded-md h-8 w-8 text-muted-foreground hover:bg-accent hover:text-accent-foreground", children: _jsx(XIcon, { className: "size-4" }) })), _jsxs("form", { ref: formRef, onSubmit: onSubmit, className: formCls, children: [_jsxs(DialogHeader, { className: headerCls, children: [_jsxs(DialogTitle, { className: modal?.icon ? 'flex items-center gap-2' : undefined, children: [HeaderIcon && (_jsx(HeaderIcon, { "aria-hidden": true, className: `size-5 shrink-0 ${iconColorClass ?? ''}`.trim() })), _jsx("span", { children: heading })] }), description && _jsx(DialogDescription, { children: description })] }), hasForm && (_jsx("div", { className: `flex flex-col gap-3 py-2 ${bodyCls}`.trim(), children: fields.map((f, i) => renderFormChild(f, i, initialValues, errors)) })), !hasForm && stickyMode && _jsx("div", { className: bodyCls }), serverError && (_jsx("p", { className: `py-2 text-sm text-destructive ${stickyMode ? 'px-6' : ''}`.trim(), children: serverError })), _jsxs(DialogFooter, { className: footerCls, children: [_jsx("button", { type: "button", onClick: () => setOpen(false), className: cancelClass, children: cancelLabel }), _jsx("button", { type: "submit", disabled: submitting, autoFocus: submitAutofocus, className: confirmClass, children: submitting ? 'Working…' : submitLabel })] })] })] }) })] }));
538
+ }
539
+ /**
540
+ * Confirm-style dialog wrapping an action's button. The trigger button is
541
+ * rendered inline; clicking it opens the dialog. On confirm we run
542
+ * `onConfirm` (which is action-style-specific — submit a form, programmatic
543
+ * POST, etc.) and close the dialog. Used by submit-style and form-method
544
+ * actions; handler-style + confirm/modal flows through ActionModalDialog
545
+ * instead.
546
+ */
547
+ function ConfirmActionDialog({ trigger, title, message, destructive, onConfirm, }) {
548
+ const [open, setOpen] = useState(false);
549
+ const confirmClass = destructive
550
+ ? 'inline-flex items-center justify-center rounded-md bg-destructive px-3 h-9 text-sm font-medium text-destructive-foreground hover:bg-destructive/90'
551
+ : 'inline-flex items-center justify-center rounded-md bg-primary px-3 h-9 text-sm font-medium text-primary-foreground hover:bg-primary/90';
552
+ return (_jsxs(_Fragment, { children: [trigger(() => setOpen(true)), _jsx(Dialog, { open: open, onOpenChange: setOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: title ?? 'Are you sure?' }), _jsx(DialogDescription, { children: message })] }), _jsxs(DialogFooter, { children: [_jsx("button", { type: "button", onClick: () => setOpen(false), className: "inline-flex items-center justify-center rounded-md border border-input bg-background px-3 h-9 text-sm font-medium hover:bg-accent hover:text-accent-foreground", children: "Cancel" }), _jsx("button", { type: "button", onClick: () => { setOpen(false); onConfirm(); }, className: confirmClass, autoFocus: true, children: destructive ? 'Delete' : 'Confirm' })] })] }) })] }));
553
+ }
554
+ /**
555
+ * Button + optional confirm dialog for a form-method action (Delete and
556
+ * the like). Click → fetch + JSON dispatch via `dispatchMethodAction` —
557
+ * no full page reload, no server-rendered form. Confirm dialog gates the
558
+ * dispatch when configured.
559
+ */
560
+ function MethodActionButton({ url, method, confirm, destructive, className, name, ariaLabel, tooltip, inner, }) {
561
+ const navigate = useNavigate();
562
+ const { notify } = useToast();
563
+ const dispatch = () => {
564
+ if (!url)
565
+ return;
566
+ void dispatchMethodAction(url, method, navigate, notify);
567
+ };
568
+ if (confirm) {
569
+ return (_jsx(ConfirmActionDialog, { title: confirm.title, message: confirm.message, destructive: destructive, onConfirm: dispatch, trigger: (open) => withTooltip(_jsx("button", { type: "button", onClick: open, className: className, "data-action-name": name, "aria-label": ariaLabel, children: inner }), tooltip) }));
570
+ }
571
+ return withTooltip(_jsx("button", { type: "button", onClick: dispatch, className: className, "data-action-name": name, "aria-label": ariaLabel, children: inner }), tooltip);
572
+ }
573
+ /**
574
+ * Button for a handler-style action without confirm/modal. Click →
575
+ * fetch + JSON via `dispatchHandlerAction`, then SPA-navigate +
576
+ * show notifications. No full page reload.
577
+ */
578
+ function HandlerActionButton({ url, ids, className, name, ariaLabel, tooltip, inner, }) {
579
+ const navigate = useNavigate();
580
+ const { notify } = useToast();
581
+ return withTooltip(_jsx("button", { type: "button", onClick: () => void dispatchHandlerAction(url, ids, navigate, notify), className: className, "data-action-name": name, "aria-label": ariaLabel, children: inner }), tooltip);
582
+ }
583
+ /** Render either a single Action or an ActionGroup based on `el.type`.
584
+ * Used by callsites that accept both (table header / bulk toolbars,
585
+ * heading actions, container schemas). */
586
+ function renderActionLike(el, index, opts = {}) {
587
+ if (el.type === 'actionGroup') {
588
+ return _jsx(ActionGroupTrigger, { el: el, ids: opts.ids ?? [] }, index);
589
+ }
590
+ return renderAction(el, index, opts);
591
+ }
592
+ /** Color preset → tailwind class group. `ghost` is bg-less and works
593
+ * with hover:bg-accent. `destructive` uses a soft tonal style (Filament-
594
+ * style) so per-row Delete buttons sit calmly next to primary actions
595
+ * instead of shouting in saturated red — the modal confirm CTA still
596
+ * renders solid red via its own hardcoded class. Others are solid + hover-
597
+ * darken. */
598
+ const COLOR_VARIANTS = {
599
+ primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
600
+ destructive: 'bg-red-50 text-red-700 hover:bg-red-100 dark:bg-red-950/40 dark:text-red-400 dark:hover:bg-red-950/60',
601
+ success: 'bg-emerald-600 text-white hover:bg-emerald-600/90',
602
+ warning: 'bg-amber-500 text-white hover:bg-amber-500/90',
603
+ info: 'bg-blue-600 text-white hover:bg-blue-600/90',
604
+ ghost: 'bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground',
605
+ };
606
+ /** Outlined variant — replaces solid bg with a border + transparent bg. */
607
+ const OUTLINED_VARIANTS = {
608
+ primary: 'border border-primary/40 text-primary bg-transparent hover:bg-primary/10',
609
+ destructive: 'border border-destructive/40 text-destructive bg-transparent hover:bg-destructive/10',
610
+ success: 'border border-emerald-600/40 text-emerald-700 dark:text-emerald-400 bg-transparent hover:bg-emerald-600/10',
611
+ warning: 'border border-amber-500/40 text-amber-700 dark:text-amber-400 bg-transparent hover:bg-amber-500/10',
612
+ info: 'border border-blue-600/40 text-blue-700 dark:text-blue-400 bg-transparent hover:bg-blue-600/10',
613
+ ghost: 'border border-input text-foreground bg-transparent hover:bg-accent',
614
+ };
615
+ /** Size preset → tailwind sizing classes. Icon-only buttons use the
616
+ * width=height variants from the second map. */
617
+ const SIZE_CLASSES = {
618
+ sm: 'h-7 px-2 text-xs',
619
+ md: 'h-8 px-3 text-sm',
620
+ lg: 'h-10 px-4 text-base',
621
+ };
622
+ const ICON_SIZE_CLASSES = {
623
+ sm: 'h-7 w-7 text-xs',
624
+ md: 'h-8 w-8 text-sm',
625
+ lg: 'h-10 w-10 text-base',
626
+ };
627
+ /** Build the trigger button className from action meta + render context. */
628
+ function actionButtonClass(el, opts) {
629
+ const destructive = Boolean(el['destructive']);
630
+ const placement = String(el['placement'] ?? 'inline');
631
+ const outlined = Boolean(el['outlined']);
632
+ const iconOnly = Boolean(el['iconOnly']);
633
+ const explicitColor = el['color'];
634
+ const explicitSize = el['size'];
635
+ // Color: explicit `.color()` wins; `destructive` flag falls back to
636
+ // 'destructive'; otherwise 'primary'.
637
+ const color = explicitColor ?? (destructive ? 'destructive' : 'primary');
638
+ const variant = (outlined ? OUTLINED_VARIANTS[color] : COLOR_VARIANTS[color]) ?? COLOR_VARIANTS['primary'];
639
+ // Size: explicit `.size()` wins; otherwise small for row context, md elsewhere.
640
+ const size = explicitSize ?? (opts.size === 'sm' || placement === 'row' ? 'sm' : 'md');
641
+ const sizingMap = iconOnly ? ICON_SIZE_CLASSES : SIZE_CLASSES;
642
+ const sizing = sizingMap[size] ?? sizingMap['md'];
643
+ return `relative inline-flex items-center justify-center gap-1.5 rounded-md font-medium transition ${variant} ${sizing}`;
644
+ }
645
+ /** Render the action's icon (when set). String names resolve through the
646
+ * user-extensible icon registry; missing names render nothing rather
647
+ * than a fallback glyph (action icons are decorative, not load-bearing). */
648
+ function renderActionIcon(el) {
649
+ const name = typeof el['icon'] === 'string' ? el['icon'] : undefined;
650
+ const Icon = resolveIcon(name);
651
+ if (!Icon)
652
+ return null;
653
+ return _jsx(Icon, { className: "size-4", "aria-hidden": "true" });
654
+ }
655
+ /** Tiny corner badge for actions that set `.badge(...)`. */
656
+ function renderActionBadge(el) {
657
+ const value = el['badge'];
658
+ if (value === undefined || value === null || value === '')
659
+ return null;
660
+ const color = el['badgeColor'] ?? 'bg-primary text-primary-foreground';
661
+ return (_jsx("span", { className: `absolute -top-1 -right-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium ${color}`, children: String(value) }));
662
+ }
663
+ /** If `meta.tooltip` is set, wrap the trigger in a Tooltip. The Tooltip's
664
+ * provider mounts on demand so multiple actions on a page don't share
665
+ * state. */
666
+ function withTooltip(node, tooltip) {
667
+ if (!tooltip)
668
+ return node;
669
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: () => node }), _jsx(TooltipContent, { children: tooltip })] }) }));
670
+ }
671
+ function renderAction(el, index, opts = {}) {
672
+ const name = String(el['name'] ?? '');
673
+ const label = String(el['label'] ?? name);
674
+ const destructive = Boolean(el['destructive']);
675
+ const href = el['href'];
676
+ const method = el['method'];
677
+ const actionUrl = el['action'];
678
+ const dispatchUrl = el['dispatchUrl'];
679
+ const submit = Boolean(el['submit']);
680
+ const confirm = el['confirm'];
681
+ const tooltip = el['tooltip'];
682
+ const iconOnly = Boolean(el['iconOnly']);
683
+ const isDisabled = Boolean(el['disabled']);
684
+ const className = actionButtonClass(el, opts) + (isDisabled ? ' opacity-50 cursor-not-allowed pointer-events-none' : '');
685
+ const icon = renderActionIcon(el);
686
+ const badge = renderActionBadge(el);
687
+ // Icon-only buttons hide the label visually but expose it via aria-label.
688
+ const ariaLabel = iconOnly ? label : undefined;
689
+ const inner = iconOnly ? _jsxs(_Fragment, { children: [icon, badge] }) : _jsxs(_Fragment, { children: [icon, _jsx("span", { children: label }), badge] });
690
+ // Submit-style action — renders as <button type="submit">. Optionally
691
+ // targets a specific form via the HTML `form="<id>"` attribute so the
692
+ // button can submit a form it lives outside of (e.g. a page-header
693
+ // Save button driving a form below). When `formField` is set, the
694
+ // button posts a sentinel name/value pair (e.g. `_continueCreate=1`)
695
+ // so the server can branch on which submit was clicked.
696
+ if (submit) {
697
+ const formTarget = el['form'];
698
+ const formField = el['formField'];
699
+ if (confirm) {
700
+ // Confirm-gated submit: render as type="button" so click opens the
701
+ // dialog instead of submitting; on confirm, programmatically submit
702
+ // the targeted form (or the closest enclosing form if no formTarget).
703
+ // `formField` is intentionally not threaded here — programmatic
704
+ // `requestSubmit()` has no submitter, so the name/value pair would
705
+ // be lost anyway. Pair `.confirm()` with a hidden input on the form
706
+ // if you need a sentinel under a confirm flow.
707
+ return (_jsx(ConfirmActionDialog, { title: confirm.title, message: confirm.message, destructive: destructive, onConfirm: () => {
708
+ if (typeof document === 'undefined')
709
+ return;
710
+ const form = formTarget
711
+ ? document.getElementById(formTarget)
712
+ : document.querySelector('form');
713
+ form?.requestSubmit();
714
+ }, trigger: (open) => withTooltip(_jsx("button", { type: "button", onClick: open, className: className, "data-action-name": name, "aria-label": ariaLabel, children: inner }), tooltip) }, index));
715
+ }
716
+ return withTooltip(_jsx("button", { type: "submit", form: formTarget, className: className, "data-action-name": name, "aria-label": ariaLabel, ...(formField ? { name: formField.name, value: formField.value } : {}), children: inner }, index), tooltip);
717
+ }
718
+ // Substitute the `:id` placeholder with the current row id when this
719
+ // action is rendered in a row context. Lets row-level link/form actions
720
+ // ship a single template URL like `/admin/articles/:id/edit`.
721
+ const rowId = opts.ids?.length === 1 ? opts.ids[0] : undefined;
722
+ const resolveTemplate = (s) => s && rowId ? s.replace(':id', rowId) : s;
723
+ // Link-style action.
724
+ if (href) {
725
+ return withTooltip(_jsx("a", { href: resolveTemplate(href), className: className, "data-action-name": name, "aria-label": ariaLabel, children: inner }, index), tooltip);
726
+ }
727
+ // Form-style action (POST/PUT/PATCH/DELETE) — fetch + JSON, no full reload.
728
+ if (method) {
729
+ const resolvedUrl = resolveTemplate(actionUrl);
730
+ return (_jsx(MethodActionButton, { url: resolvedUrl, method: method, confirm: confirm, destructive: destructive, className: className, name: name, ariaLabel: ariaLabel, tooltip: tooltip, inner: inner }, index));
731
+ }
732
+ // Handler-style action — fetch + JSON dispatch with `ids[]` body.
733
+ if (dispatchUrl) {
734
+ const ids = opts.ids ?? [];
735
+ const modal = el['modal'];
736
+ if (confirm || modal) {
737
+ return (_jsx(ActionModalDialog, { meta: el, ids: ids, trigger: (open) => withTooltip(_jsx("button", { type: "button", onClick: open, className: className, "data-action-name": name, "aria-label": ariaLabel, children: inner }), tooltip) }, index));
738
+ }
739
+ return (_jsx(HandlerActionButton, { url: dispatchUrl, ids: ids, className: className, name: name, ariaLabel: ariaLabel, tooltip: tooltip, inner: inner }, index));
740
+ }
741
+ // No dispatch wired (no href / method / dispatchUrl). Render a disabled
742
+ // placeholder so the user sees the button, but it does nothing.
743
+ return withTooltip(_jsx("button", { type: "button", disabled: true, className: className + ' opacity-50 cursor-not-allowed', "data-action-name": name, "aria-label": ariaLabel, children: inner }, index), tooltip);
744
+ }
745
+ // ─── Layout helpers ─────────────────────────────────────────
746
+ /**
747
+ * Map `meta._layout` (Plan #8 — `columnSpan / columnStart / columnOrder`)
748
+ * onto Tailwind utility classes. Returns an empty string when the
749
+ * element has no layout hints. Outside of a parent Grid/Split the
750
+ * classes have no effect — Tailwind generates `col-span-*` /
751
+ * `col-start-*` / `order-*` regardless of context.
752
+ *
753
+ * Tailwind's JIT only ships utilities up to a fixed range; clamp here
754
+ * to the safe defaults (1..12 for col, 1..12 for order).
755
+ */
756
+ function layoutClasses(el) {
757
+ const layout = el['_layout'];
758
+ if (!layout)
759
+ return '';
760
+ const out = [];
761
+ if (typeof layout.columnSpan === 'number') {
762
+ const span = Math.max(1, Math.min(12, layout.columnSpan));
763
+ out.push(`col-span-${span}`);
764
+ }
765
+ if (typeof layout.columnStart === 'number') {
766
+ const start = Math.max(1, Math.min(12, layout.columnStart));
767
+ out.push(`col-start-${start}`);
768
+ }
769
+ if (typeof layout.columnOrder === 'number') {
770
+ const order = Math.max(1, Math.min(12, layout.columnOrder));
771
+ out.push(`order-${order}`);
772
+ }
773
+ return out.join(' ');
774
+ }
775
+ // ─── Container helpers ──────────────────────────────────────
776
+ function renderChildren(children, gap = 'gap-4') {
777
+ if (!children || children.length === 0)
778
+ return null;
779
+ return (_jsx("div", { className: `flex flex-col ${gap}`, children: children.map((child, i) => renderElement(child, i)) }));
780
+ }
781
+ // ─── Tabs (stateful — needs useState) ────────────────────────
782
+ /**
783
+ * Active-filters bar — pill row above the table summarising every filter
784
+ * with a current value. Each pill shows the filter's `indicator` text
785
+ * (server-formatted via `Filter.indicator()` / per-subclass defaults) and
786
+ * an `×` button that clears that filter's URL key in place. Clicking ×
787
+ * also drops `?page` so users land on the first page of the relaxed set.
788
+ *
789
+ * Renders nothing when no filter has an indicator.
790
+ */
791
+ function ActiveFiltersBar({ filters, prefix }) {
792
+ const navigate = useNavigate();
793
+ const active = filters.filter(f => typeof f['indicator'] === 'string' && f['indicator'] !== '');
794
+ if (active.length === 0)
795
+ return null;
796
+ const clear = (name) => {
797
+ if (typeof window === 'undefined')
798
+ return;
799
+ const url = new URL(window.location.href);
800
+ url.searchParams.delete(prefixK(prefix, name));
801
+ url.searchParams.delete(prefixK(prefix, 'page'));
802
+ void navigate(url.pathname + url.search);
803
+ };
804
+ const clearAll = () => {
805
+ if (typeof window === 'undefined')
806
+ return;
807
+ const url = new URL(window.location.href);
808
+ for (const f of active)
809
+ url.searchParams.delete(prefixK(prefix, String(f['name'] ?? '')));
810
+ url.searchParams.delete(prefixK(prefix, 'page'));
811
+ void navigate(url.pathname + url.search);
812
+ };
813
+ return (_jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs", children: [active.map((f, i) => {
814
+ const name = String(f['name'] ?? '');
815
+ const indicator = String(f['indicator'] ?? '');
816
+ return (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-border bg-muted/40 pl-2.5 pr-1 py-0.5", children: [_jsx("span", { children: indicator }), _jsx("button", { type: "button", onClick: () => clear(name), "aria-label": `Clear filter ${indicator}`, className: "inline-flex size-4 items-center justify-center rounded-full text-muted-foreground hover:bg-muted hover:text-foreground", children: "\u00D7" })] }, i));
817
+ }), active.length > 1 && (_jsx("button", { type: "button", onClick: clearAll, className: "text-muted-foreground hover:text-foreground underline-offset-2 hover:underline", children: "Clear all" }))] }));
818
+ }
819
+ /**
820
+ * Filter icon button + Popover containing every filter control.
821
+ * Opens on click; the inner Selects don't dismiss the outer Popover when
822
+ * an option is chosen (Base UI Popover doesn't auto-close on inner clicks).
823
+ *
824
+ * Each FilterSelect navigates the page on change (window.location), so the
825
+ * filter form is no longer needed — keeps the search input in its own
826
+ * lightweight form for native Enter-to-submit.
827
+ */
828
+ function FilterPopover({ filters, prefix }) {
829
+ const activeCount = filters.filter(f => {
830
+ const v = f['value'];
831
+ return typeof v === 'string' && v !== '';
832
+ }).length;
833
+ return (_jsxs(Popover, { children: [_jsx(PopoverTrigger, { render: (props) => (_jsxs("button", { ...props, type: "button", "aria-label": "Filters", className: "relative inline-flex h-9 items-center justify-center gap-1.5 rounded-md border border-input bg-background px-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground", children: [_jsx(FilterIcon, { className: "size-4" }), _jsx("span", { children: "Filters" }), activeCount > 0 && (_jsx("span", { className: "ml-1 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-medium text-primary-foreground", children: activeCount }))] })) }), _jsx(PopoverContent, { align: "start", className: filters.some(f => f['kind'] === 'queryBuilder')
834
+ ? 'w-[36rem] max-w-[calc(100vw-2rem)] p-3'
835
+ : 'w-72 p-3', children: _jsx("div", { className: "flex flex-col gap-3", children: filters.map((f, i) => renderFilterControl(f, i, prefix)) }) })] }));
836
+ }
837
+ /**
838
+ * Inline strip of filter controls — used by `Table.filtersLayout('above-content'
839
+ * | 'above-content-collapsible' | 'below-content')`. Mirrors `FilterPopover`'s
840
+ * inner body but lays the controls out in a wrapping row instead of a
841
+ * vertical stack inside a popover.
842
+ */
843
+ function FilterStrip({ filters, prefix }) {
844
+ if (filters.length === 0)
845
+ return null;
846
+ return (_jsx("div", { className: "flex flex-col gap-3 rounded-md border bg-muted/30 p-3 sm:flex-row sm:flex-wrap sm:items-end", children: filters.map((f, i) => (_jsx("div", { className: "min-w-[12rem] flex-1 sm:max-w-xs", children: renderFilterControl(f, i, prefix) }, i))) }));
847
+ }
848
+ /**
849
+ * Toolbar button paired with `FilterStrip` for `Table.filtersLayout(
850
+ * 'above-content-collapsible')`. Visually matches the modal-mode trigger
851
+ * (filter icon + "Filters" label + active-count badge) but flips a parent-
852
+ * owned `open` state instead of opening a Popover.
853
+ */
854
+ function FilterStripToggle({ filters, open, onToggle, }) {
855
+ const activeCount = filters.filter(f => {
856
+ const v = f['value'];
857
+ return typeof v === 'string' && v !== '';
858
+ }).length;
859
+ return (_jsxs("button", { type: "button", "aria-label": "Filters", "aria-expanded": open, onClick: onToggle, className: "relative inline-flex h-9 items-center justify-center gap-1.5 rounded-md border border-input bg-background px-3 text-sm font-medium hover:bg-accent hover:text-accent-foreground", children: [_jsx(FilterIcon, { className: "size-4" }), _jsx("span", { children: "Filters" }), activeCount > 0 && (_jsx("span", { className: "ml-1 inline-flex h-5 min-w-5 items-center justify-center rounded-full bg-primary px-1.5 text-xs font-medium text-primary-foreground", children: activeCount }))] }));
860
+ }
861
+ /**
862
+ * Render row actions inline. Each Action becomes a small button next to
863
+ * the others; an `ActionGroup` placed in row position keeps its dropdown
864
+ * via `ActionGroupTrigger` (the dropdown UX is opt-in via grouping, not
865
+ * a default). Per-row visibility and disabled state come from the
866
+ * server-side eval inside `dispatchTable` (`_visibleActions` /
867
+ * `_disabledActions` keys on the row).
868
+ *
869
+ * Each Action's dispatch (link / fetch+JSON / modal / confirm) is handled
870
+ * by `renderActionLike` → `renderAction`, same path as header / inline /
871
+ * bulk placements. The `:id` substitution comes from `opts.ids = [rowId]`.
872
+ */
873
+ function renderRowActions(rowId, rowRecord, actions) {
874
+ const rowVisibleSet = new Set(rowRecord?.['_visibleActions'] ?? []);
875
+ const rowDisabledSet = new Set(rowRecord?.['_disabledActions'] ?? []);
876
+ const visible = actions.filter(a => {
877
+ if (!a['conditional'])
878
+ return true;
879
+ return rowVisibleSet.has(String(a['name'] ?? ''));
880
+ });
881
+ const decorate = (a) => {
882
+ const name = String(a['name'] ?? '');
883
+ if (rowDisabledSet.has(name)) {
884
+ return { ...a, disabled: true };
885
+ }
886
+ return a;
887
+ };
888
+ return (_jsx("div", { className: "flex items-center justify-end gap-1", children: visible.map((a, i) => renderActionLike(decorate(a), i, { ids: [rowId], size: 'sm' })) }));
889
+ }
890
+ /**
891
+ * Trigger button + dropdown menu for an `ActionGroup` meta. Reuses the
892
+ * action button styling helpers so a group's chrome (color/size/outlined/
893
+ * tooltip/iconButton) matches a regular Action. Each child Action
894
+ * dispatches via the same logic as `renderAction` — link/method/handler/
895
+ * confirm/modal — but routed through a `pending` state so the dropdown
896
+ * closes before any dialog opens (shadcn pattern: one popup at a time).
897
+ */
898
+ function ActionGroupTrigger({ el, ids = [], }) {
899
+ const [pending, setPending] = useState(null);
900
+ const navigate = useNavigate();
901
+ const { notify } = useToast();
902
+ const name = String(el['name'] ?? '');
903
+ const label = String(el['label'] ?? name);
904
+ const tooltip = el['tooltip'];
905
+ const iconOnly = Boolean(el['iconOnly']);
906
+ const isDisabled = Boolean(el['disabled']);
907
+ const childActions = (el.children ?? []).filter(c => c.type === 'action');
908
+ const className = actionButtonClass(el, {}) + (isDisabled ? ' opacity-50 cursor-not-allowed pointer-events-none' : '');
909
+ const ariaLabel = iconOnly ? label : undefined;
910
+ // Direct-dispatch path mirrors renderAction's branches but skipping
911
+ // confirm/modal (those queue into `pending` so the dropdown can close).
912
+ const dispatch = (action) => {
913
+ const href = action['href'];
914
+ const method = action['method'];
915
+ const actionUrl = action['action'];
916
+ const dispatchUrl = action['dispatchUrl'];
917
+ if (href) {
918
+ navigate(href);
919
+ return;
920
+ }
921
+ if (method && actionUrl) {
922
+ void dispatchMethodAction(actionUrl, method, navigate, notify);
923
+ return;
924
+ }
925
+ if (dispatchUrl) {
926
+ void dispatchHandlerAction(dispatchUrl, ids, navigate, notify);
927
+ return;
928
+ }
929
+ };
930
+ const onItemClick = (action) => {
931
+ if (action['modal'] || action['confirm']) {
932
+ setPending(action);
933
+ return;
934
+ }
935
+ dispatch(action);
936
+ };
937
+ const pendingHandler = pending && pending['dispatchUrl'];
938
+ const pendingConfirmOnly = pending && !pendingHandler && pending['confirm'];
939
+ const pendingConfirm = pendingConfirmOnly || pending?.['confirm'];
940
+ return (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { render: (props) => withTooltip(_jsx("button", { ...props, type: "button", className: className, "data-action-group-name": name, "aria-label": ariaLabel, children: iconOnly ? null : _jsx("span", { children: label }) }), tooltip) }), _jsx(DropdownMenuContent, { align: "end", children: childActions.map((a, i) => {
941
+ const itemLabel = String(a['label'] ?? a['name'] ?? '');
942
+ const destructive = Boolean(a['destructive']);
943
+ const itemDisabled = Boolean(a['disabled']);
944
+ return (_jsx(DropdownMenuItem, { destructive: destructive, disabled: itemDisabled, onClick: () => { if (!itemDisabled)
945
+ onItemClick(a); }, children: itemLabel }, i));
946
+ }) })] }), pendingHandler && pending && (_jsx(ActionModalDialog, { meta: pending, ids: ids, open: true, onOpenChange: (o) => { if (!o)
947
+ setPending(null); } })), _jsx(Dialog, { open: Boolean(pendingConfirmOnly), onOpenChange: (o) => { if (!o)
948
+ setPending(null); }, children: _jsx(DialogContent, { children: pendingConfirmOnly && pendingConfirm && (_jsxs(_Fragment, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: pendingConfirm.title ?? 'Are you sure?' }), _jsx(DialogDescription, { children: pendingConfirm.message })] }), _jsxs(DialogFooter, { children: [_jsx("button", { type: "button", onClick: () => setPending(null), className: "inline-flex items-center justify-center rounded-md border border-input bg-background px-3 h-9 text-sm font-medium hover:bg-accent hover:text-accent-foreground", children: "Cancel" }), _jsx("button", { type: "button", autoFocus: true, onClick: () => {
949
+ const action = pending;
950
+ setPending(null);
951
+ if (action)
952
+ dispatch(action);
953
+ }, className: pending && pending['destructive']
954
+ ? 'inline-flex items-center justify-center rounded-md bg-destructive px-3 h-9 text-sm font-medium text-destructive-foreground hover:bg-destructive/90'
955
+ : 'inline-flex items-center justify-center rounded-md bg-primary px-3 h-9 text-sm font-medium text-primary-foreground hover:bg-primary/90', children: pending && pending['destructive'] ? 'Delete' : 'Confirm' })] })] })) }) })] }));
956
+ }
957
+ function TabsRenderer({ el, index }) {
958
+ const tabs = (el.children ?? []).filter(c => c.type === 'tab');
959
+ if (tabs.length === 0)
960
+ return null;
961
+ const variant = el['variant'] === 'underline' ? 'underline' : 'pills';
962
+ const tabValues = tabs.map((_, i) => `tab-${i}`);
963
+ const defaultValue = tabValues[0];
964
+ // Underline variant overrides the primitive's pill chrome with a bottom
965
+ // border on the list and per-trigger underline-on-selected. No
966
+ // `<TabsIndicator>` is rendered, so there's no sliding pill to hide.
967
+ const listClass = variant === 'underline'
968
+ ? 'relative flex h-auto w-fit justify-start gap-0 rounded-none bg-transparent p-0 text-muted-foreground border-b border-border'
969
+ : undefined;
970
+ const triggerClass = variant === 'underline'
971
+ ? 'rounded-none border-0 border-b-2 border-transparent bg-transparent px-4 py-2 text-sm font-medium -mb-px data-[active]:border-primary data-[active]:text-foreground data-[active]:bg-transparent data-[active]:shadow-none'
972
+ : undefined;
973
+ return (_jsxs(Tabs, { defaultValue: defaultValue, children: [_jsx(TabsList, { className: listClass, children: tabs.map((tab, i) => (_jsxs(TabsTrigger, { value: tabValues[i], className: triggerClass, children: [String(tab['label'] ?? ''), tab['badge'] ? (_jsx("span", { className: "ml-2 text-xs px-1.5 py-0.5 rounded-full bg-muted", children: String(tab['badge']) })) : null] }, i))) }), tabs.map((tab, i) => (_jsx(TabsContent, { value: tabValues[i], className: "pt-2", children: renderChildren(tab['children']) }, i)))] }, index));
974
+ }
975
+ // ─── Section (stateful when collapsible) ────────────────────
976
+ function SectionRenderer({ el, index }) {
977
+ const title = el['title'] ? String(el['title']) : undefined;
978
+ const description = el['description'] ? String(el['description']) : undefined;
979
+ const iconName = el['icon'] ? String(el['icon']) : undefined;
980
+ const badge = el['badge'] ? String(el['badge']) : undefined;
981
+ const columns = Number(el['columns'] ?? 1);
982
+ const collapsible = Boolean(el['collapsible']);
983
+ const compact = Boolean(el['compact']);
984
+ const dense = Boolean(el['dense']);
985
+ const secondary = Boolean(el['secondary']);
986
+ const afterHeader = el['afterHeader'] ?? [];
987
+ const persist = Boolean(el['persistCollapsed']);
988
+ const persistKey = el['persistKey']
989
+ ? `pilotiq.section.${String(el['persistKey'])}`
990
+ : title
991
+ ? `pilotiq.section.${title.toLowerCase().replace(/\s+/g, '-')}`
992
+ : undefined;
993
+ const [collapsed, setCollapsed] = useState(Boolean(el['defaultCollapsed']));
994
+ // Plan #8 — persist open/closed state to localStorage. Hydration-safe:
995
+ // initial render uses `defaultCollapsed`; effect overrides from storage
996
+ // after mount so server + client first paint agree.
997
+ useEffect(() => {
998
+ if (!persist || !persistKey)
999
+ return;
1000
+ if (typeof window === 'undefined')
1001
+ return;
1002
+ try {
1003
+ const stored = window.localStorage.getItem(persistKey);
1004
+ if (stored === '0')
1005
+ setCollapsed(false);
1006
+ if (stored === '1')
1007
+ setCollapsed(true);
1008
+ }
1009
+ catch { /* localStorage may be unavailable (private mode) */ }
1010
+ }, [persist, persistKey]);
1011
+ useEffect(() => {
1012
+ if (!persist || !persistKey)
1013
+ return;
1014
+ if (typeof window === 'undefined')
1015
+ return;
1016
+ try {
1017
+ window.localStorage.setItem(persistKey, collapsed ? '1' : '0');
1018
+ }
1019
+ catch { /* ignore */ }
1020
+ }, [persist, persistKey, collapsed]);
1021
+ // `dense` tightens the inner spacing between the section's children
1022
+ // (orthogonal to `compact`, which trims the section's outer padding /
1023
+ // heading). gap-2 ≈ 8px vs gap-4 ≈ 16px.
1024
+ const innerGap = dense ? 'gap-2' : 'gap-4';
1025
+ const gridClass = columns === 2 ? `grid grid-cols-2 ${innerGap}` : columns === 3 ? `grid grid-cols-3 ${innerGap}` : `flex flex-col ${innerGap}`;
1026
+ const padding = compact ? 'p-3' : 'p-4';
1027
+ const titleSize = compact ? 'text-sm' : 'text-base';
1028
+ const Icon = resolveIcon(iconName);
1029
+ // `secondary()` flips the section background to the muted token so it
1030
+ // visually recedes beneath a primary section. The border thins to the
1031
+ // same muted tone for the same reason — a sharp `border-input` line
1032
+ // around a muted block looks like a typographic ledger rather than a
1033
+ // grouping container.
1034
+ const surfaceClass = secondary ? 'bg-muted/40 border-muted' : 'bg-card';
1035
+ return (_jsxs("section", { className: `flex flex-col ${compact ? 'gap-2' : 'gap-3'} rounded-lg border ${surfaceClass} ${padding} ${layoutClasses(el)}`.trim(), children: [(title || description || collapsible || badge || afterHeader.length > 0) && (_jsxs("header", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [Icon && _jsx(Icon, { className: "size-4 mt-0.5 text-muted-foreground", "aria-hidden": "true" }), _jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [title && _jsx("h3", { className: `${titleSize} font-semibold`, children: title }), badge && (_jsx("span", { className: "rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: badge }))] }), description && _jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: description })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [afterHeader.length > 0 && (_jsx("div", { className: "flex items-center gap-1", children: afterHeader.map((a, i) => renderElement(a, i)) })), collapsible && (_jsx("button", { type: "button", onClick: () => setCollapsed(c => !c), className: "text-xs text-muted-foreground hover:text-foreground", children: collapsed ? 'Expand' : 'Collapse' }))] })] })), !collapsed && el.children && el.children.length > 0 && (_jsx("div", { className: gridClass, children: el.children.map((c, i) => renderElement(c, i)) }))] }, index));
1036
+ }
1037
+ // ─── Wizard (Plan #8) ───────────────────────────────────────
1038
+ /**
1039
+ * Multi-step form layout. Tracks active step in `useState`, optionally
1040
+ * persisted to localStorage. On Next click, POSTs `{ step, values }` to
1041
+ * the form's `wizardUrl` (stamped by the route handler when the form
1042
+ * has a Wizard descendant). 200 → advance; 422 → stamp inline errors;
1043
+ * absent `wizardUrl` → advance immediately (no validation).
1044
+ *
1045
+ * Inactive steps render hidden (display:none) rather than unmounted so
1046
+ * controlled inputs preserve their values across step transitions and
1047
+ * cross-step `$get` works on the resolved meta.
1048
+ */
1049
+ function WizardRenderer({ el, index }) {
1050
+ const formState = useFormState();
1051
+ const formId = formState?.formMeta['formId'] ? String(formState.formMeta['formId']) : undefined;
1052
+ const wizardUrl = formState?.formMeta['wizardUrl'] ? String(formState.formMeta['wizardUrl']) : undefined;
1053
+ const steps = (el.children ?? []).filter(c => c.type === 'step');
1054
+ const skippable = Boolean(el['skippable']);
1055
+ const startOnStep = Math.max(0, Math.min(Math.max(0, steps.length - 1), Number(el['startOnStep'] ?? 0)));
1056
+ const persist = el['persist'] !== false;
1057
+ const storageKey = persist && formId ? `pilotiq.wizard.${formId}.step` : undefined;
1058
+ const [active, setActive] = useState(startOnStep);
1059
+ const [advancing, setAdvancing] = useState(false);
1060
+ const [advanceError, setAdvanceError] = useState(null);
1061
+ // Hydrate persisted step from localStorage after mount.
1062
+ useEffect(() => {
1063
+ if (!storageKey)
1064
+ return;
1065
+ if (typeof window === 'undefined')
1066
+ return;
1067
+ try {
1068
+ const stored = window.localStorage.getItem(storageKey);
1069
+ if (stored !== null) {
1070
+ const n = Number(stored);
1071
+ if (Number.isFinite(n) && n >= 0 && n < steps.length)
1072
+ setActive(n);
1073
+ }
1074
+ }
1075
+ catch { /* ignore */ }
1076
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1077
+ }, [storageKey]);
1078
+ // Persist active step changes.
1079
+ useEffect(() => {
1080
+ if (!storageKey)
1081
+ return;
1082
+ if (typeof window === 'undefined')
1083
+ return;
1084
+ try {
1085
+ window.localStorage.setItem(storageKey, String(active));
1086
+ }
1087
+ catch { /* ignore */ }
1088
+ }, [storageKey, active]);
1089
+ if (steps.length === 0) {
1090
+ return (_jsx("div", { className: "rounded-lg border border-dashed p-8 text-center text-sm text-muted-foreground", children: "No steps configured." }, index));
1091
+ }
1092
+ const isLast = active === steps.length - 1;
1093
+ const isFirst = active === 0;
1094
+ const advance = async (target) => {
1095
+ setAdvanceError(null);
1096
+ if (!wizardUrl) {
1097
+ setActive(target);
1098
+ return;
1099
+ }
1100
+ setAdvancing(true);
1101
+ try {
1102
+ const values = formState?.values ?? {};
1103
+ // Validate intermediate steps in order when jumping ahead.
1104
+ const path = target > active
1105
+ ? Array.from({ length: target - active }, (_, k) => active + k)
1106
+ : [active]; // jumping back is unconstrained
1107
+ let landed = active;
1108
+ for (const stepIdx of path) {
1109
+ const res = await fetch(wizardUrl, {
1110
+ method: 'POST',
1111
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
1112
+ body: JSON.stringify({ step: stepIdx, values }),
1113
+ });
1114
+ if (res.status === 422) {
1115
+ const data = await res.json().catch(() => ({}));
1116
+ const errors = (data?.errors ?? {});
1117
+ if (formState?.applyErrors)
1118
+ formState.applyErrors(errors);
1119
+ landed = stepIdx;
1120
+ setAdvanceError('Please fix the highlighted fields.');
1121
+ break;
1122
+ }
1123
+ if (!res.ok) {
1124
+ setAdvanceError('Step validation failed.');
1125
+ break;
1126
+ }
1127
+ landed = stepIdx + 1;
1128
+ }
1129
+ setActive(target > active ? landed : target);
1130
+ }
1131
+ catch {
1132
+ setAdvanceError('Step validation failed.');
1133
+ }
1134
+ finally {
1135
+ setAdvancing(false);
1136
+ }
1137
+ };
1138
+ return (_jsxs("div", { className: `flex flex-col gap-6 ${layoutClasses(el)}`.trim(), children: [_jsx("ol", { className: "flex items-center gap-3 overflow-x-auto", "aria-label": "Wizard progress", children: steps.map((s, i) => {
1139
+ const Icon = resolveIcon(s['icon'] ? String(s['icon']) : undefined);
1140
+ const reachable = skippable || i <= active;
1141
+ const isActive = i === active;
1142
+ const isDone = i < active;
1143
+ return (_jsxs("li", { className: "flex items-center gap-2 shrink-0", children: [_jsxs("button", { type: "button", disabled: !reachable || advancing, onClick: () => reachable && advance(i), className: [
1144
+ 'flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm transition',
1145
+ isActive ? 'border-primary bg-primary/10 text-foreground'
1146
+ : isDone ? 'border-border text-muted-foreground hover:bg-muted'
1147
+ : 'border-border text-muted-foreground',
1148
+ reachable ? 'cursor-pointer' : 'opacity-50 cursor-not-allowed',
1149
+ ].join(' '), "aria-current": isActive ? 'step' : undefined, children: [_jsx("span", { className: [
1150
+ 'flex size-5 items-center justify-center rounded-full text-[11px] font-semibold',
1151
+ isActive ? 'bg-primary text-primary-foreground'
1152
+ : isDone ? 'bg-muted-foreground/20 text-foreground'
1153
+ : 'bg-muted text-muted-foreground',
1154
+ ].join(' '), children: Icon ? _jsx(Icon, { className: "size-3", "aria-hidden": "true" }) : i + 1 }), _jsx("span", { className: "font-medium", children: String(s['label'] ?? `Step ${i + 1}`) })] }), i < steps.length - 1 && _jsx("span", { className: "h-px w-6 bg-border", "aria-hidden": "true" })] }, i));
1155
+ }) }), Boolean(steps[active]?.['description']) && (_jsx("p", { className: "text-sm text-muted-foreground", children: String(steps[active]['description']) })), steps.map((s, i) => (_jsx("div", { className: i === active ? 'flex flex-col gap-4' : 'hidden', "aria-hidden": i === active ? undefined : true, children: (s.children ?? []).map((c, ci) => renderElement(c, ci)) }, i))), advanceError && (_jsx("p", { className: "text-sm text-destructive", role: "alert", children: advanceError })), _jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx("button", { type: "button", disabled: isFirst || advancing, onClick: () => advance(active - 1), className: "rounded-md border border-border px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted disabled:opacity-50 disabled:cursor-not-allowed", children: "Back" }), isLast
1156
+ ? _jsx("span", { className: "text-xs text-muted-foreground", children: "Submit the form to finish." })
1157
+ : _jsx("button", { type: "button", disabled: advancing, onClick: () => advance(active + 1), className: "rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed", children: advancing ? 'Validating…' : 'Next' })] })] }, index));
1158
+ }
1159
+ // ─── Top-level dispatch ─────────────────────────────────────
1160
+ const TEXT_COLOR_CLASSES = {
1161
+ default: '',
1162
+ muted: 'text-muted-foreground',
1163
+ primary: 'text-primary',
1164
+ destructive: 'text-destructive',
1165
+ success: 'text-emerald-600 dark:text-emerald-400',
1166
+ warning: 'text-amber-600 dark:text-amber-400',
1167
+ info: 'text-blue-600 dark:text-blue-400',
1168
+ };
1169
+ const TEXT_SIZE_CLASSES = {
1170
+ xs: 'text-xs',
1171
+ sm: 'text-sm',
1172
+ base: 'text-base',
1173
+ lg: 'text-lg',
1174
+ xl: 'text-xl',
1175
+ };
1176
+ const TEXT_WEIGHT_CLASSES = {
1177
+ normal: 'font-normal',
1178
+ medium: 'font-medium',
1179
+ semibold: 'font-semibold',
1180
+ bold: 'font-bold',
1181
+ };
1182
+ function renderText(el, index) {
1183
+ const content = String(el['content'] ?? '');
1184
+ const color = el['color'] ? String(el['color']) : undefined;
1185
+ const size = el['size'] ? String(el['size']) : undefined;
1186
+ const weight = el['weight'] ? String(el['weight']) : undefined;
1187
+ const isBadge = el['badge'] === true;
1188
+ if (isBadge) {
1189
+ const badgeKey = el['badgeColor'] ? String(el['badgeColor']) : 'gray';
1190
+ const cls = BADGE_COLOR_CLASSES[badgeKey] ?? BADGE_COLOR_CLASSES['gray'];
1191
+ return (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}`, children: content }, index));
1192
+ }
1193
+ // Defaults match the previous bare `<p>` for back-compat: text-sm + muted.
1194
+ const sizeCls = size ? (TEXT_SIZE_CLASSES[size] ?? '') : 'text-sm';
1195
+ const colorCls = color ? (TEXT_COLOR_CLASSES[color] ?? '') : 'text-muted-foreground';
1196
+ const weightCls = weight ? (TEXT_WEIGHT_CLASSES[weight] ?? '') : '';
1197
+ return (_jsx("p", { className: `${sizeCls} ${colorCls} ${weightCls}`.trim(), children: content }, index));
1198
+ }
1199
+ /** Coerce a `KeyValueEntry` state value (object | JSON string | …) into a
1200
+ * flat record. Returns `null` when the value is empty or non-decodable. */
1201
+ function normalizeKeyValueValue(value) {
1202
+ if (value === null || value === undefined || value === '')
1203
+ return null;
1204
+ if (typeof value === 'string') {
1205
+ try {
1206
+ const parsed = JSON.parse(value);
1207
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
1208
+ return parsed;
1209
+ }
1210
+ }
1211
+ catch {
1212
+ // Non-JSON string — fall through to null so the renderer shows the
1213
+ // fallback rather than misrepresenting it as a one-row map.
1214
+ }
1215
+ return null;
1216
+ }
1217
+ if (Array.isArray(value))
1218
+ return null;
1219
+ if (typeof value === 'object')
1220
+ return value;
1221
+ return null;
1222
+ }
1223
+ /** Render a single kv cell value — primitives become their string form;
1224
+ * nested objects/arrays JSON-stringify for compactness. */
1225
+ function formatKeyValueCell(value) {
1226
+ if (value === null || value === undefined)
1227
+ return '';
1228
+ if (typeof value === 'object')
1229
+ return JSON.stringify(value);
1230
+ return String(value);
1231
+ }
1232
+ /**
1233
+ * Plan #16 — read-only label-value pair for `Resource.detail()` schemas.
1234
+ * Dispatches on `meta.entryType` (`'text' | 'badge' | 'icon' | 'image' | 'keyValue' | 'color'`).
1235
+ * Wraps the rendered value in `<EntryShell>` for the shared chrome
1236
+ * (label / helperText / tooltip / copyable trigger).
1237
+ */
1238
+ function renderEntry(el, index) {
1239
+ const entryType = String(el['entryType'] ?? 'text');
1240
+ const value = el['value'];
1241
+ const fallback = el['default'] ? String(el['default']) : '—';
1242
+ let body;
1243
+ switch (entryType) {
1244
+ case 'text': {
1245
+ const formatted = el['_formatted'] !== undefined
1246
+ ? String(el['_formatted'])
1247
+ : (el['format']
1248
+ ? applyColumnFormat(value, el['format'])
1249
+ : (value === null || value === undefined || value === '' ? '' : String(value)));
1250
+ const display = formatted === '' ? fallback : formatted;
1251
+ const isFallback = formatted === '';
1252
+ const isRichText = el['richtext'] === true && !isFallback;
1253
+ const sizeKey = el['size'] ? String(el['size']) : 'sm';
1254
+ const colorKey = el['color'] ? String(el['color']) : (isFallback ? 'muted' : 'default');
1255
+ const weightKey = el['weight'] ? String(el['weight']) : 'normal';
1256
+ const sizeCls = TEXT_SIZE_CLASSES[sizeKey] ?? 'text-sm';
1257
+ const colorCls = TEXT_COLOR_CLASSES[colorKey] ?? '';
1258
+ const weightCls = TEXT_WEIGHT_CLASSES[weightKey] ?? '';
1259
+ const lineClamp = el['lineClamp'];
1260
+ const wrap = el['wrap'] === true;
1261
+ const style = {};
1262
+ if (lineClamp !== undefined) {
1263
+ style.display = '-webkit-box';
1264
+ style.WebkitLineClamp = lineClamp;
1265
+ style.WebkitBoxOrient = 'vertical';
1266
+ style.overflow = 'hidden';
1267
+ }
1268
+ const wrapCls = wrap ? 'whitespace-pre-wrap' : (lineClamp !== undefined ? '' : 'whitespace-nowrap');
1269
+ if (isRichText) {
1270
+ // Server-rendered HTML from a registered richtext renderer (e.g.
1271
+ // `@pilotiq/tiptap`). Wrap in `prose` for sensible default
1272
+ // styling — matches the read-only `Markdown` / `Html` primes.
1273
+ const proseSize = sizeKey === 'lg' || sizeKey === 'xl'
1274
+ ? 'prose-lg'
1275
+ : sizeKey === 'sm' || sizeKey === 'xs'
1276
+ ? 'prose-sm'
1277
+ : '';
1278
+ body = (_jsx("div", { className: `prose max-w-none dark:prose-invert ${proseSize} ${colorCls} ${weightCls}`.trim(), style: style, dangerouslySetInnerHTML: { __html: display } }));
1279
+ break;
1280
+ }
1281
+ body = (_jsx("span", { className: `${sizeCls} ${colorCls} ${weightCls} ${wrapCls}`.trim(), style: style, children: display }));
1282
+ break;
1283
+ }
1284
+ case 'badge': {
1285
+ const isBlank = value === null || value === undefined || value === '';
1286
+ if (isBlank) {
1287
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1288
+ break;
1289
+ }
1290
+ const map = el['colors'] ?? {};
1291
+ const colorKey = map[String(value)] ?? 'gray';
1292
+ const cls = BADGE_COLOR_CLASSES[colorKey] ?? BADGE_COLOR_CLASSES['gray'];
1293
+ body = (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}`, children: String(value) }));
1294
+ break;
1295
+ }
1296
+ case 'icon': {
1297
+ const isBlank = value === null || value === undefined || value === '';
1298
+ const map = el['options'] ?? {};
1299
+ const opt = isBlank ? undefined : map[String(value)];
1300
+ if (!opt) {
1301
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1302
+ break;
1303
+ }
1304
+ const Icon = resolveIcon(opt.icon) ?? CircleIcon;
1305
+ const colorClass = opt.color ? (COLUMN_COLOR_CLASSES[opt.color] ?? '') : '';
1306
+ const ariaLabel = opt.label ?? String(value);
1307
+ body = _jsx(Icon, { className: `inline size-5 ${colorClass}`.trim(), "aria-label": ariaLabel });
1308
+ break;
1309
+ }
1310
+ case 'image': {
1311
+ const isBlank = value === null || value === undefined || value === '';
1312
+ if (isBlank) {
1313
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1314
+ break;
1315
+ }
1316
+ const url = String(value);
1317
+ const width = el['imageWidth'] ?? el['imageSize'] ?? 64;
1318
+ const height = el['imageHeight'] ?? el['imageSize'] ?? 64;
1319
+ const shape = String(el['imageShape'] ?? 'rounded');
1320
+ const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'square' ? '' : 'rounded-md';
1321
+ body = (_jsx("img", { src: url, alt: "", width: width, height: height, className: `inline-block object-cover ${shapeCls}`.trim() }));
1322
+ break;
1323
+ }
1324
+ case 'keyValue': {
1325
+ const parsed = normalizeKeyValueValue(value);
1326
+ const keys = parsed ? Object.keys(parsed) : [];
1327
+ if (!parsed || keys.length === 0) {
1328
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1329
+ break;
1330
+ }
1331
+ const keyLabel = el['keyLabel'] ? String(el['keyLabel']) : 'Key';
1332
+ const valueLabel = el['valueLabel'] ? String(el['valueLabel']) : 'Value';
1333
+ body = (_jsxs("table", { className: "w-full border border-border text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "bg-muted/50 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [_jsx("th", { className: "border-b border-border px-2 py-1", children: keyLabel }), _jsx("th", { className: "border-b border-border px-2 py-1", children: valueLabel })] }) }), _jsx("tbody", { children: keys.map(k => (_jsxs("tr", { className: "border-t border-border first:border-t-0", children: [_jsx("td", { className: "px-2 py-1 align-top font-mono text-xs", children: k }), _jsx("td", { className: "px-2 py-1 align-top font-mono text-xs break-all", children: formatKeyValueCell(parsed[k]) })] }, k))) })] }));
1334
+ break;
1335
+ }
1336
+ case 'color': {
1337
+ const isBlank = value === null || value === undefined || value === '';
1338
+ if (isBlank) {
1339
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1340
+ break;
1341
+ }
1342
+ const hex = String(value);
1343
+ const width = el['colorWidth'] ?? el['colorSize'] ?? 24;
1344
+ const height = el['colorHeight'] ?? el['colorSize'] ?? 24;
1345
+ const shape = String(el['colorShape'] ?? 'rounded');
1346
+ const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'square' ? '' : 'rounded-md';
1347
+ const showValue = el['showValue'] !== false;
1348
+ body = (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { className: `inline-block border border-border ${shapeCls}`.trim(), style: { width, height, backgroundColor: hex }, "aria-label": hex }), showValue && (_jsx("span", { className: "font-mono text-xs text-muted-foreground", children: hex }))] }));
1349
+ break;
1350
+ }
1351
+ case 'code': {
1352
+ const isBlank = value === null || value === undefined || value === '';
1353
+ if (isBlank) {
1354
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1355
+ break;
1356
+ }
1357
+ const text = typeof value === 'string' ? value : String(value);
1358
+ const lang = el['language'] ? String(el['language']) : undefined;
1359
+ body = (_jsx("pre", { className: "rounded-md border border-border bg-muted/40 p-3 text-xs overflow-x-auto", "data-language": lang, children: _jsx("code", { className: "font-mono", children: text }) }));
1360
+ break;
1361
+ }
1362
+ case 'component': {
1363
+ const componentName = String(el['component'] ?? '');
1364
+ if (!componentName) {
1365
+ body = (_jsxs(EntryComponentError, { children: ["ComponentEntry is missing its ", _jsx("code", { className: "font-mono", children: "component" }), " name \u2014 set ", _jsx("code", { className: "font-mono", children: "static componentName = '...'" }), " on the subclass or call ", _jsx("code", { className: "font-mono", children: ".component('...')" }), " in the fluent form."] }));
1366
+ break;
1367
+ }
1368
+ const Component = getEntryComponent(componentName);
1369
+ if (!Component) {
1370
+ body = (_jsxs(EntryComponentError, { children: ["No component registered under name ", _jsx("code", { className: "font-mono", children: componentName }), ". Register it at app boot:", _jsx("pre", { className: "mt-2 overflow-x-auto rounded bg-amber-100/60 p-2 text-xs dark:bg-amber-900/30", children: `import { registerEntryComponents } from '@pilotiq/pilotiq/entries'\nregisterEntryComponents({ ${componentName}: ${componentName} })` })] }));
1371
+ break;
1372
+ }
1373
+ // Render-time errors propagate to React's nearest error boundary —
1374
+ // surfacing them inline here would require wrapping every entry in
1375
+ // its own boundary, which v1 doesn't ship. The two pre-render
1376
+ // sentinels above (missing name / missing registration) cover the
1377
+ // typical wiring mistakes.
1378
+ body = _jsx(Component, { value: value });
1379
+ break;
1380
+ }
1381
+ case 'repeatable': {
1382
+ // Read-only sibling of `Repeater`. Reads `meta.rows` (resolved by
1383
+ // `resolveRepeatableRows`) and dispatches on the chosen layout —
1384
+ // `table > grid > stack`. Empty / non-array state falls through to
1385
+ // the inherited `default()` placeholder, same as every other entry.
1386
+ const rows = el['rows'] ?? [];
1387
+ if (rows.length === 0) {
1388
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1389
+ break;
1390
+ }
1391
+ const tableCfg = el['table'];
1392
+ const gridN = el['grid'];
1393
+ const innerCols = el['columns'];
1394
+ const contained = el['contained'] !== false;
1395
+ if (tableCfg && tableCfg.columns.length > 0) {
1396
+ const cols = tableCfg.columns;
1397
+ body = (_jsxs("table", { className: "w-full border border-border text-sm", children: [cols.some(c => c.width) && (_jsx("colgroup", { children: cols.map((c, i) => (_jsx("col", { style: c.width ? { width: c.width } : undefined }, i))) })), _jsx("thead", { children: _jsx("tr", { className: "bg-muted/50 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground", children: cols.map((c, i) => (_jsx("th", { className: `border-b border-border px-2 py-1 ${c.alignment === 'right' ? 'text-right' : c.alignment === 'center' ? 'text-center' : ''}`.trim(), children: c.label }, i))) }) }), _jsx("tbody", { children: rows.map(row => (_jsx("tr", { className: "border-t border-border first:border-t-0 align-top", children: row.children.map((child, i) => {
1398
+ const align = cols[i]?.alignment;
1399
+ const alignCls = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : '';
1400
+ return (_jsx("td", { className: `px-2 py-1 ${alignCls}`.trim(), children: renderElement(child, i) }, i));
1401
+ }) }, row.id))) })] }));
1402
+ break;
1403
+ }
1404
+ const cardCls = contained
1405
+ ? 'rounded-md border border-border p-3 bg-background'
1406
+ : '';
1407
+ const innerColsCls = innerCols && innerCols >= 2
1408
+ ? `grid gap-3 grid-cols-1 md:grid-cols-${Math.min(innerCols, 6)}`
1409
+ : 'space-y-2';
1410
+ const cards = rows.map(row => (_jsx("div", { className: `${cardCls} ${innerColsCls}`.trim(), children: row.children.map((child, i) => renderElement(child, i)) }, row.id)));
1411
+ if (gridN && gridN >= 2) {
1412
+ const cap = Math.min(gridN, 6);
1413
+ body = (_jsx("div", { className: `w-full grid gap-3 grid-cols-1 md:grid-cols-${cap}`, children: cards }));
1414
+ break;
1415
+ }
1416
+ body = _jsx("div", { className: "w-full space-y-3", children: cards });
1417
+ break;
1418
+ }
1419
+ default:
1420
+ body = _jsx("span", { className: "text-sm text-muted-foreground", children: fallback });
1421
+ }
1422
+ const copyable = el['copyable'];
1423
+ const copyValue = el['_formatted'] !== undefined
1424
+ ? String(el['_formatted'])
1425
+ : value === null || value === undefined
1426
+ ? ''
1427
+ : typeof value === 'object'
1428
+ ? JSON.stringify(value)
1429
+ : String(value);
1430
+ return (_jsx(EntryShell, { el: el, copyValue: copyable !== undefined ? copyValue : undefined, copyableLabel: copyable?.label, children: body }, index));
1431
+ }
1432
+ function EntryShell({ el, copyValue, copyableLabel, children }) {
1433
+ const label = String(el['label'] ?? '');
1434
+ const helperText = el['helperText'] ? String(el['helperText']) : undefined;
1435
+ const tooltipText = el['tooltip'] ? String(el['tooltip']) : undefined;
1436
+ const inline = el['inlineLabel'] === true;
1437
+ const labelNode = label ? (_jsxs("div", { className: "flex items-center gap-1.5 text-sm font-medium text-muted-foreground", children: [_jsx("span", { children: label }), tooltipText && _jsx(EntryTooltip, { text: tooltipText })] })) : null;
1438
+ const valueRow = (_jsxs("div", { className: "flex items-center gap-2", children: [children, copyValue !== undefined && (_jsx(EntryCopyButton, { text: copyValue, label: copyableLabel ?? 'Copy' }))] }));
1439
+ if (inline) {
1440
+ return (_jsxs("div", { className: "flex items-baseline gap-3", children: [labelNode && _jsx("div", { className: "min-w-32", children: labelNode }), _jsxs("div", { className: "min-w-0 flex-1", children: [valueRow, helperText && _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: helperText })] })] }));
1441
+ }
1442
+ return (_jsxs("div", { className: "space-y-1", children: [labelNode, valueRow, helperText && _jsx("p", { className: "text-xs text-muted-foreground", children: helperText })] }));
1443
+ }
1444
+ function EntryComponentError({ children }) {
1445
+ return (_jsx("div", { role: "alert", className: "rounded-md border border-amber-500/40 bg-amber-50 p-3 text-sm text-amber-800 dark:bg-amber-950/30 dark:text-amber-200", children: children }));
1446
+ }
1447
+ function EntryTooltip({ text }) {
1448
+ const trigger = (_jsx("button", { type: "button", className: "inline-flex h-3.5 w-3.5 items-center justify-center rounded-full border text-[10px] text-muted-foreground", "aria-label": text, children: "?" }));
1449
+ return (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: () => trigger }), _jsx(TooltipContent, { children: text })] }) }));
1450
+ }
1451
+ function EntryCopyButton({ text, label }) {
1452
+ const [copied, setCopied] = useState(false);
1453
+ const handleClick = () => {
1454
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
1455
+ navigator.clipboard.writeText(text).then(() => {
1456
+ setCopied(true);
1457
+ setTimeout(() => setCopied(false), 1500);
1458
+ }).catch(() => { });
1459
+ }
1460
+ };
1461
+ return (_jsx("button", { type: "button", onClick: handleClick, "aria-label": label, title: label, className: "inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted", children: copied ? _jsx(CheckIcon, { className: "size-3.5" }) : _jsx(CopyIcon, { className: "size-3.5" }) }));
1462
+ }
1463
+ function renderElement(el, index) {
1464
+ switch (el.type) {
1465
+ case 'text':
1466
+ return renderText(el, index);
1467
+ case 'image': {
1468
+ const url = String(el['url'] ?? '');
1469
+ const alt = String(el['alt'] ?? '');
1470
+ const width = el['width'];
1471
+ const height = el['height'];
1472
+ const shape = String(el['shape'] ?? 'square');
1473
+ const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'rounded' ? 'rounded-md' : '';
1474
+ return (_jsx("img", { src: url, alt: alt, ...(width !== undefined ? { width } : {}), ...(height !== undefined ? { height } : {}), className: `inline-block object-cover ${shapeCls}` }, index));
1475
+ }
1476
+ case 'icon': {
1477
+ const name = el['name'] ? String(el['name']) : undefined;
1478
+ const size = el['size'] ?? 16;
1479
+ const color = String(el['color'] ?? 'default');
1480
+ const label = el['label'] ? String(el['label']) : undefined;
1481
+ const Cmp = resolveIcon(name);
1482
+ if (!Cmp)
1483
+ return null;
1484
+ const colorClass = COLUMN_COLOR_CLASSES[color] ?? '';
1485
+ return (_jsx(Cmp, { className: `inline ${colorClass}`, ...(label ? { 'aria-label': label } : { 'aria-hidden': true }), style: { width: size, height: size } }, index));
1486
+ }
1487
+ case 'markdown':
1488
+ case 'html': {
1489
+ const html = String(el['html'] ?? '');
1490
+ const prose = el['prose'] !== false;
1491
+ const size = el['size'] ? String(el['size']) : undefined;
1492
+ const proseCls = prose
1493
+ ? `prose max-w-none ${size === 'sm' ? 'prose-sm' : size === 'lg' ? 'prose-lg' : ''}`.trim()
1494
+ : '';
1495
+ return (_jsx("div", { className: proseCls || undefined, dangerouslySetInnerHTML: { __html: html } }, index));
1496
+ }
1497
+ case 'heading': {
1498
+ const level = el['level'] ?? 1;
1499
+ const content = String(el['content'] ?? '');
1500
+ const description = el['description'] ? String(el['description']) : undefined;
1501
+ const headerActions = (el.children ?? []).filter(c => c.type === 'action' || c.type === 'actionGroup');
1502
+ const Tag = level === 1 ? 'h1' : level === 2 ? 'h2' : 'h3';
1503
+ const sizes = { 1: 'text-2xl', 2: 'text-xl', 3: 'text-lg' };
1504
+ const titleBlock = (_jsxs("div", { children: [_jsx(Tag, { className: `${sizes[level]} font-bold tracking-tight`, children: content }), description && (_jsx("p", { className: "text-sm text-muted-foreground mt-1", children: description }))] }));
1505
+ if (headerActions.length === 0) {
1506
+ return _jsx("div", { children: titleBlock }, index);
1507
+ }
1508
+ return (_jsxs("div", { className: "flex items-start justify-between gap-4", children: [titleBlock, _jsx("div", { className: "flex items-center gap-2 shrink-0", children: headerActions.map((a, i) => renderActionLike(a, i)) })] }, index));
1509
+ }
1510
+ case 'alert': {
1511
+ const alertType = String(el['alertType'] ?? 'info');
1512
+ const styles = alertStyles[alertType] ?? alertStyles['info'];
1513
+ const title = el['title'] ? String(el['title']) : undefined;
1514
+ const footer = (el.children ?? []).filter(c => c.type === 'action' || c.type === 'actionGroup');
1515
+ return (_jsxs("div", { className: `rounded-lg border p-4 ${styles}`, children: [title && _jsx("p", { className: "font-medium mb-1", children: title }), _jsx("p", { className: "text-sm", children: String(el['content'] ?? '') }), footer.length > 0 && (_jsx("div", { className: "flex items-center gap-2 mt-3", children: footer.map((a, i) => renderActionLike(a, i)) }))] }, index));
1516
+ }
1517
+ case 'emptyState': {
1518
+ const heading = String(el['heading'] ?? '');
1519
+ const description = el['description'] ? String(el['description']) : undefined;
1520
+ const iconName = el['icon'] ? String(el['icon']) : undefined;
1521
+ const contained = el['contained'] !== false;
1522
+ const Icon = iconName ? resolveIcon(iconName) : undefined;
1523
+ const footer = (el.children ?? []).filter(c => c.type === 'action' || c.type === 'actionGroup');
1524
+ const wrapper = contained
1525
+ ? 'rounded-lg border border-border bg-card text-card-foreground py-12 px-6'
1526
+ : 'py-8';
1527
+ return (_jsxs("div", { className: `${wrapper} flex flex-col items-center text-center gap-3`, children: [Icon && _jsx(Icon, { className: "size-10 text-muted-foreground", "aria-hidden": "true" }), _jsx("h3", { className: "text-lg font-semibold", children: heading }), description && _jsx("p", { className: "text-sm text-muted-foreground max-w-md", children: description }), footer.length > 0 && (_jsx("div", { className: "flex items-center gap-2 mt-2", children: footer.map((a, i) => renderActionLike(a, i)) }))] }, index));
1528
+ }
1529
+ case 'divider': {
1530
+ const label = el['label'] ? String(el['label']) : undefined;
1531
+ return label
1532
+ ? _jsxs("div", { className: "relative py-2", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("span", { className: "w-full border-t border-border" }) }), _jsx("div", { className: "relative flex justify-center", children: _jsx("span", { className: "bg-background px-2 text-xs text-muted-foreground", children: label }) })] }, index)
1533
+ : _jsx("hr", { className: "border-border" }, index);
1534
+ }
1535
+ case 'unorderedList': {
1536
+ const items = el['items'] ?? [];
1537
+ const color = el['color'] ? String(el['color']) : undefined;
1538
+ const size = el['size'] ? String(el['size']) : undefined;
1539
+ const weight = el['weight'] ? String(el['weight']) : undefined;
1540
+ const sizeCls = size ? (TEXT_SIZE_CLASSES[size] ?? '') : 'text-sm';
1541
+ const colorCls = color ? (TEXT_COLOR_CLASSES[color] ?? '') : '';
1542
+ const weightCls = weight ? (TEXT_WEIGHT_CLASSES[weight] ?? '') : '';
1543
+ return (_jsx("ul", { className: `list-disc list-inside space-y-1 ${sizeCls} ${colorCls} ${weightCls}`.trim(), children: items.map((item, i) => (_jsx("li", { children: String(item) }, i))) }, index));
1544
+ }
1545
+ case 'card': {
1546
+ const title = el['title'] ? String(el['title']) : undefined;
1547
+ const description = el['description'] ? String(el['description']) : undefined;
1548
+ return (_jsxs("div", { className: "rounded-xl border bg-card p-6 shadow-sm", children: [title && _jsx("h3", { className: "font-semibold mb-1", children: title }), description && _jsx("p", { className: "text-sm text-muted-foreground mb-4", children: description }), renderChildren(el.children)] }, index));
1549
+ }
1550
+ case 'section':
1551
+ return _jsx(SectionRenderer, { el: el, index: index }, index);
1552
+ case 'tabs':
1553
+ return _jsx(TabsRenderer, { el: el, index: index }, index);
1554
+ case 'tab':
1555
+ // Tabs are rendered by their parent `tabs` element; standalone Tab is a no-op.
1556
+ return null;
1557
+ case 'listTabs':
1558
+ return _jsx(ListTabsRenderer, { el: el }, index);
1559
+ case 'relation-tabs':
1560
+ return _jsx(RelationTabsRenderer, { el: el }, index);
1561
+ case 'breadcrumbs':
1562
+ return _jsx(BreadcrumbsRenderer, { el: el }, index);
1563
+ case 'listTab':
1564
+ // List tabs are rendered by their parent `listTabs` strip; standalone is a no-op.
1565
+ return null;
1566
+ case 'grid': {
1567
+ const columns = Math.max(1, Math.min(12, Number(el['columns'] ?? 2)));
1568
+ const gapPx = el['gap'] !== undefined ? `${Number(el['gap'])}px` : undefined;
1569
+ return (_jsx("div", { className: `grid gap-4 ${layoutClasses(el)}`.trim(), style: {
1570
+ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
1571
+ ...(gapPx ? { gap: gapPx } : {}),
1572
+ }, children: (el.children ?? []).map((c, i) => renderElement(c, i)) }, index));
1573
+ }
1574
+ case 'group': {
1575
+ const layout = layoutClasses(el);
1576
+ return (_jsx("div", { className: layout || undefined, children: renderChildren(el.children) }, index));
1577
+ }
1578
+ case 'split': {
1579
+ const from = el['from'] === 'left' ? 'left' : 'right';
1580
+ const gap = Math.max(0, Math.min(12, Number(el['gap'] ?? 6)));
1581
+ const children = el.children ?? [];
1582
+ // Find the explicit aside child first; fall back to "second child is
1583
+ // aside" so terse Split.make().schema([main, aside]) still works.
1584
+ let asideIdx = children.findIndex(c => c['aside'] === true);
1585
+ if (asideIdx === -1 && children.length >= 2)
1586
+ asideIdx = 1;
1587
+ const mainChildren = children.filter((_, i) => i !== asideIdx);
1588
+ const asideChild = asideIdx >= 0 ? children[asideIdx] : undefined;
1589
+ const orderClasses = from === 'left'
1590
+ ? { aside: '@md:order-first', main: '@md:order-last' }
1591
+ : { aside: '@md:order-last', main: '@md:order-first' };
1592
+ return (_jsxs("div", { className: `@container flex flex-col @md:flex-row gap-${gap} ${layoutClasses(el)}`.trim(), children: [_jsx("div", { className: `flex flex-col gap-4 flex-1 min-w-0 ${orderClasses.main}`, children: mainChildren.map((c, i) => renderElement(c, i)) }), asideChild && (_jsx("aside", { className: `flex flex-col gap-4 @md:w-80 @md:shrink-0 ${orderClasses.aside}`, children: renderElement(asideChild, asideIdx) }))] }, index));
1593
+ }
1594
+ case 'fieldset': {
1595
+ const label = String(el['label'] ?? '');
1596
+ const columns = Math.max(1, Math.min(3, Number(el['columns'] ?? 1)));
1597
+ const gridStyle = columns > 1
1598
+ ? { display: 'grid', gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`, gap: '1rem' }
1599
+ : undefined;
1600
+ return (_jsxs("fieldset", { className: `rounded-md border border-border px-4 pt-3 pb-4 ${layoutClasses(el)}`.trim(), children: [label && _jsx("legend", { className: "px-1 text-xs font-medium text-muted-foreground", children: label }), _jsx("div", { className: columns === 1 ? 'flex flex-col gap-3' : undefined, style: gridStyle, children: (el.children ?? []).map((c, i) => renderElement(c, i)) })] }, index));
1601
+ }
1602
+ case 'wizard':
1603
+ return _jsx(WizardRenderer, { el: el, index: index }, index);
1604
+ case 'step':
1605
+ // Steps are rendered by their parent Wizard; standalone Step is a no-op.
1606
+ return null;
1607
+ case 'field':
1608
+ return renderField(el, index);
1609
+ case 'entry':
1610
+ return renderEntry(el, index);
1611
+ case 'action':
1612
+ return renderAction(el, index);
1613
+ case 'actionGroup':
1614
+ return _jsx(ActionGroupTrigger, { el: el }, index);
1615
+ case 'form': {
1616
+ // Key on formId so SPA navigation between pages with different
1617
+ // forms (list → edit, edit → edit-of-different-record, etc.)
1618
+ // forces a fresh React mount. Form fields are uncontrolled
1619
+ // (`defaultValue`), so without remount, prop updates wouldn't
1620
+ // propagate into the rendered <input>s — the form would render
1621
+ // with stale or empty values.
1622
+ const formId = String(el['formId'] ?? index);
1623
+ return _jsx(FormRenderer, { el: el }, formId);
1624
+ }
1625
+ case 'table':
1626
+ return _jsx(TableRenderer, { el: el }, index);
1627
+ case 'column':
1628
+ // Columns are rendered by their parent table; standalone column is a no-op.
1629
+ return null;
1630
+ case 'stats':
1631
+ return _jsx(StatsOverviewRenderer, { meta: el }, index);
1632
+ case 'tableWidget':
1633
+ return _jsx(TableWidgetRenderer, { meta: el }, index);
1634
+ case 'view':
1635
+ return _jsx(ViewRenderer, { meta: el }, index);
1636
+ default: {
1637
+ // Plan #15 Phase C — server-data widget elements registered by
1638
+ // adapter packages (`@pilotiq/recharts` for `'chart'`, future
1639
+ // `@pilotiq/chartjs`, etc.) dispatch through the runtime widget
1640
+ // registry. The fallback error message points the consumer at the
1641
+ // install command — silent `null` here would let a missing
1642
+ // `registerChartRenderer()` slip through.
1643
+ if (el['serverData'] === true) {
1644
+ const widgetType = String(el.type ?? '');
1645
+ const Renderer = getWidgetRenderer(widgetType);
1646
+ if (Renderer)
1647
+ return _jsx(Renderer, { meta: el }, index);
1648
+ return (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-50 p-3 text-sm text-amber-800 dark:bg-amber-950/30 dark:text-amber-200", role: "alert", children: ["No renderer registered for widget type ", _jsx("code", { className: "font-mono", children: widgetType }), ".", widgetType === 'chart' && (_jsxs(_Fragment, { children: [" Install ", _jsx("code", { className: "font-mono", children: "@pilotiq/recharts" }), " and call ", _jsx("code", { className: "font-mono", children: "registerChartRenderer()" }), " at app boot."] }))] }, index));
1649
+ }
1650
+ return null;
1651
+ }
1652
+ }
1653
+ }
1654
+ // ─── Form ───────────────────────────────────────────────────
1655
+ function FormRenderer({ el }) {
1656
+ const formId = String(el['formId'] ?? '');
1657
+ const method = String(el['method'] ?? 'post').toLowerCase();
1658
+ const action = el['action'] ? String(el['action']) : undefined;
1659
+ const stateUrl = el['stateUrl'] ? String(el['stateUrl']) : undefined;
1660
+ const serverValues = el['values'] ?? {};
1661
+ const serverErrors = el['errors'] ?? {};
1662
+ // Methods other than GET/POST are spoofed via _method, mirroring Laravel.
1663
+ const httpMethod = method === 'get' ? 'get' : 'post';
1664
+ const spoofedMethod = method !== 'get' && method !== 'post' ? method : undefined;
1665
+ const navigate = useNavigate();
1666
+ const { notify } = useToast();
1667
+ // Client-side errors override server-rendered ones after a fetch-mode
1668
+ // 422 response. Field values stay uncontrolled — the inputs in the DOM
1669
+ // still hold whatever the user typed, so we don't need to mirror them.
1670
+ const [clientErrors, setClientErrors] = useState(null);
1671
+ const [submitting, setSubmitting] = useState(false);
1672
+ const errors = clientErrors ?? serverErrors;
1673
+ // Plan #14 — formRef is threaded into FormStateProvider so live triggers
1674
+ // can snapshot the form's full DOM state via FormData (captures
1675
+ // uncontrolled inner-Repeater inputs that don't participate in the
1676
+ // controlled values map).
1677
+ const formRef = useRef(null);
1678
+ const formErrors = errors['_form'] ?? [];
1679
+ const hasFieldErrors = Object.keys(errors).some(k => k !== '_form');
1680
+ const onSubmit = async (e) => {
1681
+ if (!action)
1682
+ return; // no action URL → fall through to native submit
1683
+ e.preventDefault();
1684
+ if (submitting)
1685
+ return;
1686
+ setSubmitting(true);
1687
+ setClientErrors(null);
1688
+ try {
1689
+ // Thread `event.submitter` so the clicked submit button's
1690
+ // name/value pair lands in the FormData. Without this, secondary
1691
+ // submits like "Create & create another" can't signal which
1692
+ // button fired through the body. Supported in all evergreen
1693
+ // browsers since 2022; cast through `as any` because TS lib.dom
1694
+ // hasn't picked up the optional submitter argument on every
1695
+ // version.
1696
+ const submitter = e.nativeEvent.submitter;
1697
+ const fd = new FormData(e.currentTarget, submitter ?? undefined);
1698
+ const res = await fetch(action, {
1699
+ method: 'POST',
1700
+ headers: { 'Accept': 'application/json' },
1701
+ body: fd,
1702
+ });
1703
+ const data = await res.json().catch(() => ({}));
1704
+ if (res.status === 422) {
1705
+ const next = data.errors ?? {};
1706
+ setClientErrors(next);
1707
+ // Surface a banner-level message if no field errors were returned
1708
+ // — the form-level _form key lights up the existing banner.
1709
+ setSubmitting(false);
1710
+ return;
1711
+ }
1712
+ if (!res.ok) {
1713
+ const message = String(data.error ?? `Request failed (${res.status})`);
1714
+ notify({ type: 'error', title: 'Save failed', body: message });
1715
+ setSubmitting(false);
1716
+ return;
1717
+ }
1718
+ // Success — drain notifications and SPA-navigate to the redirect.
1719
+ const notifs = data.notifications;
1720
+ if (notifs && notifs.length > 0)
1721
+ for (const n of notifs)
1722
+ notify(n);
1723
+ const redirect = String(data.redirect ?? '');
1724
+ // The server may force a navigate even when the redirect equals
1725
+ // the current URL — used by "Create & create another" so the
1726
+ // form remounts with empty defaults instead of preserving the
1727
+ // just-submitted values. Otherwise: skip navigate when the
1728
+ // redirect matches the current URL, since re-fetching the same
1729
+ // page would force a form remount and reset scroll.
1730
+ const force = Boolean(data.force);
1731
+ const currentUrl = typeof window !== 'undefined'
1732
+ ? window.location.pathname + window.location.search
1733
+ : '';
1734
+ if (redirect && (force || redirect !== currentUrl)) {
1735
+ navigate(redirect);
1736
+ // Don't reset submitting on success — the navigation will unmount us.
1737
+ }
1738
+ else {
1739
+ setSubmitting(false);
1740
+ }
1741
+ }
1742
+ catch (err) {
1743
+ notify({ type: 'error', title: 'Save failed', body: err instanceof Error ? err.message : String(err) });
1744
+ setSubmitting(false);
1745
+ }
1746
+ };
1747
+ return (_jsxs("form", { ref: formRef, id: formId || undefined, "data-form-id": formId || undefined, method: httpMethod, action: action, onSubmit: onSubmit, className: "flex flex-col gap-6", children: [formId && _jsx("input", { type: "hidden", name: "_formId", value: formId }), spoofedMethod && _jsx("input", { type: "hidden", name: "_method", value: spoofedMethod }), (formErrors.length > 0 || hasFieldErrors) && (_jsx("div", { className: "rounded-lg border border-destructive/40 bg-destructive/5 text-destructive p-3 text-sm", children: formErrors.length > 0 ? (_jsx("ul", { className: "list-disc pl-4", children: formErrors.map((msg, i) => _jsx("li", { children: msg }, i)) })) : ('Please correct the errors below.') })), _jsx(FormIdContext.Provider, { value: formId, children: stateUrl ? (_jsx(FormStateProvider, { initialMeta: el, initialErrors: errors, formRef: formRef, children: _jsx(FormBody, { fallbackChildren: el.children ?? [], fallbackValues: serverValues, fallbackErrors: errors }) })) : ((el.children ?? []).map((child, i) => renderFormChild(child, i, serverValues, errors))) })] }));
1748
+ }
1749
+ /**
1750
+ * Renders the controlled-form's children, sourcing them from the
1751
+ * `FormStateProvider`'s current `formMeta` (which gets replaced after
1752
+ * each live POST). Falls back to the props if (somehow) used outside a
1753
+ * provider — the shell only mounts this when `stateUrl` is set so the
1754
+ * fallback path is dead code in practice, but keeping it defensive.
1755
+ */
1756
+ function FormBody({ fallbackChildren, fallbackValues, fallbackErrors, }) {
1757
+ const ctx = useFormState();
1758
+ if (!ctx) {
1759
+ return _jsx(_Fragment, { children: fallbackChildren.map((child, i) => renderFormChild(child, i, fallbackValues, fallbackErrors)) });
1760
+ }
1761
+ const children = (ctx.formMeta.children ?? []);
1762
+ return _jsx(_Fragment, { children: children.map((child, i) => renderFormChild(child, i, ctx.values, ctx.errors)) });
1763
+ }
1764
+ /**
1765
+ * Render one child of a form's resolved schema with per-field values + errors.
1766
+ *
1767
+ * Exported so sibling renderers (e.g. `SelectFieldInput`'s inline-create
1768
+ * modal) can render a sub-schema with the same FieldShell + error-stamping
1769
+ * conventions as the parent form. Public surface beyond the file boundary
1770
+ * stays narrow — callers should pass `child.type === 'field'` elements;
1771
+ * non-field elements fall through to `renderElement`.
1772
+ */
1773
+ export function renderFormChild(child, index, values, errors) {
1774
+ if (child.type === 'field') {
1775
+ const name = String(child['name'] ?? '');
1776
+ const fieldErrors = errors[name] ?? [];
1777
+ const value = values[name];
1778
+ return (_jsxs("div", { className: "flex flex-col gap-1", children: [renderFieldWithValue(child, index, value), fieldErrors.map((msg, i) => (_jsx("p", { className: "text-xs text-destructive", children: msg }, i)))] }, index));
1779
+ }
1780
+ return renderElement(child, index);
1781
+ }
1782
+ function renderFieldWithValue(el, index, value) {
1783
+ // The form-state value (from `withValues` / record-fill) wins when present;
1784
+ // otherwise the meta's own `defaultValue` (Plan #6 `Field.default()`) survives.
1785
+ const enriched = value !== undefined
1786
+ ? { ...el, defaultValue: value }
1787
+ : el;
1788
+ return renderField(enriched, index);
1789
+ }
1790
+ // Mirror of `prefixedKey` in `elements/dispatchTable.ts`. Kept inline so
1791
+ // SchemaRenderer doesn't drag the server-side dispatcher into the client
1792
+ // bundle.
1793
+ function prefixK(prefix, key) {
1794
+ return prefix === undefined || prefix === '' ? key : `${prefix}_${key}`;
1795
+ }
1796
+ let cachedSearchString = null;
1797
+ let cachedSearchParams = null;
1798
+ function getCurrentSearchParams() {
1799
+ if (typeof window === 'undefined')
1800
+ return null;
1801
+ const s = window.location.search;
1802
+ if (s === cachedSearchString && cachedSearchParams)
1803
+ return cachedSearchParams;
1804
+ cachedSearchString = s;
1805
+ cachedSearchParams = new URLSearchParams(s);
1806
+ return cachedSearchParams;
1807
+ }
1808
+ function SearchFormHiddenInputs({ prefix }) {
1809
+ const sp = getCurrentSearchParams();
1810
+ if (!sp)
1811
+ return _jsx(_Fragment, {});
1812
+ const searchKey = prefixK(prefix, 'search');
1813
+ const pageKey = prefixK(prefix, 'page');
1814
+ const inputs = [];
1815
+ let i = 0;
1816
+ for (const [k, v] of sp) {
1817
+ if (k === searchKey || k === pageKey)
1818
+ continue;
1819
+ inputs.push(_jsx("input", { type: "hidden", name: k, value: v }, i++));
1820
+ }
1821
+ return _jsx(_Fragment, { children: inputs });
1822
+ }
1823
+ function buildTableQuery(state, override, pathname, filterValues = {}, prefix) {
1824
+ const merged = { ...state, ...override };
1825
+ const params = new URLSearchParams();
1826
+ // Foreign URL params (other tables' state, app-level params) round-trip
1827
+ // verbatim so this builder only ever rewrites its own slice.
1828
+ const currentParams = getCurrentSearchParams();
1829
+ if (currentParams) {
1830
+ const ours = new Set([
1831
+ prefixK(prefix, 'search'),
1832
+ prefixK(prefix, 'sort'),
1833
+ prefixK(prefix, 'page'),
1834
+ prefixK(prefix, 'perPage'),
1835
+ prefixK(prefix, 'group'),
1836
+ ...Object.keys(filterValues).map(n => prefixK(prefix, n)),
1837
+ ]);
1838
+ for (const [k, v] of currentParams) {
1839
+ if (ours.has(k))
1840
+ continue;
1841
+ params.set(k, v);
1842
+ }
1843
+ }
1844
+ // Carry forward active filter values so sort/pagination links don't
1845
+ // accidentally clear them. Filter names can't collide with reserved
1846
+ // keys (search/sort/page/perPage/group) — that's enforced upstream.
1847
+ for (const [name, val] of Object.entries(filterValues)) {
1848
+ if (val)
1849
+ params.set(prefixK(prefix, name), val);
1850
+ }
1851
+ if (merged.search)
1852
+ params.set(prefixK(prefix, 'search'), merged.search);
1853
+ if (merged.sort)
1854
+ params.set(prefixK(prefix, 'sort'), `${merged.sort.column}:${merged.sort.direction}`);
1855
+ if (merged.page && merged.page > 1)
1856
+ params.set(prefixK(prefix, 'page'), String(merged.page));
1857
+ if (merged.group !== undefined)
1858
+ params.set(prefixK(prefix, 'group'), merged.group);
1859
+ const qs = params.toString();
1860
+ // Always anchor to a real pathname — Vike's client-side router treats
1861
+ // a bare `?qs` href as a fresh URL with empty pathname, which routes
1862
+ // to the dashboard and blanks the page during SPA navigation.
1863
+ const base = pathname || (typeof window !== 'undefined' ? window.location.pathname : '');
1864
+ return qs ? `${base}?${qs}` : (base || '#');
1865
+ }
1866
+ function nextSortDir(current, column) {
1867
+ if (current?.column === column) {
1868
+ return { column, direction: current.direction === 'asc' ? 'desc' : 'asc' };
1869
+ }
1870
+ return { column, direction: 'asc' };
1871
+ }
1872
+ /** Map ColumnColor → tailwind text-color class. Used by TextColumn and
1873
+ * IconColumn alike. */
1874
+ const COLUMN_COLOR_CLASSES = {
1875
+ default: '',
1876
+ muted: 'text-muted-foreground',
1877
+ primary: 'text-primary',
1878
+ destructive: 'text-destructive',
1879
+ success: 'text-emerald-600 dark:text-emerald-400',
1880
+ warning: 'text-amber-600 dark:text-amber-400',
1881
+ info: 'text-blue-600 dark:text-blue-400',
1882
+ };
1883
+ const COLUMN_WEIGHT_CLASSES = {
1884
+ normal: 'font-normal',
1885
+ medium: 'font-medium',
1886
+ semibold: 'font-semibold',
1887
+ bold: 'font-bold',
1888
+ };
1889
+ const BADGE_COLOR_CLASSES = {
1890
+ gray: 'bg-muted text-muted-foreground',
1891
+ primary: 'bg-primary/10 text-primary',
1892
+ success: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-200',
1893
+ warning: 'bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-200',
1894
+ destructive: 'bg-destructive/10 text-destructive',
1895
+ info: 'bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-200',
1896
+ };
1897
+ /** Apply a built-in `ColumnFormat` to a raw value; returns a string. */
1898
+ function applyColumnFormat(value, format) {
1899
+ if (value === null || value === undefined || value === '')
1900
+ return '';
1901
+ switch (format['kind']) {
1902
+ case 'dateTime': {
1903
+ const d = value instanceof Date ? value : new Date(String(value));
1904
+ if (isNaN(d.getTime()))
1905
+ return String(value);
1906
+ // Default — locale-aware short date+time. Custom patterns aren't
1907
+ // supported (no date-fns dep); pattern is kept on meta for future use.
1908
+ return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
1909
+ }
1910
+ case 'since': {
1911
+ const d = value instanceof Date ? value : new Date(String(value));
1912
+ if (isNaN(d.getTime()))
1913
+ return String(value);
1914
+ const seconds = Math.round((Date.now() - d.getTime()) / 1000);
1915
+ const abs = Math.abs(seconds);
1916
+ const past = seconds >= 0;
1917
+ const fmt = (n, unit) => past ? `${n} ${unit}${n === 1 ? '' : 's'} ago` : `in ${n} ${unit}${n === 1 ? '' : 's'}`;
1918
+ if (abs < 60)
1919
+ return past ? 'just now' : 'in a moment';
1920
+ if (abs < 3600)
1921
+ return fmt(Math.floor(abs / 60), 'minute');
1922
+ if (abs < 86400)
1923
+ return fmt(Math.floor(abs / 3600), 'hour');
1924
+ if (abs < 2592000)
1925
+ return fmt(Math.floor(abs / 86400), 'day');
1926
+ if (abs < 31536000)
1927
+ return fmt(Math.floor(abs / 2592000), 'month');
1928
+ return fmt(Math.floor(abs / 31536000), 'year');
1929
+ }
1930
+ case 'money': {
1931
+ const n = typeof value === 'number' ? value : Number(value);
1932
+ if (isNaN(n))
1933
+ return String(value);
1934
+ const currency = String(format['currency'] ?? 'USD');
1935
+ const locale = format['locale'];
1936
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(n);
1937
+ }
1938
+ case 'numeric': {
1939
+ const n = typeof value === 'number' ? value : Number(value);
1940
+ if (isNaN(n))
1941
+ return String(value);
1942
+ const decimals = format['decimals'];
1943
+ const locale = format['locale'];
1944
+ const opts = {};
1945
+ if (decimals !== undefined) {
1946
+ opts.minimumFractionDigits = decimals;
1947
+ opts.maximumFractionDigits = decimals;
1948
+ }
1949
+ return new Intl.NumberFormat(locale, opts).format(n);
1950
+ }
1951
+ case 'limit': {
1952
+ const s = String(value);
1953
+ const n = format['chars'];
1954
+ return s.length > n ? s.slice(0, n) + '…' : s;
1955
+ }
1956
+ case 'words': {
1957
+ const s = String(value).trim();
1958
+ if (s.length === 0)
1959
+ return s;
1960
+ const tokens = s.split(/\s+/);
1961
+ const n = format['words'];
1962
+ return tokens.length > n ? tokens.slice(0, n).join(' ') + '…' : s;
1963
+ }
1964
+ default:
1965
+ return String(value);
1966
+ }
1967
+ }
1968
+ /** Render a cell. Honors the column's `columnType` (badge/icon/boolean/
1969
+ * image), built-in `format` spec, and per-row `_formatted[name]`
1970
+ * overrides from server-side `formatStateUsing` callbacks. */
1971
+ function formatCell(value, col, row) {
1972
+ if (col === undefined) {
1973
+ // Legacy raw-value fallback for non-column callsites.
1974
+ if (value === null || value === undefined)
1975
+ return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
1976
+ if (value instanceof Date)
1977
+ return value.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' });
1978
+ if (typeof value === 'boolean')
1979
+ return value ? 'Yes' : 'No';
1980
+ if (typeof value === 'object')
1981
+ return JSON.stringify(value);
1982
+ return String(value);
1983
+ }
1984
+ const columnType = String(col['columnType'] ?? 'text');
1985
+ const fallback = col['default'];
1986
+ // Per-row server-eval result wins over everything.
1987
+ const colName = String(col['name'] ?? '');
1988
+ const formatted = row?.['_formatted']?.[colName];
1989
+ const richtext = row?.['_richtextCells']?.[colName] === true;
1990
+ const isBlank = value === null || value === undefined || value === '';
1991
+ if (formatted !== undefined && formatted !== '') {
1992
+ return wrapCell(formatted, col, richtext);
1993
+ }
1994
+ if (isBlank) {
1995
+ return _jsx("span", { className: "text-muted-foreground", children: fallback ?? '—' });
1996
+ }
1997
+ switch (columnType) {
1998
+ case 'badge': {
1999
+ const map = col['badgeColors'] ?? {};
2000
+ const color = map[String(value)] ?? 'gray';
2001
+ const cls = BADGE_COLOR_CLASSES[color] ?? BADGE_COLOR_CLASSES['gray'];
2002
+ return (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}`, children: String(value) }));
2003
+ }
2004
+ case 'icon':
2005
+ case 'boolean': {
2006
+ const map = col['iconOptions'] ?? {};
2007
+ const opt = map[String(value)];
2008
+ if (!opt)
2009
+ return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
2010
+ const Icon = resolveIcon(opt.icon) ?? CircleIcon;
2011
+ const colorClass = opt.color ? (COLUMN_COLOR_CLASSES[opt.color] ?? '') : '';
2012
+ return _jsx(Icon, { className: `size-4 inline ${colorClass}`, "aria-label": String(value) });
2013
+ }
2014
+ case 'image': {
2015
+ const url = String(value);
2016
+ const size = col['imageSize'] ?? 32;
2017
+ const shape = col['imageShape'] === 'circle' ? 'rounded-full' : 'rounded-md';
2018
+ return (_jsx("img", { src: url, alt: "", width: size, height: size, className: `${shape} object-cover` }));
2019
+ }
2020
+ case 'color': {
2021
+ const css = String(value);
2022
+ const shape = col['colorShape'];
2023
+ const shapeClass = shape === 'circle' ? 'rounded-full' :
2024
+ shape === 'square' ? 'rounded-none' : 'rounded';
2025
+ const hideValue = col['colorHideValue'] === true;
2026
+ return (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { className: `size-4 border border-border ${shapeClass}`, style: { backgroundColor: css }, "aria-hidden": "true" }), !hideValue && _jsx("span", { className: "text-sm", children: css })] }));
2027
+ }
2028
+ default: {
2029
+ // Array-valued cells — `bulleted()` wins over `listWithLineBreaks()`
2030
+ // when both are set. Falls through to the standard string path for
2031
+ // non-array values so the per-cell formatters keep working.
2032
+ if (Array.isArray(value)) {
2033
+ const items = value.map(v => String(v));
2034
+ if (col['bulleted'] === true) {
2035
+ return wrapCellList(items, col, 'bulleted');
2036
+ }
2037
+ if (col['listWithLineBreaks'] === true) {
2038
+ return wrapCellList(items, col, 'lines');
2039
+ }
2040
+ // Bare array — comma-join (matches the existing legacy fallback).
2041
+ return wrapCell(items.join(', '), col);
2042
+ }
2043
+ // Text column — apply built-in format, then wrapper.
2044
+ const fmt = col['format'];
2045
+ const display = fmt ? applyColumnFormat(value, fmt) : String(value);
2046
+ return wrapCell(display, col);
2047
+ }
2048
+ }
2049
+ }
2050
+ /** Apply text-rendering chrome (color, weight, line-clamp, wrap, tooltip)
2051
+ * to a stringified cell value. Used by the text and per-row formatter
2052
+ * paths so styling stays consistent. When `asHtml` is true the content
2053
+ * is server-rendered HTML (e.g. from the registered richtext renderer)
2054
+ * and gets injected via `dangerouslySetInnerHTML`. */
2055
+ function wrapCell(content, col, asHtml = false) {
2056
+ const color = col['color'];
2057
+ const weight = col['weight'];
2058
+ const tooltip = col['tooltip'];
2059
+ const wrapping = Boolean(col['wrap']);
2060
+ const clamp = col['lineClamp'];
2061
+ const copyMsg = col['copyMessage'];
2062
+ const colorCls = color ? (COLUMN_COLOR_CLASSES[color] ?? '') : '';
2063
+ const weightCls = weight ? (COLUMN_WEIGHT_CLASSES[weight] ?? '') : '';
2064
+ const wrapCls = wrapping ? 'whitespace-normal' : '';
2065
+ const clampStyle = clamp !== undefined
2066
+ ? { display: '-webkit-box', WebkitLineClamp: String(clamp), WebkitBoxOrient: 'vertical', overflow: 'hidden' }
2067
+ : undefined;
2068
+ const valueNode = asHtml
2069
+ ? (_jsx("span", { className: `prose prose-sm max-w-none dark:prose-invert ${colorCls} ${weightCls} ${wrapCls}`.trim(), title: tooltip, style: clampStyle, dangerouslySetInnerHTML: { __html: content } }))
2070
+ : (_jsx("span", { className: `${colorCls} ${weightCls} ${wrapCls}`.trim(), title: tooltip, style: clampStyle, children: content }));
2071
+ if (copyMsg === undefined)
2072
+ return valueNode;
2073
+ // Copy-to-clipboard trigger — copies the rendered text. For richtext
2074
+ // cells the underlying source isn't separately stamped on the wire
2075
+ // (would double the row payload), so the rendered HTML is what gets
2076
+ // copied; admins comfortable with HTML still get something usable.
2077
+ return (_jsxs("span", { className: "inline-flex items-center gap-1.5", children: [valueNode, _jsx(CellCopyButton, { text: content, label: copyMsg })] }));
2078
+ }
2079
+ /** Tabular-list rendering used by `Column.bulleted()` /
2080
+ * `Column.listWithLineBreaks()`. `mode='bulleted'` mounts a `<ul>` with
2081
+ * bullet markers; `mode='lines'` separates entries with `<br>`. Both
2082
+ * inherit the same color / weight / wrap / tooltip / clamp chrome as
2083
+ * the text path. Empty arrays fall through to the muted dash. */
2084
+ function wrapCellList(items, col, mode) {
2085
+ if (items.length === 0) {
2086
+ const fallback = col['default'] ?? '—';
2087
+ return _jsx("span", { className: "text-muted-foreground", children: fallback });
2088
+ }
2089
+ const color = col['color'];
2090
+ const weight = col['weight'];
2091
+ const tooltip = col['tooltip'];
2092
+ const colorCls = color ? (COLUMN_COLOR_CLASSES[color] ?? '') : '';
2093
+ const weightCls = weight ? (COLUMN_WEIGHT_CLASSES[weight] ?? '') : '';
2094
+ if (mode === 'bulleted') {
2095
+ return (_jsx("ul", { className: `list-disc pl-4 space-y-0.5 ${colorCls} ${weightCls}`.trim(), title: tooltip, children: items.map((s, i) => _jsx("li", { children: s }, i)) }));
2096
+ }
2097
+ return (_jsx("span", { className: `${colorCls} ${weightCls}`.trim(), title: tooltip, children: items.map((s, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx("br", {}), s] }, i))) }));
2098
+ }
2099
+ /** Slim copy-to-clipboard button used by `Column.copyMessage()`. The
2100
+ * label doubles as the toast text. Mirrors `EntryCopyButton`'s shape
2101
+ * but compact enough to live inline next to a cell value. */
2102
+ function CellCopyButton({ text, label }) {
2103
+ const [copied, setCopied] = useState(false);
2104
+ const handleClick = (e) => {
2105
+ e.stopPropagation();
2106
+ e.preventDefault();
2107
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
2108
+ navigator.clipboard.writeText(text).then(() => {
2109
+ setCopied(true);
2110
+ setTimeout(() => setCopied(false), 1500);
2111
+ }).catch(() => { });
2112
+ }
2113
+ };
2114
+ return (_jsx("button", { type: "button", onClick: handleClick, "aria-label": copied ? label : 'Copy', title: copied ? label : 'Copy', "data-no-row-nav": true, className: "inline-flex h-5 w-5 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted", children: copied ? _jsx(CheckIcon, { className: "size-3" }) : _jsx(CopyIcon, { className: "size-3" }) }));
2115
+ }
2116
+ function rowId(row, index) {
2117
+ if (row && typeof row === 'object' && 'id' in row) {
2118
+ const id = row.id;
2119
+ if (id !== undefined && id !== null)
2120
+ return String(id);
2121
+ }
2122
+ return String(index);
2123
+ }
2124
+ /**
2125
+ * Filter dropdown that updates the URL directly on change. We don't rely
2126
+ * on a wrapping `<form>` because filters now live inside a portaled
2127
+ * Popover (the search input keeps its own form for Enter-to-submit).
2128
+ *
2129
+ * Empty value (`''`) is the "All" sentinel — the param is removed from
2130
+ * the URL rather than serialized as `&name=`.
2131
+ */
2132
+ function FilterSelect({ name, label, defaultValue, placeholder, options, prefix, }) {
2133
+ const [value, setValue] = useState(defaultValue);
2134
+ const navigate = useNavigate();
2135
+ const onChange = (next) => {
2136
+ const v = typeof next === 'string' ? next : '';
2137
+ setValue(v);
2138
+ if (typeof window === 'undefined')
2139
+ return;
2140
+ const url = new URL(window.location.href);
2141
+ const k = prefixK(prefix, name);
2142
+ if (v === '')
2143
+ url.searchParams.delete(k);
2144
+ else
2145
+ url.searchParams.set(k, v);
2146
+ // Filter changes reset pagination — first page of the new result set.
2147
+ url.searchParams.delete(prefixK(prefix, 'page'));
2148
+ // SPA navigate via context (vike's navigate when mounted under the
2149
+ // Vike-generated +Layout). Fallback is full reload — see useNavigate.
2150
+ void navigate(url.pathname + url.search);
2151
+ };
2152
+ return (_jsxs("div", { className: "flex flex-col gap-1 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsxs(Select, { value: value, onValueChange: onChange, children: [_jsx(SelectTrigger, { size: "sm", className: "w-full", children: _jsx(SelectValue, { placeholder: placeholder }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "", children: placeholder }), options.map(o => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value)))] })] })] }));
2153
+ }
2154
+ /**
2155
+ * Heading-row text for a group band. Shows `<label>: <value-or-title>`
2156
+ * with an optional description below. Reused for both collapsible and
2157
+ * static heading rows.
2158
+ */
2159
+ function GroupHeaderText({ label, value, title, description, }) {
2160
+ const display = title ?? value ?? '';
2161
+ return (_jsxs("span", { className: "flex flex-col gap-0.5", children: [_jsxs("span", { children: [label && _jsxs("span", { className: "text-muted-foreground/70", children: [label, ": "] }), _jsx("span", { className: "text-foreground", children: display || 'Ungrouped' })] }), description && (_jsx("span", { className: "text-[10px] font-normal normal-case text-muted-foreground/80", children: description }))] }));
2162
+ }
2163
+ /**
2164
+ * "Group by" dropdown rendered above the table when 2+ TableGroups
2165
+ * are registered (or 1 group with rich metadata). Selecting "None"
2166
+ * sets `?group=` (empty) which explicitly overrides `defaultGroup`.
2167
+ *
2168
+ * URL-driven — `onChange` builds the next href via `buildTableQuery`
2169
+ * and SPA-navigates; the page re-renders with the new active group.
2170
+ */
2171
+ function TableGroupPicker({ options, active, onChange, }) {
2172
+ const value = active ?? '';
2173
+ return (_jsxs(Select, { value: value, onValueChange: (v) => onChange(typeof v === 'string' ? v : ''), children: [_jsx(SelectTrigger, { size: "sm", className: "h-9 w-44", children: _jsx(SelectValue, { placeholder: "Group by\u2026" }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "", children: "No grouping" }), options.map(o => (_jsx(SelectItem, { value: o.column, children: o.label }, o.column)))] })] }));
2174
+ }
2175
+ /**
2176
+ * Pair-of-date-inputs filter for `kind === 'dateRange'`. Each side
2177
+ * navigates the URL on change, encoding the pair as `from..to` keyed
2178
+ * off the filter name. Empty pair drops the URL key.
2179
+ */
2180
+ function FilterDateRange({ name, label, defaultValue, placeholder, includesTime, minDate, maxDate, prefix, }) {
2181
+ const initial = parseDateRangeValue(defaultValue);
2182
+ const [from, setFrom] = useState(initial.from ?? '');
2183
+ const [to, setTo] = useState(initial.to ?? '');
2184
+ const navigate = useNavigate();
2185
+ const inputType = includesTime ? 'datetime-local' : 'date';
2186
+ const navigateTo = (nextFrom, nextTo) => {
2187
+ if (typeof window === 'undefined')
2188
+ return;
2189
+ const url = new URL(window.location.href);
2190
+ const encoded = encodeDateRangeValue({ from: nextFrom, to: nextTo });
2191
+ const k = prefixK(prefix, name);
2192
+ if (encoded === '')
2193
+ url.searchParams.delete(k);
2194
+ else
2195
+ url.searchParams.set(k, encoded);
2196
+ url.searchParams.delete(prefixK(prefix, 'page'));
2197
+ void navigate(url.pathname + url.search);
2198
+ };
2199
+ const onFromChange = (e) => {
2200
+ const v = e.target.value;
2201
+ setFrom(v);
2202
+ navigateTo(v, to);
2203
+ };
2204
+ const onToChange = (e) => {
2205
+ const v = e.target.value;
2206
+ setTo(v);
2207
+ navigateTo(from, v);
2208
+ };
2209
+ const onClear = () => {
2210
+ setFrom('');
2211
+ setTo('');
2212
+ navigateTo('', '');
2213
+ };
2214
+ const hasValue = from !== '' || to !== '';
2215
+ return (_jsxs("div", { className: "flex flex-col gap-1 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { type: inputType, value: from, onChange: onFromChange, placeholder: placeholder, "aria-label": `${label} from`, ...(minDate !== undefined ? { min: minDate } : {}), ...(maxDate !== undefined ? { max: maxDate } : {}), className: "h-8 text-xs" }), _jsx("span", { className: "text-muted-foreground", children: "\u2192" }), _jsx(Input, { type: inputType, value: to, onChange: onToChange, placeholder: placeholder, "aria-label": `${label} to`, ...(minDate !== undefined ? { min: minDate } : {}), ...(maxDate !== undefined ? { max: maxDate } : {}), className: "h-8 text-xs" }), hasValue && (_jsx("button", { type: "button", onClick: onClear, "aria-label": `Clear ${label}`, className: "text-muted-foreground hover:text-foreground px-1", children: "\u00D7" }))] })] }));
2216
+ }
2217
+ /**
2218
+ * Multi-value filter for `kind === 'multiSelect'`. Renders a checkbox
2219
+ * stack inside the popover; toggling a box patches the comma-separated
2220
+ * URL value for the filter's name. Empty selection drops the URL key.
2221
+ */
2222
+ function FilterMultiSelect({ name, label, defaultValue, options, prefix, }) {
2223
+ const [selected, setSelected] = useState(() => parseMultiSelectValue(defaultValue));
2224
+ const navigate = useNavigate();
2225
+ const apply = (next) => {
2226
+ setSelected(next);
2227
+ if (typeof window === 'undefined')
2228
+ return;
2229
+ const url = new URL(window.location.href);
2230
+ const encoded = encodeMultiSelectValue(next);
2231
+ const k = prefixK(prefix, name);
2232
+ if (encoded === '')
2233
+ url.searchParams.delete(k);
2234
+ else
2235
+ url.searchParams.set(k, encoded);
2236
+ url.searchParams.delete(prefixK(prefix, 'page'));
2237
+ void navigate(url.pathname + url.search);
2238
+ };
2239
+ const toggle = (value, checked) => {
2240
+ const next = checked
2241
+ ? [...selected.filter(v => v !== value), value]
2242
+ : selected.filter(v => v !== value);
2243
+ apply(next);
2244
+ };
2245
+ return (_jsxs("div", { className: "flex flex-col gap-1 text-xs", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("div", { className: "flex flex-col gap-1.5", children: options.map(o => {
2246
+ const checked = selected.includes(o.value);
2247
+ return (_jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [_jsx(Checkbox, { checked: checked, onCheckedChange: (c) => toggle(o.value, c === true) }), _jsx("span", { children: o.label })] }, o.value));
2248
+ }) })] }));
2249
+ }
2250
+ /**
2251
+ * Multi-field filter for `kind === 'form'`. The popover renders an inner
2252
+ * sub-form with the user-declared schema; submitting bundles all named
2253
+ * inputs into a `Record<string, unknown>`, JSON-encodes the non-empty
2254
+ * subset under the filter's URL key, and SPA-navigates. Empty submit
2255
+ * drops the URL key entirely.
2256
+ *
2257
+ * The fields' `defaultValue` were pre-hydrated server-side from the
2258
+ * active URL value (see `FormFilter.toMeta`), so an existing filter
2259
+ * round-trips into the form on render. Inputs are uncontrolled — we
2260
+ * read state via `new FormData(form)` on submit, matching how the
2261
+ * outer page-level Form works on full submit.
2262
+ */
2263
+ function FilterForm({ name, label, defaultValue, formSchema, prefix, }) {
2264
+ const formRef = useRef(null);
2265
+ const navigate = useNavigate();
2266
+ const hasValue = defaultValue !== '' && defaultValue !== '{}';
2267
+ const onApply = (e) => {
2268
+ e?.preventDefault();
2269
+ if (!formRef.current)
2270
+ return;
2271
+ const fd = new FormData(formRef.current);
2272
+ const values = {};
2273
+ for (const [key, val] of fd.entries()) {
2274
+ const existing = values[key];
2275
+ if (existing === undefined) {
2276
+ values[key] = val;
2277
+ }
2278
+ else if (Array.isArray(existing)) {
2279
+ existing.push(val);
2280
+ }
2281
+ else {
2282
+ values[key] = [existing, val];
2283
+ }
2284
+ }
2285
+ if (typeof window === 'undefined')
2286
+ return;
2287
+ const url = new URL(window.location.href);
2288
+ const encoded = encodeFormFilterValue(values);
2289
+ const k = prefixK(prefix, name);
2290
+ if (encoded === '')
2291
+ url.searchParams.delete(k);
2292
+ else
2293
+ url.searchParams.set(k, encoded);
2294
+ url.searchParams.delete(prefixK(prefix, 'page'));
2295
+ void navigate(url.pathname + url.search);
2296
+ };
2297
+ const onClear = () => {
2298
+ if (typeof window === 'undefined')
2299
+ return;
2300
+ const url = new URL(window.location.href);
2301
+ url.searchParams.delete(prefixK(prefix, name));
2302
+ url.searchParams.delete(prefixK(prefix, 'page'));
2303
+ void navigate(url.pathname + url.search);
2304
+ };
2305
+ return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: label }), _jsxs("form", { ref: formRef, onSubmit: onApply, className: "flex flex-col gap-2", children: [formSchema.map((child, i) => renderFormChild(child, i, {}, {})), _jsxs("div", { className: "flex gap-2 pt-1", children: [_jsx("button", { type: "submit", className: "inline-flex h-8 items-center justify-center rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: "Apply" }), hasValue && (_jsx("button", { type: "button", onClick: onClear, className: "inline-flex h-8 items-center justify-center rounded-md border border-input bg-background px-3 text-xs font-medium hover:bg-accent hover:text-accent-foreground", children: "Clear" }))] })] })] }));
2306
+ }
2307
+ /**
2308
+ * Composable advanced filter for `kind === 'queryBuilder'`. v2 emits a
2309
+ * full tree — root AND/OR connector + nested groups arbitrarily deep —
2310
+ * JSON-encoded into a single URL key on Apply (see
2311
+ * `encodeQueryBuilderValue`).
2312
+ *
2313
+ * State is local — typing into a value input doesn't navigate. Only the
2314
+ * Apply button writes the URL. This mirrors `FilterForm`'s behavior and
2315
+ * keeps the popover quiet under the cursor.
2316
+ */
2317
+ function FilterQueryBuilder({ name, label, defaultValue, constraints, prefix, }) {
2318
+ const navigate = useNavigate();
2319
+ const initialTree = parseQueryBuilderValue(defaultValue);
2320
+ const [tree, setTree] = useState(initialTree);
2321
+ const hasValue = defaultValue !== '' && initialTree.rules.length > 0;
2322
+ const onApply = (e) => {
2323
+ e?.preventDefault();
2324
+ if (typeof window === 'undefined')
2325
+ return;
2326
+ const encoded = encodeQueryBuilderValue(tree);
2327
+ const url = new URL(window.location.href);
2328
+ const k = prefixK(prefix, name);
2329
+ if (encoded === '')
2330
+ url.searchParams.delete(k);
2331
+ else
2332
+ url.searchParams.set(k, encoded);
2333
+ url.searchParams.delete(prefixK(prefix, 'page'));
2334
+ void navigate(url.pathname + url.search);
2335
+ };
2336
+ const onClear = () => {
2337
+ setTree({ operator: 'and', rules: [] });
2338
+ if (typeof window === 'undefined')
2339
+ return;
2340
+ const url = new URL(window.location.href);
2341
+ url.searchParams.delete(prefixK(prefix, name));
2342
+ url.searchParams.delete(prefixK(prefix, 'page'));
2343
+ void navigate(url.pathname + url.search);
2344
+ };
2345
+ if (constraints.length === 0) {
2346
+ return (_jsxs("div", { className: "text-muted-foreground text-xs", children: [label, ": no constraints declared."] }));
2347
+ }
2348
+ return (_jsxs("div", { className: "flex flex-col gap-2 min-w-[24rem]", children: [_jsx("span", { className: "text-muted-foreground text-xs", children: label }), _jsxs("form", { onSubmit: onApply, className: "flex flex-col gap-2", children: [_jsx(QueryBuilderGroup, { tree: tree, constraints: constraints, isRoot: true, onChange: setTree }), _jsxs("div", { className: "flex items-center gap-2 pt-1", children: [_jsx("div", { className: "flex-1" }), _jsx("button", { type: "submit", className: "inline-flex h-8 items-center justify-center rounded-md bg-primary px-3 text-xs font-medium text-primary-foreground hover:bg-primary/90", children: "Apply" }), (hasValue || tree.rules.length > 0) && (_jsx("button", { type: "button", onClick: onClear, className: "inline-flex h-8 items-center justify-center rounded-md border border-input bg-background px-3 text-xs font-medium hover:bg-accent hover:text-accent-foreground", children: "Clear" }))] })] })] }));
2349
+ }
2350
+ /**
2351
+ * Recursive group renderer — emits a connector picker (AND / OR) at the
2352
+ * top, a vertical stack of children (rules and sub-groups), and footer
2353
+ * buttons for "+ Add condition" and "+ Add group". Calls `onChange` with
2354
+ * the updated sub-tree so parents can splice it back into their own
2355
+ * `rules` array. Root groups skip the outer border so the popover doesn't
2356
+ * carry a redundant frame; nested groups draw a faint left rule + soft
2357
+ * background so the nesting is visible without blowing up the width.
2358
+ */
2359
+ function QueryBuilderGroup({ tree, constraints, isRoot, onChange, onRemove, }) {
2360
+ const constraintMap = new Map();
2361
+ for (const c of constraints)
2362
+ constraintMap.set(c.name, c);
2363
+ const setOperator = (op) => {
2364
+ onChange({ ...tree, operator: op });
2365
+ };
2366
+ const updateChildAt = (index, next) => {
2367
+ onChange({ ...tree, rules: tree.rules.map((r, i) => i === index ? next : r) });
2368
+ };
2369
+ const removeChildAt = (index) => {
2370
+ onChange({ ...tree, rules: tree.rules.filter((_, i) => i !== index) });
2371
+ };
2372
+ const addRule = () => {
2373
+ const first = constraints[0];
2374
+ if (!first)
2375
+ return;
2376
+ onChange({
2377
+ ...tree,
2378
+ rules: [...tree.rules, {
2379
+ constraint: first.name,
2380
+ operator: first.defaultOperator ?? first.operators[0]?.name ?? 'equals',
2381
+ value: undefined,
2382
+ }],
2383
+ });
2384
+ };
2385
+ const addGroup = () => {
2386
+ onChange({
2387
+ ...tree,
2388
+ rules: [...tree.rules, { operator: 'and', rules: [] }],
2389
+ });
2390
+ };
2391
+ const wrapper = isRoot
2392
+ ? 'flex flex-col gap-2'
2393
+ : 'flex flex-col gap-2 rounded-md border-l-2 border-primary/40 bg-muted/30 pl-2 py-2 pr-2';
2394
+ return (_jsxs("div", { className: wrapper, children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ConnectorToggle, { value: tree.operator, onChange: setOperator }), _jsx("span", { className: "text-muted-foreground text-[11px]", children: tree.operator === 'and' ? 'Match all of the following' : 'Match any of the following' }), !isRoot && onRemove && (_jsxs(_Fragment, { children: [_jsx("div", { className: "flex-1" }), _jsx("button", { type: "button", onClick: onRemove, "aria-label": "Remove group", className: "inline-flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground", children: "\u00D7" })] }))] }), tree.rules.length === 0 && (_jsx("div", { className: "text-muted-foreground text-xs italic", children: "No conditions yet." })), tree.rules.map((child, i) => {
2395
+ if (isQueryBuilderTree(child)) {
2396
+ return (_jsx(QueryBuilderGroup, { tree: child, constraints: constraints, isRoot: false, onChange: (next) => updateChildAt(i, next), onRemove: () => removeChildAt(i) }, i));
2397
+ }
2398
+ return (_jsx(QueryBuilderRow, { rule: child, constraints: constraints, constraintMeta: constraintMap.get(child.constraint), onConstraintChange: (v) => {
2399
+ const c = constraintMap.get(v);
2400
+ if (!c)
2401
+ return;
2402
+ updateChildAt(i, {
2403
+ constraint: v,
2404
+ operator: c.defaultOperator ?? c.operators[0]?.name ?? 'equals',
2405
+ value: undefined,
2406
+ });
2407
+ }, onOperatorChange: (v) => {
2408
+ updateChildAt(i, {
2409
+ ...child,
2410
+ operator: v,
2411
+ value: undefined,
2412
+ });
2413
+ }, onValueChange: (v) => updateChildAt(i, { ...child, value: v }), onRemove: () => removeChildAt(i) }, i));
2414
+ }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("button", { type: "button", onClick: addRule, className: "inline-flex h-8 items-center justify-center rounded-md border border-dashed border-input bg-background px-3 text-xs font-medium hover:bg-accent hover:text-accent-foreground", children: "+ Add condition" }), _jsx("button", { type: "button", onClick: addGroup, className: "inline-flex h-8 items-center justify-center rounded-md border border-dashed border-input bg-background px-3 text-xs font-medium hover:bg-accent hover:text-accent-foreground", children: "+ Add group" })] })] }));
2415
+ }
2416
+ /**
2417
+ * Compact AND/OR segmented control used at the head of every group. Pure
2418
+ * presentation — the parent owns the value.
2419
+ */
2420
+ function ConnectorToggle({ value, onChange, }) {
2421
+ const base = 'inline-flex h-7 items-center px-2 text-[11px] font-medium uppercase tracking-wide transition';
2422
+ const on = 'bg-primary text-primary-foreground';
2423
+ const off = 'bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground';
2424
+ return (_jsxs("div", { className: "inline-flex overflow-hidden rounded-md border border-input", children: [_jsx("button", { type: "button", onClick: () => onChange('and'), className: `${base} ${value === 'and' ? on : off}`, "aria-pressed": value === 'and', children: "AND" }), _jsx("button", { type: "button", onClick: () => onChange('or'), className: `${base} ${value === 'or' ? on : off}`, "aria-pressed": value === 'or', children: "OR" })] }));
2425
+ }
2426
+ /**
2427
+ * One condition row inside `FilterQueryBuilder`. Three controls
2428
+ * left-to-right: constraint picker, operator picker, value input. The
2429
+ * value input dispatches off the operator's `valueKind` — `none` hides
2430
+ * it entirely, `numberRange` / `dateRange` mount a pair, otherwise a
2431
+ * single typed input.
2432
+ */
2433
+ function QueryBuilderRow({ rule, constraints, constraintMeta, onConstraintChange, onOperatorChange, onValueChange, onRemove, }) {
2434
+ const operators = constraintMeta?.operators ?? [];
2435
+ const activeOp = operators.find(o => o.name === rule.operator);
2436
+ const valueKind = activeOp?.valueKind ?? 'text';
2437
+ return (_jsxs("div", { className: "flex items-start gap-1.5 rounded-md border border-input bg-background p-2", children: [_jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-1.5", children: [_jsxs(Select, { value: rule.constraint, onValueChange: (v) => onConstraintChange(typeof v === 'string' ? v : ''), children: [_jsx(SelectTrigger, { size: "sm", className: "h-8 w-36 text-xs", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: constraints.map(c => (_jsx(SelectItem, { value: c.name, children: c.label }, c.name))) })] }), _jsxs(Select, { value: rule.operator, onValueChange: (v) => onOperatorChange(typeof v === 'string' ? v : ''), children: [_jsx(SelectTrigger, { size: "sm", className: "h-8 w-32 text-xs", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: operators.map(o => (_jsx(SelectItem, { value: o.name, children: o.label }, o.name))) })] }), _jsx(QueryBuilderValueInput, { kind: valueKind, value: rule.value, options: constraintMeta?.options, onChange: onValueChange })] }), _jsx("button", { type: "button", onClick: onRemove, "aria-label": "Remove condition", className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground", children: "\u00D7" })] }));
2438
+ }
2439
+ /**
2440
+ * Operator-aware value control. Switches over the constraint operator's
2441
+ * `valueKind` and mounts the matching input. Value shapes:
2442
+ * - `text / number / date / dateTime / select` → scalar
2443
+ * - `multiSelect` → string[]
2444
+ * - `numberRange / dateRange` → [string, string]
2445
+ * - `boolean / none` → null / undefined
2446
+ */
2447
+ function QueryBuilderValueInput({ kind, value, options, onChange, }) {
2448
+ if (kind === 'none' || kind === 'boolean')
2449
+ return null;
2450
+ if (kind === 'select') {
2451
+ const opts = options ?? [];
2452
+ const v = value === undefined || value === null ? '' : String(value);
2453
+ return (_jsxs(Select, { value: v, onValueChange: (next) => onChange(typeof next === 'string' ? next : ''), children: [_jsx(SelectTrigger, { size: "sm", className: "h-8 min-w-32 text-xs", children: _jsx(SelectValue, { placeholder: "Pick\u2026" }) }), _jsx(SelectContent, { children: opts.map(o => (_jsx(SelectItem, { value: o.value, children: o.label }, o.value))) })] }));
2454
+ }
2455
+ if (kind === 'multiSelect') {
2456
+ const opts = options ?? [];
2457
+ const list = Array.isArray(value) ? value.map(v => String(v)) : [];
2458
+ const toggle = (val) => {
2459
+ if (list.includes(val))
2460
+ onChange(list.filter(v => v !== val));
2461
+ else
2462
+ onChange([...list, val]);
2463
+ };
2464
+ return (_jsx("div", { className: "flex flex-wrap items-center gap-1", children: opts.map(o => {
2465
+ const active = list.includes(o.value);
2466
+ return (_jsx("button", { type: "button", onClick: () => toggle(o.value), className: 'inline-flex h-7 items-center rounded-md border px-2 text-xs ' +
2467
+ (active
2468
+ ? 'border-primary bg-primary text-primary-foreground'
2469
+ : 'border-input bg-background hover:bg-accent'), children: o.label }, o.value));
2470
+ }) }));
2471
+ }
2472
+ if (kind === 'numberRange') {
2473
+ const [min, max] = Array.isArray(value) ? [value[0], value[1]] : [undefined, undefined];
2474
+ return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { type: "number", className: "h-8 w-24 text-xs", value: min === undefined || min === null ? '' : String(min), onChange: (e) => onChange([e.target.value, max ?? '']), placeholder: "Min" }), _jsx("span", { className: "text-muted-foreground text-xs", children: "\u2013" }), _jsx(Input, { type: "number", className: "h-8 w-24 text-xs", value: max === undefined || max === null ? '' : String(max), onChange: (e) => onChange([min ?? '', e.target.value]), placeholder: "Max" })] }));
2475
+ }
2476
+ if (kind === 'dateRange') {
2477
+ const [from, to] = Array.isArray(value) ? [value[0], value[1]] : [undefined, undefined];
2478
+ return (_jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Input, { type: "date", className: "h-8 w-36 text-xs", value: from === undefined || from === null ? '' : String(from), onChange: (e) => onChange([e.target.value, to ?? '']) }), _jsx("span", { className: "text-muted-foreground text-xs", children: "\u2192" }), _jsx(Input, { type: "date", className: "h-8 w-36 text-xs", value: to === undefined || to === null ? '' : String(to), onChange: (e) => onChange([from ?? '', e.target.value]) })] }));
2479
+ }
2480
+ if (kind === 'date' || kind === 'dateTime') {
2481
+ const v = value === undefined || value === null ? '' : String(value);
2482
+ return (_jsx(Input, { type: kind === 'dateTime' ? 'datetime-local' : 'date', className: "h-8 w-44 text-xs", value: v, onChange: (e) => onChange(e.target.value) }));
2483
+ }
2484
+ if (kind === 'number') {
2485
+ const v = value === undefined || value === null ? '' : String(value);
2486
+ return (_jsx(Input, { type: "number", className: "h-8 w-32 text-xs", value: v, onChange: (e) => onChange(e.target.value), placeholder: "Value" }));
2487
+ }
2488
+ // Default: text
2489
+ const v = value === undefined || value === null ? '' : String(value);
2490
+ return (_jsx(Input, { type: "text", className: "h-8 min-w-32 flex-1 text-xs", value: v, onChange: (e) => onChange(e.target.value), placeholder: "Value" }));
2491
+ }
2492
+ function renderFilterControl(el, index, prefix) {
2493
+ const name = String(el['name'] ?? '');
2494
+ const label = String(el['label'] ?? name);
2495
+ const kind = String(el['kind'] ?? 'select');
2496
+ const value = el['value'] ? String(el['value']) : '';
2497
+ const placeholder = el['placeholder'] ? String(el['placeholder']) : 'All';
2498
+ if (kind === 'queryBuilder') {
2499
+ const constraints = el['constraints'] ?? [];
2500
+ return (_jsx(FilterQueryBuilder, { name: name, label: label, defaultValue: value, constraints: constraints, prefix: prefix }, index));
2501
+ }
2502
+ if (kind === 'form') {
2503
+ const formSchema = el['formSchema'] ?? [];
2504
+ return (_jsx(FilterForm, { name: name, label: label, defaultValue: value, formSchema: formSchema, prefix: prefix }, index));
2505
+ }
2506
+ if (kind === 'boolean') {
2507
+ return (_jsx(FilterSelect, { name: name, label: label, defaultValue: value, placeholder: placeholder, options: [{ value: '1', label: 'Yes' }, { value: '0', label: 'No' }], prefix: prefix }, index));
2508
+ }
2509
+ if (kind === 'multiSelect') {
2510
+ const options = el['options'] ?? [];
2511
+ return (_jsx(FilterMultiSelect, { name: name, label: label, defaultValue: value, options: options, prefix: prefix }, index));
2512
+ }
2513
+ if (kind === 'dateRange') {
2514
+ const includesTime = Boolean(el['includesTime']);
2515
+ const minDate = el['minDate'] ? String(el['minDate']) : undefined;
2516
+ const maxDate = el['maxDate'] ? String(el['maxDate']) : undefined;
2517
+ return (_jsx(FilterDateRange, { name: name, label: label, defaultValue: value, placeholder: placeholder, includesTime: includesTime, prefix: prefix, ...(minDate !== undefined ? { minDate } : {}), ...(maxDate !== undefined ? { maxDate } : {}) }, index));
2518
+ }
2519
+ // 'ternary' and 'select' both render as a single-select dropdown,
2520
+ // differing only in their server-supplied option set.
2521
+ const options = el['options'] ?? [];
2522
+ return (_jsx(FilterSelect, { name: name, label: label, defaultValue: value, placeholder: placeholder, options: options, prefix: prefix }, index));
2523
+ }
2524
+ /**
2525
+ * Resolve the record URL for a single data cell. Column-level override
2526
+ * (`Column.recordUrl(fn)` → `_columnRecordUrls[name]`) wins over the
2527
+ * table-level `Table.recordUrl(fn)` (`_recordUrl`). Explicit per-column
2528
+ * opt-out (`Column.recordUrl(false)` → `meta.recordUrl === false`)
2529
+ * suppresses the link entirely. Returns `undefined` when the cell is
2530
+ * not linkable, in which case the renderer leaves it unwrapped.
2531
+ */
2532
+ function resolveColumnUrl(col, tableUrl, colUrls) {
2533
+ if (col['recordUrl'] === false)
2534
+ return undefined;
2535
+ const own = colUrls[String(col['name'] ?? '')];
2536
+ if (own !== undefined)
2537
+ return own;
2538
+ return tableUrl;
2539
+ }
2540
+ /**
2541
+ * Cell-level link wrapper. Renders a real `<a href>` so right-click /
2542
+ * cmd-click / middle-click "open in new tab" works, but intercepts plain
2543
+ * left-clicks for SPA navigation via `useNavigate()`. Modified clicks
2544
+ * (cmd / ctrl / shift / alt / non-primary buttons) fall through to the
2545
+ * browser's default link behavior.
2546
+ */
2547
+ function RecordCellLink({ href, navigate, children, }) {
2548
+ const onClick = (e) => {
2549
+ if (e.button !== 0)
2550
+ return;
2551
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
2552
+ return;
2553
+ e.preventDefault();
2554
+ void navigate(href);
2555
+ };
2556
+ return (_jsx("a", { href: href, onClick: onClick, className: "block px-2 py-2 text-inherit no-underline hover:text-inherit focus:outline-none focus-visible:ring-2 focus-visible:ring-ring rounded", children: children }));
2557
+ }
2558
+ /**
2559
+ * List-page tab strip — Filament-style query shortcuts above the table
2560
+ * ("All / Drafts / Published / Archived"). Each trigger is a real `<a>`
2561
+ * (right-click / cmd-click "open in new tab" works); plain left-click is
2562
+ * intercepted for SPA navigation. Active tab carries `data-active`.
2563
+ *
2564
+ * The server stamps `active` + per-tab `url` + resolved badge string on
2565
+ * each `listTab` meta entry — this component just renders.
2566
+ */
2567
+ function ListTabsRenderer({ el }) {
2568
+ const navigate = useNavigate();
2569
+ const tabs = (el.children ?? []).filter(c => c.type === 'listTab');
2570
+ if (tabs.length === 0)
2571
+ return null;
2572
+ return (_jsx("div", { className: "border-b border-border", children: _jsx("nav", { className: "flex items-center gap-1 -mb-px overflow-x-auto", role: "tablist", children: tabs.map((t, i) => {
2573
+ const name = String(t['name'] ?? '');
2574
+ const label = String(t['label'] ?? name);
2575
+ const active = Boolean(t['active']);
2576
+ const url = String(t['url'] ?? `?tab=${encodeURIComponent(name)}`);
2577
+ const iconKey = t['icon'] ? String(t['icon']) : undefined;
2578
+ const Icon = iconKey ? (resolveIcon(iconKey) ?? CircleIcon) : undefined;
2579
+ const badge = t['badge'] !== undefined ? String(t['badge']) : undefined;
2580
+ const badgeKey = t['badgeColor'] ? String(t['badgeColor']) : (active ? 'primary' : 'gray');
2581
+ const badgeCls = BADGE_COLOR_CLASSES[badgeKey] ?? BADGE_COLOR_CLASSES['gray'];
2582
+ const triggerCls = [
2583
+ 'inline-flex items-center gap-1.5 px-3 py-2 text-sm border-b-2 transition-colors whitespace-nowrap',
2584
+ active
2585
+ ? 'border-primary text-foreground font-medium'
2586
+ : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border',
2587
+ ].join(' ');
2588
+ const onClick = (e) => {
2589
+ if (e.button !== 0)
2590
+ return;
2591
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
2592
+ return;
2593
+ e.preventDefault();
2594
+ void navigate(url);
2595
+ };
2596
+ return (_jsxs("a", { href: url, onClick: onClick, role: "tab", "aria-selected": active, "data-active": active || undefined, className: triggerCls, children: [Icon && _jsx(Icon, { className: "size-4", "aria-hidden": "true" }), _jsx("span", { children: label }), badge !== undefined && (_jsx("span", { className: `ml-1 inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${badgeCls}`, children: badge }))] }, i));
2597
+ }) }) }));
2598
+ }
2599
+ /** Phase C — server-resolved breadcrumb chain rendered above any other
2600
+ * top-of-page chrome. The trailing item carries no `url` and renders
2601
+ * as plain text + `aria-current="page"`. SPA-navigates on plain
2602
+ * left-click; modified clicks fall through. */
2603
+ function BreadcrumbsRenderer({ el }) {
2604
+ const navigate = useNavigate();
2605
+ const items = el['items'] ?? [];
2606
+ if (items.length < 2)
2607
+ return null;
2608
+ return (_jsx("nav", { "aria-label": "Breadcrumb", className: "text-sm text-muted-foreground", children: _jsx("ol", { className: "flex flex-wrap items-center gap-1.5", children: items.map((item, i) => {
2609
+ const isLast = i === items.length - 1;
2610
+ const linkable = !!item.url && !isLast;
2611
+ const onClick = (e) => {
2612
+ if (e.button !== 0)
2613
+ return;
2614
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
2615
+ return;
2616
+ if (!item.url)
2617
+ return;
2618
+ e.preventDefault();
2619
+ void navigate(item.url);
2620
+ };
2621
+ return (_jsxs("li", { className: "inline-flex items-center gap-1.5", children: [linkable
2622
+ ? (_jsx("a", { href: item.url, onClick: onClick, className: "hover:text-foreground transition-colors", children: item.label }))
2623
+ : (_jsx("span", { "aria-current": isLast ? 'page' : undefined, className: isLast ? 'text-foreground font-medium' : undefined, children: item.label })), !isLast && (_jsx("span", { "aria-hidden": "true", className: "text-muted-foreground/50", children: "/" }))] }, `${i}:${item.label}`));
2624
+ }) }) }));
2625
+ }
2626
+ /** Plan #11 — relation manager nav strip. Renders one anchor per tab;
2627
+ * the active tab gets the same border-primary styling as ListTabs.
2628
+ * SPA-navigates on plain left-click; cmd/ctrl/shift/middle-click fall
2629
+ * through so users can open a manager in a new tab. */
2630
+ function RelationTabsRenderer({ el }) {
2631
+ const navigate = useNavigate();
2632
+ const tabs = el['tabs'] ?? [];
2633
+ if (tabs.length === 0)
2634
+ return null;
2635
+ return (_jsx("div", { className: "border-b border-border", children: _jsx("nav", { className: "flex items-center gap-1 -mb-px overflow-x-auto", role: "tablist", children: tabs.map((t, i) => {
2636
+ const triggerCls = [
2637
+ 'inline-flex items-center gap-1.5 px-3 py-2 text-sm border-b-2 transition-colors whitespace-nowrap',
2638
+ t.active
2639
+ ? 'border-primary text-foreground font-medium'
2640
+ : 'border-transparent text-muted-foreground hover:text-foreground hover:border-border',
2641
+ ].join(' ');
2642
+ const onClick = (e) => {
2643
+ if (e.button !== 0)
2644
+ return;
2645
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
2646
+ return;
2647
+ e.preventDefault();
2648
+ void navigate(t.url);
2649
+ };
2650
+ return (_jsxs("a", { href: t.url, onClick: onClick, role: "tab", "aria-selected": t.active, "data-active": t.active || undefined, className: triggerCls, children: [_jsx(RelationTabIcon, { icon: t.icon }), _jsx("span", { children: t.label })] }, t.key + ':' + i));
2651
+ }) }) }));
2652
+ }
2653
+ function RelationTabIcon({ icon }) {
2654
+ // SerializedIcon is `string | { class: string }`. Use useIconFor to
2655
+ // resolve component-typed icons through the Vite plugin's manifest.
2656
+ const Icon = useIconFor(icon);
2657
+ if (!Icon)
2658
+ return null;
2659
+ return _jsx(Icon, { className: "size-4", "aria-hidden": "true" });
2660
+ }
2661
+ /**
2662
+ * Sort-by dropdown for `contentLayout: 'cards'`. Since the column-header
2663
+ * row (which usually doubles as the sort affordance) is hidden in cards
2664
+ * mode, this picker appears in the top bar instead. Each `Column` flagged
2665
+ * `.sortable()` contributes two options — ascending and descending —
2666
+ * yielding "Title (A→Z) / Title (Z→A) / Date (oldest first) / Date (newest
2667
+ * first)" style entries. Selecting an option resets `?page=1`.
2668
+ */
2669
+ function SortByPicker({ columns, active, onChange, }) {
2670
+ const sortable = columns.filter(c => Boolean(c['sortable']));
2671
+ if (sortable.length === 0)
2672
+ return null;
2673
+ const value = active ? `${active.column}:${active.direction}` : '';
2674
+ return (_jsxs(Select, { value: value, onValueChange: (v) => {
2675
+ if (typeof v !== 'string' || v === '')
2676
+ return;
2677
+ const idx = v.indexOf(':');
2678
+ if (idx < 0)
2679
+ return;
2680
+ const col = v.slice(0, idx);
2681
+ const dir = v.slice(idx + 1) === 'desc' ? 'desc' : 'asc';
2682
+ onChange(col, dir);
2683
+ }, children: [_jsx(SelectTrigger, { size: "sm", className: "h-9 w-44", children: _jsx(SelectValue, { placeholder: "Sort by\u2026" }) }), _jsx(SelectContent, { children: sortable.map(col => {
2684
+ const name = String(col['name'] ?? '');
2685
+ const label = String(col['label'] ?? name);
2686
+ return (_jsxs(React.Fragment, { children: [_jsxs(SelectItem, { value: `${name}:asc`, children: [label, " (A\u2192Z)"] }), _jsxs(SelectItem, { value: `${name}:desc`, children: [label, " (Z\u2192A)"] })] }, name));
2687
+ }) })] }));
2688
+ }
2689
+ /**
2690
+ * Lookup tables for responsive grid column-counts in `contentLayout:
2691
+ * 'cards'`. Tailwind's JIT scanner needs **literal** class strings; we
2692
+ * can't construct them at runtime via template literals (`grid-cols-${n}`
2693
+ * would never be matched). Limit to 1–6 columns + 12 — covers every
2694
+ * reasonable card grid; bigger values are silently capped at 6 for
2695
+ * non-base breakpoints in `cardsPerRowClasses`.
2696
+ */
2697
+ const CARDS_GRID_COLS_BASE = {
2698
+ 1: 'grid-cols-1',
2699
+ 2: 'grid-cols-2',
2700
+ 3: 'grid-cols-3',
2701
+ 4: 'grid-cols-4',
2702
+ 5: 'grid-cols-5',
2703
+ 6: 'grid-cols-6',
2704
+ 12: 'grid-cols-12',
2705
+ };
2706
+ const CARDS_GRID_COLS_SM = {
2707
+ 1: 'sm:grid-cols-1', 2: 'sm:grid-cols-2', 3: 'sm:grid-cols-3',
2708
+ 4: 'sm:grid-cols-4', 5: 'sm:grid-cols-5', 6: 'sm:grid-cols-6',
2709
+ 12: 'sm:grid-cols-12',
2710
+ };
2711
+ const CARDS_GRID_COLS_MD = {
2712
+ 1: 'md:grid-cols-1', 2: 'md:grid-cols-2', 3: 'md:grid-cols-3',
2713
+ 4: 'md:grid-cols-4', 5: 'md:grid-cols-5', 6: 'md:grid-cols-6',
2714
+ 12: 'md:grid-cols-12',
2715
+ };
2716
+ const CARDS_GRID_COLS_LG = {
2717
+ 1: 'lg:grid-cols-1', 2: 'lg:grid-cols-2', 3: 'lg:grid-cols-3',
2718
+ 4: 'lg:grid-cols-4', 5: 'lg:grid-cols-5', 6: 'lg:grid-cols-6',
2719
+ 12: 'lg:grid-cols-12',
2720
+ };
2721
+ const CARDS_GRID_COLS_XL = {
2722
+ 1: 'xl:grid-cols-1', 2: 'xl:grid-cols-2', 3: 'xl:grid-cols-3',
2723
+ 4: 'xl:grid-cols-4', 5: 'xl:grid-cols-5', 6: 'xl:grid-cols-6',
2724
+ 12: 'xl:grid-cols-12',
2725
+ };
2726
+ const CARDS_GRID_COLS_2XL = {
2727
+ 1: '2xl:grid-cols-1', 2: '2xl:grid-cols-2', 3: '2xl:grid-cols-3',
2728
+ 4: '2xl:grid-cols-4', 5: '2xl:grid-cols-5', 6: '2xl:grid-cols-6',
2729
+ 12: '2xl:grid-cols-12',
2730
+ };
2731
+ function pickCardCols(table, raw) {
2732
+ if (raw === undefined)
2733
+ return undefined;
2734
+ if (table[raw])
2735
+ return table[raw];
2736
+ // Snap unsupported values to nearest available — values outside [1,6]∪{12}
2737
+ // round down. Already-clamped to [1,12] server-side.
2738
+ if (raw >= 12)
2739
+ return table[12];
2740
+ if (raw >= 6)
2741
+ return table[6];
2742
+ if (raw >= 5)
2743
+ return table[5];
2744
+ if (raw >= 4)
2745
+ return table[4];
2746
+ if (raw >= 3)
2747
+ return table[3];
2748
+ if (raw >= 2)
2749
+ return table[2];
2750
+ return table[1];
2751
+ }
2752
+ /** Build a Tailwind grid-cols class string from a per-row config. Default
2753
+ * `{ default: 1, sm: 2, lg: 3 }` mirrors Filament's typical card grid. */
2754
+ function cardsPerRowClasses(opts) {
2755
+ const cfg = opts ?? {};
2756
+ const baseN = cfg['default'] ?? 1;
2757
+ const out = [pickCardCols(CARDS_GRID_COLS_BASE, baseN)];
2758
+ if (cfg['sm'] !== undefined) {
2759
+ const c = pickCardCols(CARDS_GRID_COLS_SM, cfg['sm']);
2760
+ if (c)
2761
+ out.push(c);
2762
+ }
2763
+ if (cfg['md'] !== undefined) {
2764
+ const c = pickCardCols(CARDS_GRID_COLS_MD, cfg['md']);
2765
+ if (c)
2766
+ out.push(c);
2767
+ }
2768
+ if (cfg['lg'] !== undefined) {
2769
+ const c = pickCardCols(CARDS_GRID_COLS_LG, cfg['lg']);
2770
+ if (c)
2771
+ out.push(c);
2772
+ }
2773
+ if (cfg['xl'] !== undefined) {
2774
+ const c = pickCardCols(CARDS_GRID_COLS_XL, cfg['xl']);
2775
+ if (c)
2776
+ out.push(c);
2777
+ }
2778
+ if (cfg['2xl'] !== undefined) {
2779
+ const c = pickCardCols(CARDS_GRID_COLS_2XL, cfg['2xl']);
2780
+ if (c)
2781
+ out.push(c);
2782
+ }
2783
+ // Unset fallback covers Filament's typical default — 1 column on mobile,
2784
+ // 2 on small screens, 3 on large.
2785
+ if (Object.keys(cfg).length === 0) {
2786
+ return 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3';
2787
+ }
2788
+ return out.join(' ');
2789
+ }
2790
+ /**
2791
+ * Tier-3 deferred-load shell. When `Resource.deferLoading = true`, the
2792
+ * SSR pass marks each Table on the page as `deferred` + stamps a
2793
+ * `tableUrl`. This wrapper paints a skeleton on first frame and fetches
2794
+ * the actual rows from the JSON endpoint after mount; the inner
2795
+ * `TableRendererBody` renders identically against either the SSR meta
2796
+ * (non-deferred case) or the fetched meta (deferred case).
2797
+ *
2798
+ * SPA nav with a query change re-runs SSR, which re-stamps `deferred`
2799
+ * — so the URL-change effect fires another fetch. The skeleton frame
2800
+ * still shows current sort / search / page / filter chrome because the
2801
+ * SSR pass mirrors URL state on the deferred Table.
2802
+ */
2803
+ function TableRenderer({ el }) {
2804
+ const isDeferred = el['deferred'] === true && typeof el['tableUrl'] === 'string';
2805
+ const tableUrl = isDeferred ? el['tableUrl'] : '';
2806
+ // Track the URL search string so a navigation that changes filters /
2807
+ // sort / page re-fires the fetch. Initialized lazy on first client
2808
+ // render; on the SSR pass we just fall through to skeleton.
2809
+ const [search, setSearch] = useState(() => typeof window === 'undefined' ? '' : window.location.search);
2810
+ useEffect(() => {
2811
+ if (!isDeferred)
2812
+ return;
2813
+ if (typeof window === 'undefined')
2814
+ return;
2815
+ setSearch(window.location.search);
2816
+ }, [isDeferred, el]);
2817
+ const [deferredMeta, setDeferredMeta] = useState(null);
2818
+ const [deferredError, setDeferredError] = useState(null);
2819
+ useEffect(() => {
2820
+ if (!isDeferred || !tableUrl)
2821
+ return;
2822
+ if (typeof window === 'undefined')
2823
+ return;
2824
+ let cancelled = false;
2825
+ setDeferredMeta(null);
2826
+ setDeferredError(null);
2827
+ fetch(tableUrl + search, {
2828
+ headers: { 'Accept': 'application/json' },
2829
+ credentials: 'same-origin',
2830
+ })
2831
+ .then(async (r) => {
2832
+ const data = (await r.json());
2833
+ if (cancelled)
2834
+ return;
2835
+ if (data.ok && Array.isArray(data.tables) && data.tables.length > 0) {
2836
+ setDeferredMeta(data.tables[0]);
2837
+ }
2838
+ else {
2839
+ setDeferredError(data.error ?? 'Failed to load table');
2840
+ }
2841
+ })
2842
+ .catch(err => {
2843
+ if (cancelled)
2844
+ return;
2845
+ setDeferredError(err instanceof Error ? err.message : 'Failed to load table');
2846
+ });
2847
+ return () => { cancelled = true; };
2848
+ }, [isDeferred, tableUrl, search]);
2849
+ if (isDeferred && deferredError) {
2850
+ return (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 px-4 py-3 text-sm text-destructive", children: ["Failed to load table: ", deferredError] }));
2851
+ }
2852
+ if (isDeferred && !deferredMeta) {
2853
+ return _jsx(TableSkeleton, { el: el });
2854
+ }
2855
+ return _jsx(TableRendererBody, { el: isDeferred ? deferredMeta : el });
2856
+ }
2857
+ /**
2858
+ * Skeleton placeholder painted while a deferred-loaded table fetches
2859
+ * its rows. Mirrors the table's heading + description chrome (already
2860
+ * present on `el`) so the frame doesn't pop layout when the real rows
2861
+ * arrive. Renders a small column header strip + 5 placeholder rows.
2862
+ */
2863
+ function TableSkeleton({ el }) {
2864
+ const heading = typeof el['heading'] === 'string' ? el['heading'] : undefined;
2865
+ const description = typeof el['description'] === 'string' ? el['description'] : undefined;
2866
+ const children = el.children ?? [];
2867
+ const colCount = Math.max(1, children.filter(c => c.type === 'column').length);
2868
+ return (_jsxs("div", { className: "space-y-3", children: [(heading || description) ? (_jsxs("div", { className: "space-y-1", children: [heading ? _jsx("div", { className: "text-lg font-semibold", children: heading }) : null, description ? _jsx("div", { className: "text-sm text-muted-foreground", children: description }) : null] })) : null, _jsxs("div", { className: "rounded-md border", children: [_jsx("div", { className: "grid border-b bg-muted/50 px-4 py-2", style: { gridTemplateColumns: `repeat(${colCount}, minmax(0, 1fr))` }, children: Array.from({ length: colCount }).map((_, i) => (_jsx("div", { className: "h-4 w-20 rounded bg-muted-foreground/20" }, i))) }), _jsx("div", { className: "divide-y", children: Array.from({ length: 5 }).map((_, rowIdx) => (_jsx("div", { className: "grid items-center px-4 py-3", style: { gridTemplateColumns: `repeat(${colCount}, minmax(0, 1fr))` }, children: Array.from({ length: colCount }).map((_, colIdx) => (_jsx("div", { className: "h-4 w-2/3 rounded bg-muted-foreground/10 animate-pulse" }, colIdx))) }, rowIdx))) })] })] }));
2869
+ }
2870
+ function TableRendererBody({ el }) {
2871
+ const navigate = useNavigate();
2872
+ const children = el.children ?? [];
2873
+ const columns = children.filter(c => c.type === 'column');
2874
+ // Actions and ActionGroups share placement — both show up in the
2875
+ // header/bulk/row toolbars depending on their `placement` field.
2876
+ const actionLike = children.filter(c => c.type === 'action' || c.type === 'actionGroup');
2877
+ const filters = children.filter(c => c.type === 'filter');
2878
+ const hasRecordUrl = Boolean(el['recordUrl']);
2879
+ const hasRecordClasses = Boolean(el['recordClasses']);
2880
+ const pollInterval = typeof el['pollInterval'] === 'number' ? el['pollInterval'] : undefined;
2881
+ const defaultGroup = typeof el['defaultGroup'] === 'string' ? el['defaultGroup'] : undefined;
2882
+ const summaries = el['summaries'];
2883
+ const groupSummaries = el['groupSummaries'];
2884
+ const groupOptions = el['groups'] ?? [];
2885
+ // Active group's registered metadata (if any). Falls back to a synth
2886
+ // for the bare-column form so the heading row still has a label.
2887
+ const activeGroupMeta = defaultGroup
2888
+ ? (groupOptions.find(g => g.column === defaultGroup) ?? {
2889
+ column: defaultGroup,
2890
+ label: (() => {
2891
+ const col = columns.find(c => c['name'] === defaultGroup);
2892
+ return col ? String(col['label'] ?? defaultGroup) : defaultGroup;
2893
+ })(),
2894
+ })
2895
+ : undefined;
2896
+ const groupColumnLabel = activeGroupMeta?.label;
2897
+ // Auto-refresh: re-visit current URL on a timer so sort/filter/pagination
2898
+ // state survives. Pause while the document is hidden — background tabs
2899
+ // shouldn't keep hammering the server.
2900
+ useEffect(() => {
2901
+ if (!pollInterval || pollInterval <= 0)
2902
+ return;
2903
+ if (typeof document === 'undefined')
2904
+ return;
2905
+ let timerId;
2906
+ const tick = () => navigate(window.location.pathname + window.location.search);
2907
+ const start = () => {
2908
+ if (timerId === undefined)
2909
+ timerId = setInterval(tick, pollInterval * 1000);
2910
+ };
2911
+ const stop = () => {
2912
+ if (timerId !== undefined) {
2913
+ clearInterval(timerId);
2914
+ timerId = undefined;
2915
+ }
2916
+ };
2917
+ if (document.visibilityState === 'visible')
2918
+ start();
2919
+ const onVis = () => {
2920
+ if (document.visibilityState === 'visible')
2921
+ start();
2922
+ else
2923
+ stop();
2924
+ };
2925
+ document.addEventListener('visibilitychange', onVis);
2926
+ return () => {
2927
+ document.removeEventListener('visibilitychange', onVis);
2928
+ stop();
2929
+ };
2930
+ }, [pollInterval, navigate]);
2931
+ // Group actions by placement. `inline` defaults to header so it shows up
2932
+ // somewhere visible — explicit placements always win.
2933
+ const placementOf = (a) => String(a['placement'] ?? 'inline');
2934
+ const headerActions = actionLike.filter(a => { const p = placementOf(a); return p === 'header' || p === 'inline'; });
2935
+ const bulkActions = actionLike.filter(a => placementOf(a) === 'bulk');
2936
+ const rowActions = actionLike.filter(a => placementOf(a) === 'row');
2937
+ const rawRows = el['rows'] ?? [];
2938
+ const total = el['total'] ?? rawRows.length;
2939
+ const search = el['search'];
2940
+ const currentSort = el['currentSort'];
2941
+ const currentPage = el['currentPage'] ?? 1;
2942
+ const perPage = el['perPage'];
2943
+ const searchable = Boolean(el['searchable']);
2944
+ const currentPath = el['currentPath'] ?? '';
2945
+ // Tier-3 — when the table opts into `Table.queryStringIdentifier(...)`,
2946
+ // every URL key (search / sort / page / perPage / group / filter names)
2947
+ // gets prefixed with `${id}_` so multiple tables on one page don't
2948
+ // collide on `?search=` etc. Bare keys still apply when unset.
2949
+ const queryPrefix = typeof el['queryStringIdentifier'] === 'string'
2950
+ ? el['queryStringIdentifier']
2951
+ : undefined;
2952
+ // Reorderable rows — grip column + HTML5 DnD wiring. Rows live in
2953
+ // local state during a drag so the optimistic reorder happens
2954
+ // immediately; on POST failure we roll back to the server's order.
2955
+ const reorderableColumn = typeof el['reorderableColumn'] === 'string' ? el['reorderableColumn'] : undefined;
2956
+ const reorderUrl = typeof el['reorderUrl'] === 'string' ? el['reorderUrl'] : undefined;
2957
+ const [reorderRowsLocal, setReorderRowsLocal] = useState(null);
2958
+ const rows = reorderRowsLocal ?? rawRows;
2959
+ const { notify } = useToast();
2960
+ // Read the explicit `?group=` value out of the URL so sort/pagination
2961
+ // links preserve "None" overrides (`?group=`). Server render: no URL,
2962
+ // so we fall back to `defaultGroup` from the meta — which is already
2963
+ // the reconciled active column.
2964
+ const urlGroup = typeof window === 'undefined'
2965
+ ? undefined
2966
+ : (() => {
2967
+ const sp = new URLSearchParams(window.location.search);
2968
+ const k = prefixK(queryPrefix, 'group');
2969
+ return sp.has(k) ? sp.get(k) : undefined;
2970
+ })();
2971
+ // Collapsible groups — per-group fold state. Keyed by `_groupValue`
2972
+ // (the raw column value, NOT the resolved title) so rows that share a
2973
+ // group key fold together. Persisted in localStorage at
2974
+ // `pilotiq.table.<currentPath>.groups.<column>.<value>`. Default-
2975
+ // collapsed groups derive their initial state from `meta.collapsed`.
2976
+ const groupCollapsible = activeGroupMeta?.collapsible === true;
2977
+ const groupDefaultCollapsed = activeGroupMeta?.collapsed === true;
2978
+ const groupStorageKey = (groupValue) => `pilotiq.table.${currentPath}.groups.${defaultGroup ?? ''}.${groupValue}`;
2979
+ // Lazy-init from localStorage on mount; SSR returns the meta default.
2980
+ const [collapsedGroups, setCollapsedGroups] = useState({});
2981
+ useEffect(() => {
2982
+ if (!groupCollapsible || !defaultGroup)
2983
+ return;
2984
+ if (typeof window === 'undefined')
2985
+ return;
2986
+ // Walk the rendered rows once on mount, picking up persisted state.
2987
+ const next = {};
2988
+ const seen = new Set();
2989
+ for (const row of rows) {
2990
+ const v = String(row['_groupValue'] ?? '');
2991
+ if (seen.has(v))
2992
+ continue;
2993
+ seen.add(v);
2994
+ try {
2995
+ const stored = window.localStorage.getItem(groupStorageKey(v));
2996
+ next[v] = stored === null ? groupDefaultCollapsed : stored === '1';
2997
+ }
2998
+ catch {
2999
+ next[v] = groupDefaultCollapsed;
3000
+ }
3001
+ }
3002
+ setCollapsedGroups(next);
3003
+ // Re-run if the active group changes — different values, different
3004
+ // localStorage namespace.
3005
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3006
+ }, [defaultGroup, groupCollapsible, groupDefaultCollapsed, currentPath]);
3007
+ const toggleGroupCollapsed = (groupValue) => {
3008
+ setCollapsedGroups(prev => {
3009
+ const nextOpen = !prev[groupValue];
3010
+ const next = { ...prev, [groupValue]: nextOpen };
3011
+ if (typeof window !== 'undefined') {
3012
+ try {
3013
+ window.localStorage.setItem(groupStorageKey(groupValue), nextOpen ? '1' : '0');
3014
+ }
3015
+ catch { /* private mode / quota — silent */ }
3016
+ }
3017
+ return next;
3018
+ });
3019
+ };
3020
+ const state = {
3021
+ ...(search !== undefined ? { search } : {}),
3022
+ ...(currentSort !== undefined ? { sort: currentSort } : {}),
3023
+ page: currentPage,
3024
+ ...(urlGroup !== undefined ? { group: urlGroup }
3025
+ : defaultGroup !== undefined ? { group: defaultGroup }
3026
+ : {}),
3027
+ };
3028
+ // Snapshot active filter values for sort/pagination href construction.
3029
+ // Filter form submits already carry these (selects are inside the
3030
+ // form); `<a href>` links don't, so we re-emit them here.
3031
+ const activeFilters = {};
3032
+ for (const f of filters) {
3033
+ const v = f['value'];
3034
+ if (typeof v === 'string' && v !== '')
3035
+ activeFilters[String(f['name'])] = v;
3036
+ }
3037
+ // Track which row ids are currently checked. Keyed by id (string), not
3038
+ // by index, so pagination and re-renders don't drop selection state.
3039
+ const [selected, setSelected] = useState(() => new Set());
3040
+ const visibleIds = rows.map((row, i) => rowId(row, i));
3041
+ const allChecked = visibleIds.length > 0 && visibleIds.every(id => selected.has(id));
3042
+ const someChecked = selected.size > 0;
3043
+ const toggleRow = (id) => {
3044
+ setSelected(prev => {
3045
+ const next = new Set(prev);
3046
+ if (next.has(id))
3047
+ next.delete(id);
3048
+ else
3049
+ next.add(id);
3050
+ return next;
3051
+ });
3052
+ };
3053
+ const toggleAll = () => {
3054
+ setSelected(prev => {
3055
+ if (visibleIds.every(id => prev.has(id))) {
3056
+ const next = new Set(prev);
3057
+ for (const id of visibleIds)
3058
+ next.delete(id);
3059
+ return next;
3060
+ }
3061
+ const next = new Set(prev);
3062
+ for (const id of visibleIds)
3063
+ next.add(id);
3064
+ return next;
3065
+ });
3066
+ };
3067
+ // ── Reorder DnD state + handlers ──────────────────────
3068
+ // dragId — the row currently being dragged (string id), or null.
3069
+ // dropAt — the boundary the cursor is hovering (0..rows.length), or null.
3070
+ const [dragId, setDragId] = useState(null);
3071
+ const [dropAt, setDropAt] = useState(null);
3072
+ const onRowDragStart = (id) => (e) => {
3073
+ if (!reorderEnabled)
3074
+ return;
3075
+ setDragId(id);
3076
+ e.dataTransfer.effectAllowed = 'move';
3077
+ try {
3078
+ e.dataTransfer.setData('text/plain', id);
3079
+ }
3080
+ catch { /* IE quirk */ }
3081
+ };
3082
+ const onRowDragOver = (idx) => (e) => {
3083
+ if (!reorderEnabled || dragId === null)
3084
+ return;
3085
+ e.preventDefault();
3086
+ e.dataTransfer.dropEffect = 'move';
3087
+ const rect = e.currentTarget.getBoundingClientRect();
3088
+ const aboveHalf = e.clientY < rect.top + rect.height / 2;
3089
+ setDropAt(aboveHalf ? idx : idx + 1);
3090
+ };
3091
+ const onRowDrop = async (e) => {
3092
+ if (!reorderEnabled || dragId === null || dropAt === null || !reorderUrl) {
3093
+ setDragId(null);
3094
+ setDropAt(null);
3095
+ return;
3096
+ }
3097
+ e.preventDefault();
3098
+ const fromIdx = visibleIds.findIndex(id => id === dragId);
3099
+ setDragId(null);
3100
+ setDropAt(null);
3101
+ if (fromIdx < 0)
3102
+ return;
3103
+ const target = dropAt > fromIdx ? dropAt - 1 : dropAt;
3104
+ if (target === fromIdx)
3105
+ return;
3106
+ const reordered = rows.slice();
3107
+ const moved = reordered.splice(fromIdx, 1)[0];
3108
+ reordered.splice(target, 0, moved);
3109
+ const newIds = reordered.map((row, i) => rowId(row, i));
3110
+ const previousLocal = reorderRowsLocal;
3111
+ setReorderRowsLocal(reordered);
3112
+ try {
3113
+ const res = await fetch(reorderUrl, {
3114
+ method: 'POST',
3115
+ headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
3116
+ body: JSON.stringify({ ids: newIds }),
3117
+ });
3118
+ if (!res.ok)
3119
+ throw new Error(`Reorder failed (${res.status})`);
3120
+ }
3121
+ catch (err) {
3122
+ // Roll back to server order. The toast surfaces the failure;
3123
+ // next page render fetches the persisted column.
3124
+ setReorderRowsLocal(previousLocal);
3125
+ notify({
3126
+ type: 'error',
3127
+ title: 'Could not save new order',
3128
+ body: err instanceof Error ? err.message : 'Reorder failed',
3129
+ });
3130
+ }
3131
+ };
3132
+ const onRowDragEnd = () => {
3133
+ setDragId(null);
3134
+ setDropAt(null);
3135
+ };
3136
+ if (columns.length === 0) {
3137
+ return (_jsx("div", { className: "rounded-xl border bg-card p-6 text-sm text-muted-foreground", children: "No columns configured for this table." }));
3138
+ }
3139
+ const isCardsLayout = el['contentLayout'] === 'cards';
3140
+ const cardsPerRow = el['cardsPerRow'];
3141
+ const totalPages = perPage && perPage > 0 ? Math.max(1, Math.ceil(total / perPage)) : 1;
3142
+ const showPagination = totalPages > 1;
3143
+ const hasFilters = filters.length > 0;
3144
+ // Filter layout positions (Filament v5). `'modal'` (default) keeps the
3145
+ // toolbar Filters button + popover. The three inline modes lay every
3146
+ // filter widget out as a wrapping strip in the matching slot. The
3147
+ // collapsible variant adds a toolbar toggle + per-table-path persisted
3148
+ // open state.
3149
+ const filtersLayout = el['filtersLayout'] ?? 'modal';
3150
+ const filtersInModal = filtersLayout === 'modal';
3151
+ const filtersAbove = filtersLayout === 'above-content'
3152
+ || filtersLayout === 'above-content-collapsible';
3153
+ const filtersBelow = filtersLayout === 'below-content';
3154
+ const filtersCollapsible = filtersLayout === 'above-content-collapsible';
3155
+ const filtersStripStorageKey = `pilotiq.table.${currentPath}.filters.open`;
3156
+ const [filtersOpen, setFiltersOpen] = useState(() => {
3157
+ if (!filtersCollapsible)
3158
+ return true;
3159
+ if (typeof window === 'undefined')
3160
+ return false;
3161
+ try {
3162
+ const stored = window.localStorage.getItem(filtersStripStorageKey);
3163
+ // Default to OPEN when filters are active (URL carried filter values
3164
+ // in) so the user can see what's filtering — same UX cue as the
3165
+ // active-filters pill row.
3166
+ if (stored === null)
3167
+ return Object.keys(activeFilters).length > 0;
3168
+ return stored === '1';
3169
+ }
3170
+ catch {
3171
+ return false;
3172
+ }
3173
+ });
3174
+ const toggleFiltersOpen = () => {
3175
+ setFiltersOpen(prev => {
3176
+ const next = !prev;
3177
+ if (typeof window !== 'undefined') {
3178
+ try {
3179
+ window.localStorage.setItem(filtersStripStorageKey, next ? '1' : '0');
3180
+ }
3181
+ catch { /* private mode / quota — silent */ }
3182
+ }
3183
+ return next;
3184
+ });
3185
+ };
3186
+ // Show the "Group by" dropdown when 2+ groups are registered, or 1
3187
+ // group with rich metadata (label/collapsible/etc.). A single bare
3188
+ // `defaultGroup('col')` with no `groups([...])` registration shouldn't
3189
+ // render the picker — there's nothing to pick.
3190
+ const hasGroupPicker = groupOptions.length >= 2
3191
+ || (groupOptions.length === 1 && Boolean(groupOptions[0].collapsible
3192
+ || groupOptions[0].collapsed
3193
+ || groupOptions[0].date));
3194
+ const sortableColumns = isCardsLayout ? columns.filter(c => Boolean(c['sortable'])) : [];
3195
+ const hasSortPicker = isCardsLayout && sortableColumns.length > 0;
3196
+ // Only modal + collapsible mount a toolbar widget; the always-visible
3197
+ // strip modes don't add anything to the header bar.
3198
+ const showFiltersInToolbar = hasFilters && (filtersInModal || filtersCollapsible);
3199
+ const showHeaderBar = searchable || headerActions.length > 0 || showFiltersInToolbar || hasGroupPicker || hasSortPicker;
3200
+ const hasBulkActions = bulkActions.length > 0;
3201
+ const hasRowActions = rowActions.length > 0;
3202
+ // Drag-to-reorder is enabled only when the visible rows ARE the
3203
+ // canonical sort. Filters / search / non-default sort / pagination
3204
+ // beyond page 1 all break that invariant; we render the grip column
3205
+ // greyed-out instead of letting the user reorder a slice that won't
3206
+ // round-trip cleanly. `reorderableColumn` is set server-side when
3207
+ // `Table.reorderable()` opts in.
3208
+ const sortMatchesReorder = currentSort?.column === reorderableColumn &&
3209
+ currentSort?.direction === 'asc';
3210
+ const filtersActive = Object.keys(activeFilters).length > 0;
3211
+ const searchActive = typeof search === 'string' && search !== '';
3212
+ const reorderEnabled = reorderableColumn !== undefined &&
3213
+ reorderUrl !== undefined &&
3214
+ sortMatchesReorder &&
3215
+ !filtersActive &&
3216
+ !searchActive &&
3217
+ currentPage === 1;
3218
+ const reorderColumnVisible = reorderableColumn !== undefined;
3219
+ const totalCols = columns.length
3220
+ + (hasBulkActions ? 1 : 0)
3221
+ + (hasRowActions ? 1 : 0)
3222
+ + (reorderColumnVisible ? 1 : 0);
3223
+ // Top-bar chrome (heading / description / striped / emptyState).
3224
+ const tableHeading = el['heading'];
3225
+ const tableDescription = el['description'];
3226
+ const striped = Boolean(el['striped']);
3227
+ const emptyState = el['emptyState'];
3228
+ const filteredEmptyState = el['filteredEmptyState'];
3229
+ const hasFilterOrSearch = (search !== undefined && search !== '') ||
3230
+ Object.keys(activeFilters).length > 0;
3231
+ // Distinct copy when a query / filter is active. Falls back to
3232
+ // `emptyState` when `filteredEmptyState` is not set, preserving the
3233
+ // pre-2026-05-04 behavior for tables that haven't opted in.
3234
+ const activeEmpty = (hasFilterOrSearch && filteredEmptyState) ? filteredEmptyState : emptyState;
3235
+ const EmptyIcon = activeEmpty?.icon ? (resolveIcon(activeEmpty.icon) ?? InboxIcon) : InboxIcon;
3236
+ 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) ? (_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 })), hasFilters && filtersCollapsible && (_jsx(FilterStripToggle, { filters: filters, open: filtersOpen, onToggle: toggleFiltersOpen })), hasGroupPicker && (_jsx(TableGroupPicker, { options: groupOptions, active: defaultGroup, onChange: (value) => {
3237
+ // value === '' → explicit "None" (clears defaultGroup);
3238
+ // value !== '' → switch to that column.
3239
+ const href = buildTableQuery(state, { page: 1, group: value }, currentPath, activeFilters, queryPrefix);
3240
+ navigate(href);
3241
+ } })), hasSortPicker && (_jsx(SortByPicker, { columns: sortableColumns, active: currentSort, onChange: (column, direction) => {
3242
+ const href = buildTableQuery(state, { sort: { column, direction }, page: 1 }, currentPath, activeFilters, queryPrefix);
3243
+ navigate(href);
3244
+ } }))] })) : _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 })), 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, { el: el, columns: columns, rows: rows, visibleIds: visibleIds, selected: selected, toggleRow: toggleRow, hasBulkActions: hasBulkActions, hasRowActions: hasRowActions, rowActions: rowActions, hasRecordUrl: hasRecordUrl, hasRecordClasses: hasRecordClasses, striped: striped, activeEmpty: activeEmpty, EmptyIcon: EmptyIcon, hasFilterOrSearch: hasFilterOrSearch, defaultGroup: defaultGroup, groupColumnLabel: groupColumnLabel, groupCollapsible: groupCollapsible, collapsedGroups: collapsedGroups, toggleGroupCollapsed: toggleGroupCollapsed, cardsPerRow: cardsPerRow, navigate: navigate })) : (_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() }) })), columns.map((col, i) => {
3245
+ const name = String(col['name'] ?? '');
3246
+ const label = String(col['label'] ?? name);
3247
+ const sortable = Boolean(col['sortable']);
3248
+ const isActive = currentSort?.column === name;
3249
+ if (!sortable) {
3250
+ return (_jsx(TableHead, { className: "text-xs uppercase tracking-wider", children: label }, i));
3251
+ }
3252
+ const next = nextSortDir(currentSort, name);
3253
+ const href = buildTableQuery(state, { sort: next, page: 1 }, currentPath, activeFilters, queryPrefix);
3254
+ 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));
3255
+ }), 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
3256
+ ?? (hasFilterOrSearch ? 'No matching records' : 'No records yet') }), (activeEmpty?.description ||
3257
+ (hasFilterOrSearch && !activeEmpty?.description)) && (_jsx("p", { className: "text-sm", children: activeEmpty?.description
3258
+ ?? 'Try clearing filters or adjusting your search.' }))] }) }) })) : rows.map((row, ri) => {
3259
+ const id = visibleIds[ri];
3260
+ const recordObj = row;
3261
+ const isSelected = selected.has(id);
3262
+ const stripedClass = striped && ri % 2 === 1 ? 'bg-muted/30' : '';
3263
+ // Group banding — emit a heading row whenever `_groupValue`
3264
+ // differs from the previous row. The first row in any group
3265
+ // gets the heading; rows within keep their normal chrome.
3266
+ const groupValue = defaultGroup
3267
+ ? String(recordObj['_groupValue'] ?? '')
3268
+ : undefined;
3269
+ const groupTitle = defaultGroup
3270
+ ? recordObj['_groupTitle']
3271
+ : undefined;
3272
+ const groupDescription = defaultGroup
3273
+ ? recordObj['_groupDescription']
3274
+ : undefined;
3275
+ const prevGroupValue = defaultGroup && ri > 0
3276
+ ? String((rows[ri - 1]['_groupValue'] ?? ''))
3277
+ : undefined;
3278
+ const showGroupHeader = defaultGroup !== undefined && groupValue !== prevGroupValue;
3279
+ // Hide data rows whose group is collapsed. The heading row
3280
+ // for that group still renders (so the user can re-expand).
3281
+ const isInCollapsedGroup = groupCollapsible && groupValue !== undefined && collapsedGroups[groupValue] === true;
3282
+ // Filament-style per-cell linking. Each data cell wraps
3283
+ // its content in a real `<a href>` when the column resolves
3284
+ // to a record URL — column override (`Column.recordUrl(fn)`)
3285
+ // beats inheritance from the table (`Table.recordUrl(fn)`),
3286
+ // and `Column.recordUrl(false)` opts out. Action and bulk
3287
+ // cells are never wrapped, so clicks there fire only their
3288
+ // own handlers — no event-bubbling gymnastics.
3289
+ const tableUrl = hasRecordUrl ? recordObj['_recordUrl'] : undefined;
3290
+ const colUrls = recordObj['_columnRecordUrls'] ?? {};
3291
+ const rowHasAnyLink = tableUrl !== undefined || Object.keys(colUrls).length > 0;
3292
+ const customRowClasses = hasRecordClasses
3293
+ ? recordObj['_recordClasses'] ?? ''
3294
+ : '';
3295
+ const rowClassName = [stripedClass, rowHasAnyLink ? 'cursor-pointer' : '', customRowClasses]
3296
+ .filter(Boolean)
3297
+ .join(' ')
3298
+ .trim();
3299
+ 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: groupCollapsible ? (_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 text-left", onClick: () => toggleGroupCollapsed(groupValue), "aria-expanded": !isInCollapsedGroup, children: [_jsx(ChevronDownIcon, { className: [
3300
+ 'size-4 transition-transform',
3301
+ isInCollapsedGroup ? '-rotate-90' : '',
3302
+ ].filter(Boolean).join(' ') }), _jsx(GroupHeaderText, { label: groupColumnLabel, value: groupValue, title: groupTitle, description: groupDescription })] })) : (_jsx(GroupHeaderText, { label: groupColumnLabel, value: groupValue, title: groupTitle, description: groupDescription })) }) }, `group-${id}`)), isInCollapsedGroup ? null : (_jsxs(TableRow, { "data-state": isSelected ? 'selected' : undefined, className: [
3303
+ rowClassName,
3304
+ dragId === id ? 'opacity-50' : '',
3305
+ dropAt === ri && dragId !== null ? 'border-t-2 border-t-primary' : '',
3306
+ ].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
3307
+ ? 'inline-flex cursor-grab text-muted-foreground hover:text-foreground active:cursor-grabbing'
3308
+ : '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) }) })), columns.map((col, ci) => {
3309
+ const name = String(col['name'] ?? '');
3310
+ const value = recordObj[name];
3311
+ const align = col['alignment'] === 'center' ? 'text-center'
3312
+ : col['alignment'] === 'end' ? 'text-right'
3313
+ : 'text-left';
3314
+ const widthStyle = col['width']
3315
+ ? { width: String(col['width']) }
3316
+ : undefined;
3317
+ // Inline-edit cells take priority over read-only chrome.
3318
+ // `_cellEditable[name]` is set per row by `loadTableRecords`
3319
+ // only when `R.canEdit(user, row)` passed; the URL was
3320
+ // stamped by `tagCellEditUrls` immediately after.
3321
+ const editableMap = recordObj['_cellEditable'];
3322
+ const editUrlMap = recordObj['_cellEditUrls'];
3323
+ const cellDisabledMap = recordObj['_cellDisabled'];
3324
+ const editUrl = editableMap?.[name] ? editUrlMap?.[name] : undefined;
3325
+ const EditableComp = editUrl !== undefined
3326
+ ? pickEditableCell(String(col['columnType'] ?? 'text'))
3327
+ : null;
3328
+ if (EditableComp && editUrl !== undefined) {
3329
+ const cellDisabled = col['disabled'] === true || cellDisabledMap?.[name] === true;
3330
+ return (_jsx(TableCell, { className: `text-sm text-foreground ${align} p-0`, style: widthStyle, children: _jsx(EditableComp, { url: editUrl, col: col, value: value, disabled: cellDisabled }) }, ci));
3331
+ }
3332
+ const cellContent = formatCell(value, col, recordObj);
3333
+ const colUrl = resolveColumnUrl(col, tableUrl, colUrls);
3334
+ return (_jsx(TableCell, { className: `text-sm text-foreground ${align} p-0`, style: widthStyle, children: colUrl !== undefined
3335
+ ? _jsx(RecordCellLink, { href: colUrl, navigate: navigate, children: cellContent })
3336
+ : _jsx("div", { className: "px-2 py-2", children: cellContent }) }, ci));
3337
+ }), hasRowActions && (_jsx(TableCell, { className: "w-px text-right", children: renderRowActions(id, recordObj, rowActions) }))] })), (() => {
3338
+ if (!groupSummaries)
3339
+ return null;
3340
+ if (groupValue === undefined)
3341
+ return null;
3342
+ if (isInCollapsedGroup)
3343
+ return null;
3344
+ const isLastInGroup = ri === rows.length - 1
3345
+ || String((rows[ri + 1]['_groupValue'] ?? '')) !== groupValue;
3346
+ if (!isLastInGroup)
3347
+ return null;
3348
+ const perCol = groupSummaries[groupValue];
3349
+ if (!perCol || Object.keys(perCol).length === 0)
3350
+ return null;
3351
+ return (_jsxs(TableRow, { className: "bg-muted/20 hover:bg-muted/20", children: [reorderColumnVisible && _jsx(TableCell, {}), hasBulkActions && _jsx(TableCell, {}), columns.map((col, ci) => {
3352
+ const name = String(col['name'] ?? '');
3353
+ const align = col['alignment'] === 'center' ? 'text-center'
3354
+ : col['alignment'] === 'end' ? 'text-right'
3355
+ : 'text-left';
3356
+ const items = perCol[name];
3357
+ 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));
3358
+ }), hasRowActions && _jsx(TableCell, {})] }, `group-summary-${id}`));
3359
+ })()] }, id));
3360
+ }) }), summaries && Object.keys(summaries).length > 0 && (_jsx(TableFooter, { children: _jsxs(TableRow, { children: [reorderColumnVisible && _jsx(TableCell, {}), hasBulkActions && _jsx(TableCell, {}), columns.map((col, ci) => {
3361
+ const name = String(col['name'] ?? '');
3362
+ const align = col['alignment'] === 'center' ? 'text-center'
3363
+ : col['alignment'] === 'end' ? 'text-right'
3364
+ : 'text-left';
3365
+ const items = summaries[name];
3366
+ 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));
3367
+ }), 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 }))] }));
3368
+ }
3369
+ /**
3370
+ * Card-grid body for `Table.contentLayout('cards')`. Renders the rows
3371
+ * area only — the surrounding chrome (heading / search / filters /
3372
+ * pagination / bulk-action toolbar / "Sort by" picker) lives in the
3373
+ * parent `TableRendererBody` so both layouts share it.
3374
+ *
3375
+ * Each card renders its `_cardChildren` schema via the standard
3376
+ * `renderElement` walker, so any display-Element (Heading, Text, Image,
3377
+ * Icon, Badge entries, layout primitives, etc.) drops in without a new
3378
+ * renderer. Per-row chrome attaches via the same `_recordUrl` /
3379
+ * `_recordClasses` / `_visibleActions` / `_disabledActions` keys the
3380
+ * table-mode renderer reads from — `loadTableRecords` is unchanged.
3381
+ *
3382
+ * Group banding splits the rows into contiguous sections by
3383
+ * `_groupValue`, emitting a heading row above each section. The user's
3384
+ * configured per-card grid (`cardsPerRow`) re-applies inside every
3385
+ * section so the column count stays consistent.
3386
+ */
3387
+ function CardsLayoutBody({ el, columns, rows, visibleIds, selected, toggleRow, hasBulkActions, hasRowActions, rowActions, hasRecordUrl, hasRecordClasses, striped, activeEmpty, EmptyIcon, hasFilterOrSearch, defaultGroup, groupColumnLabel, groupCollapsible, collapsedGroups, toggleGroupCollapsed, cardsPerRow, navigate, }) {
3388
+ void el; // keep prop for future telemetry; silences unused-prop lint
3389
+ void columns;
3390
+ void striped; // visual stripes don't apply to cards (each card has its own surface)
3391
+ const gridClass = `grid gap-4 ${cardsPerRowClasses(cardsPerRow)}`;
3392
+ if (rows.length === 0) {
3393
+ return (_jsx("div", { className: "rounded-xl border bg-card 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
3394
+ ?? (hasFilterOrSearch ? 'No matching records' : 'No records yet') }), (activeEmpty?.description ||
3395
+ (hasFilterOrSearch && !activeEmpty?.description)) && (_jsx("p", { className: "text-sm", children: activeEmpty?.description
3396
+ ?? 'Try clearing filters or adjusting your search.' }))] }) }));
3397
+ }
3398
+ const sections = [];
3399
+ if (defaultGroup === undefined) {
3400
+ sections.push({ indices: rows.map((_, i) => i) });
3401
+ }
3402
+ else {
3403
+ let current;
3404
+ for (let i = 0; i < rows.length; i++) {
3405
+ const r = rows[i];
3406
+ const v = String(r['_groupValue'] ?? '');
3407
+ if (current === undefined || current.groupValue !== v) {
3408
+ const title = r['_groupTitle'];
3409
+ const description = r['_groupDescription'];
3410
+ current = { groupValue: v, indices: [], ...(title ? { title } : {}), ...(description ? { description } : {}) };
3411
+ sections.push(current);
3412
+ }
3413
+ current.indices.push(i);
3414
+ }
3415
+ }
3416
+ return (_jsx("div", { className: "flex flex-col gap-4", children: sections.map((section, si) => {
3417
+ const collapsed = groupCollapsible
3418
+ && section.groupValue !== undefined
3419
+ && collapsedGroups[section.groupValue] === true;
3420
+ return (_jsxs("div", { className: "flex flex-col gap-3", children: [section.groupValue !== undefined && (groupCollapsible ? (_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 text-left text-xs font-semibold uppercase tracking-wider text-muted-foreground", onClick: () => toggleGroupCollapsed(section.groupValue), "aria-expanded": !collapsed, children: [_jsx(ChevronDownIcon, { className: [
3421
+ 'size-4 transition-transform',
3422
+ collapsed ? '-rotate-90' : '',
3423
+ ].filter(Boolean).join(' ') }), _jsx(GroupHeaderText, { label: groupColumnLabel, value: section.groupValue, title: section.title, description: section.description })] })) : (_jsx("div", { className: "text-xs font-semibold uppercase tracking-wider text-muted-foreground", children: _jsx(GroupHeaderText, { label: groupColumnLabel, value: section.groupValue, title: section.title, description: section.description }) }))), !collapsed && (_jsx("div", { className: gridClass, children: section.indices.map((ri) => {
3424
+ const id = visibleIds[ri];
3425
+ const recordObj = rows[ri];
3426
+ const isSelected = selected.has(id);
3427
+ const recordUrl = hasRecordUrl ? recordObj['_recordUrl'] : undefined;
3428
+ const customRowClasses = hasRecordClasses
3429
+ ? recordObj['_recordClasses'] ?? ''
3430
+ : '';
3431
+ const cardChildren = recordObj['_cardChildren'] ?? [];
3432
+ const cardClassName = [
3433
+ 'group relative flex flex-col gap-3 rounded-xl border bg-card p-4 transition-colors',
3434
+ recordUrl ? 'hover:border-primary/40 hover:bg-accent/30' : '',
3435
+ isSelected ? 'border-primary ring-2 ring-primary/20' : '',
3436
+ customRowClasses,
3437
+ ].filter(Boolean).join(' ');
3438
+ const onLinkClick = (e) => {
3439
+ if (e.button !== 0)
3440
+ return;
3441
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
3442
+ return;
3443
+ e.preventDefault();
3444
+ if (recordUrl)
3445
+ void navigate(recordUrl);
3446
+ };
3447
+ return (_jsxs("div", { className: cardClassName, children: [recordUrl !== undefined && (_jsx("a", { href: recordUrl, onClick: onLinkClick, "aria-label": "Open record", className: "absolute inset-0 z-0 rounded-xl focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: _jsx("span", { className: "sr-only", children: "Open record" }) })), hasBulkActions && (_jsx("div", { className: "absolute top-3 right-3 z-10", children: _jsx(Checkbox, { "aria-label": `Select row ${id}`, checked: isSelected, onCheckedChange: () => toggleRow(id), "data-no-row-nav": true }) })), _jsx("div", { className: "relative z-[1] flex flex-col gap-3", children: cardChildren.length === 0 ? (_jsx("div", { className: "text-xs italic text-muted-foreground", children: "No card content configured." })) : cardChildren.map((c, i) => renderElement(c, i)) }), hasRowActions && (_jsx("div", { className: "relative z-10 mt-auto flex items-center justify-end pt-2 border-t border-border/60", children: renderRowActions(id, recordObj, rowActions) }))] }, id));
3448
+ }) }))] }, si));
3449
+ }) }));
3450
+ }
3451
+ export function SchemaRenderer({ elements, widgetData }) {
3452
+ if (!elements || elements.length === 0)
3453
+ return null;
3454
+ // exactOptionalPropertyTypes: only spread `data` when defined.
3455
+ const providerProps = widgetData !== undefined ? { data: widgetData } : {};
3456
+ return (_jsx(WidgetDataProvider, { ...providerProps, children: _jsx("div", { className: "flex flex-col gap-6", children: elements.map((el, i) => renderElement(el, i)) }) }));
3457
+ }
3458
+ //# sourceMappingURL=SchemaRenderer.js.map