@pilotiq/pilotiq 0.7.1 → 0.8.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 (367) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +154 -0
  3. package/CLAUDE.md +59 -3
  4. package/dist/Pilotiq.d.ts +83 -0
  5. package/dist/Pilotiq.d.ts.map +1 -1
  6. package/dist/Pilotiq.js +39 -0
  7. package/dist/Pilotiq.js.map +1 -1
  8. package/dist/actions/Action.d.ts +27 -99
  9. package/dist/actions/Action.d.ts.map +1 -1
  10. package/dist/actions/Action.js +52 -754
  11. package/dist/actions/Action.js.map +1 -1
  12. package/dist/actions/bulkFactories.d.ts +46 -0
  13. package/dist/actions/bulkFactories.d.ts.map +1 -0
  14. package/dist/actions/bulkFactories.js +144 -0
  15. package/dist/actions/bulkFactories.js.map +1 -0
  16. package/dist/actions/crudFactories.d.ts +94 -0
  17. package/dist/actions/crudFactories.d.ts.map +1 -0
  18. package/dist/actions/crudFactories.js +209 -0
  19. package/dist/actions/crudFactories.js.map +1 -0
  20. package/dist/actions/factoryHelpers.d.ts +108 -0
  21. package/dist/actions/factoryHelpers.d.ts.map +1 -0
  22. package/dist/actions/factoryHelpers.js +138 -0
  23. package/dist/actions/factoryHelpers.js.map +1 -0
  24. package/dist/actions/m2mFactories.d.ts +47 -0
  25. package/dist/actions/m2mFactories.d.ts.map +1 -0
  26. package/dist/actions/m2mFactories.js +173 -0
  27. package/dist/actions/m2mFactories.js.map +1 -0
  28. package/dist/actions/relationFactories.d.ts +93 -0
  29. package/dist/actions/relationFactories.d.ts.map +1 -0
  30. package/dist/actions/relationFactories.js +321 -0
  31. package/dist/actions/relationFactories.js.map +1 -0
  32. package/dist/elements/dispatchForm.js +1 -1
  33. package/dist/elements/dispatchForm.js.map +1 -1
  34. package/dist/elements/dispatchTable.js +1 -1
  35. package/dist/elements/dispatchTable.js.map +1 -1
  36. package/dist/fields/Field.d.ts +31 -0
  37. package/dist/fields/Field.d.ts.map +1 -1
  38. package/dist/fields/Field.js +25 -0
  39. package/dist/fields/Field.js.map +1 -1
  40. package/dist/pageData/breadcrumbs.d.ts +42 -0
  41. package/dist/pageData/breadcrumbs.d.ts.map +1 -0
  42. package/dist/pageData/breadcrumbs.js +172 -0
  43. package/dist/pageData/breadcrumbs.js.map +1 -0
  44. package/dist/pageData/forms.d.ts +137 -0
  45. package/dist/pageData/forms.d.ts.map +1 -0
  46. package/dist/pageData/forms.js +427 -0
  47. package/dist/pageData/forms.js.map +1 -0
  48. package/dist/pageData/helpers.d.ts +239 -0
  49. package/dist/pageData/helpers.d.ts.map +1 -0
  50. package/dist/pageData/helpers.js +703 -0
  51. package/dist/pageData/helpers.js.map +1 -0
  52. package/dist/pageData/misc.d.ts +76 -0
  53. package/dist/pageData/misc.d.ts.map +1 -0
  54. package/dist/pageData/misc.js +263 -0
  55. package/dist/pageData/misc.js.map +1 -0
  56. package/dist/pageData/navigation.d.ts +292 -0
  57. package/dist/pageData/navigation.d.ts.map +1 -0
  58. package/dist/pageData/navigation.js +591 -0
  59. package/dist/pageData/navigation.js.map +1 -0
  60. package/dist/pageData/relationPages.d.ts +172 -0
  61. package/dist/pageData/relationPages.d.ts.map +1 -0
  62. package/dist/pageData/relationPages.js +867 -0
  63. package/dist/pageData/relationPages.js.map +1 -0
  64. package/dist/pageData/relationTabs.d.ts +65 -0
  65. package/dist/pageData/relationTabs.d.ts.map +1 -0
  66. package/dist/pageData/relationTabs.js +258 -0
  67. package/dist/pageData/relationTabs.js.map +1 -0
  68. package/dist/pageData/resourcePages.d.ts +48 -0
  69. package/dist/pageData/resourcePages.d.ts.map +1 -0
  70. package/dist/pageData/resourcePages.js +504 -0
  71. package/dist/pageData/resourcePages.js.map +1 -0
  72. package/dist/pageData.d.ts +12 -792
  73. package/dist/pageData.d.ts.map +1 -1
  74. package/dist/pageData.js +24 -3797
  75. package/dist/pageData.js.map +1 -1
  76. package/dist/react/AppShell.d.ts +8 -0
  77. package/dist/react/AppShell.d.ts.map +1 -1
  78. package/dist/react/AppShell.js +11 -1
  79. package/dist/react/AppShell.js.map +1 -1
  80. package/dist/react/CollabExtensionFactoryRegistry.d.ts +47 -0
  81. package/dist/react/CollabExtensionFactoryRegistry.d.ts.map +1 -0
  82. package/dist/react/CollabExtensionFactoryRegistry.js +14 -0
  83. package/dist/react/CollabExtensionFactoryRegistry.js.map +1 -0
  84. package/dist/react/CollabRoomContext.d.ts +37 -0
  85. package/dist/react/CollabRoomContext.d.ts.map +1 -0
  86. package/dist/react/CollabRoomContext.js +12 -0
  87. package/dist/react/CollabRoomContext.js.map +1 -0
  88. package/dist/react/FormCollabBindingRegistry.d.ts +62 -0
  89. package/dist/react/FormCollabBindingRegistry.d.ts.map +1 -0
  90. package/dist/react/FormCollabBindingRegistry.js +14 -0
  91. package/dist/react/FormCollabBindingRegistry.js.map +1 -0
  92. package/dist/react/RecordWrapperGate.d.ts +25 -0
  93. package/dist/react/RecordWrapperGate.d.ts.map +1 -0
  94. package/dist/react/RecordWrapperGate.js +30 -0
  95. package/dist/react/RecordWrapperGate.js.map +1 -0
  96. package/dist/react/RecordWrapperRegistry.d.ts +31 -0
  97. package/dist/react/RecordWrapperRegistry.d.ts.map +1 -0
  98. package/dist/react/RecordWrapperRegistry.js +15 -0
  99. package/dist/react/RecordWrapperRegistry.js.map +1 -0
  100. package/dist/react/SchemaRenderer.d.ts +17 -23
  101. package/dist/react/SchemaRenderer.d.ts.map +1 -1
  102. package/dist/react/SchemaRenderer.js +71 -3647
  103. package/dist/react/SchemaRenderer.js.map +1 -1
  104. package/dist/react/component-slots.d.ts +103 -0
  105. package/dist/react/component-slots.d.ts.map +1 -0
  106. package/dist/react/component-slots.js +18 -0
  107. package/dist/react/component-slots.js.map +1 -0
  108. package/dist/react/fields/BuilderInput.d.ts.map +1 -1
  109. package/dist/react/fields/BuilderInput.js +21 -117
  110. package/dist/react/fields/BuilderInput.js.map +1 -1
  111. package/dist/react/fields/MarkdownInput.d.ts.map +1 -1
  112. package/dist/react/fields/MarkdownInput.js +1 -3
  113. package/dist/react/fields/MarkdownInput.js.map +1 -1
  114. package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
  115. package/dist/react/fields/RepeaterInput.js +22 -127
  116. package/dist/react/fields/RepeaterInput.js.map +1 -1
  117. package/dist/react/fields/rowState.d.ts +40 -0
  118. package/dist/react/fields/rowState.d.ts.map +1 -0
  119. package/dist/react/fields/rowState.js +60 -0
  120. package/dist/react/fields/rowState.js.map +1 -0
  121. package/dist/react/fields/useRowReorderDnd.d.ts +28 -0
  122. package/dist/react/fields/useRowReorderDnd.d.ts.map +1 -0
  123. package/dist/react/fields/useRowReorderDnd.js +51 -0
  124. package/dist/react/fields/useRowReorderDnd.js.map +1 -0
  125. package/dist/react/index.d.ts +9 -0
  126. package/dist/react/index.d.ts.map +1 -1
  127. package/dist/react/index.js +8 -0
  128. package/dist/react/index.js.map +1 -1
  129. package/dist/react/layouts/SidebarLayout.d.ts +1 -1
  130. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  131. package/dist/react/layouts/SidebarLayout.js +10 -2
  132. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  133. package/dist/react/layouts/TopbarLayout.d.ts +1 -1
  134. package/dist/react/layouts/TopbarLayout.d.ts.map +1 -1
  135. package/dist/react/layouts/TopbarLayout.js +19 -11
  136. package/dist/react/layouts/TopbarLayout.js.map +1 -1
  137. package/dist/react/parseRecordEditUrl.d.ts +29 -0
  138. package/dist/react/parseRecordEditUrl.d.ts.map +1 -0
  139. package/dist/react/parseRecordEditUrl.js +25 -0
  140. package/dist/react/parseRecordEditUrl.js.map +1 -0
  141. package/dist/react/persistedState.d.ts +19 -0
  142. package/dist/react/persistedState.d.ts.map +1 -0
  143. package/dist/react/persistedState.js +51 -0
  144. package/dist/react/persistedState.js.map +1 -0
  145. package/dist/react/schemaRenderer/AlertRenderer.d.ts +12 -0
  146. package/dist/react/schemaRenderer/AlertRenderer.d.ts.map +1 -0
  147. package/dist/react/schemaRenderer/AlertRenderer.js +61 -0
  148. package/dist/react/schemaRenderer/AlertRenderer.js.map +1 -0
  149. package/dist/react/schemaRenderer/EntryRenderer.d.ts +13 -0
  150. package/dist/react/schemaRenderer/EntryRenderer.d.ts.map +1 -0
  151. package/dist/react/schemaRenderer/EntryRenderer.js +277 -0
  152. package/dist/react/schemaRenderer/EntryRenderer.js.map +1 -0
  153. package/dist/react/schemaRenderer/SectionRenderer.d.ts +16 -0
  154. package/dist/react/schemaRenderer/SectionRenderer.d.ts.map +1 -0
  155. package/dist/react/schemaRenderer/SectionRenderer.js +62 -0
  156. package/dist/react/schemaRenderer/SectionRenderer.js.map +1 -0
  157. package/dist/react/schemaRenderer/SimpleElements.d.ts +25 -0
  158. package/dist/react/schemaRenderer/SimpleElements.d.ts.map +1 -0
  159. package/dist/react/schemaRenderer/SimpleElements.js +147 -0
  160. package/dist/react/schemaRenderer/SimpleElements.js.map +1 -0
  161. package/dist/react/schemaRenderer/TabsRenderer.d.ts +17 -0
  162. package/dist/react/schemaRenderer/TabsRenderer.d.ts.map +1 -0
  163. package/dist/react/schemaRenderer/TabsRenderer.js +31 -0
  164. package/dist/react/schemaRenderer/TabsRenderer.js.map +1 -0
  165. package/dist/react/schemaRenderer/WizardRenderer.d.ts +34 -0
  166. package/dist/react/schemaRenderer/WizardRenderer.d.ts.map +1 -0
  167. package/dist/react/schemaRenderer/WizardRenderer.js +208 -0
  168. package/dist/react/schemaRenderer/WizardRenderer.js.map +1 -0
  169. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts +21 -0
  170. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts.map +1 -0
  171. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js +82 -0
  172. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js.map +1 -0
  173. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts +30 -0
  174. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts.map +1 -0
  175. package/dist/react/schemaRenderer/action/ActionModalDialog.js +182 -0
  176. package/dist/react/schemaRenderer/action/ActionModalDialog.js.map +1 -0
  177. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts +17 -0
  178. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts.map +1 -0
  179. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js +19 -0
  180. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js.map +1 -0
  181. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts +16 -0
  182. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts.map +1 -0
  183. package/dist/react/schemaRenderer/action/HandlerActionButton.js +16 -0
  184. package/dist/react/schemaRenderer/action/HandlerActionButton.js.map +1 -0
  185. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts +22 -0
  186. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts.map +1 -0
  187. package/dist/react/schemaRenderer/action/MethodActionButton.js +26 -0
  188. package/dist/react/schemaRenderer/action/MethodActionButton.js.map +1 -0
  189. package/dist/react/schemaRenderer/action/buttons.d.ts +18 -0
  190. package/dist/react/schemaRenderer/action/buttons.d.ts.map +1 -0
  191. package/dist/react/schemaRenderer/action/buttons.js +74 -0
  192. package/dist/react/schemaRenderer/action/buttons.js.map +1 -0
  193. package/dist/react/schemaRenderer/action/helpers.d.ts +26 -0
  194. package/dist/react/schemaRenderer/action/helpers.d.ts.map +1 -0
  195. package/dist/react/schemaRenderer/action/helpers.js +126 -0
  196. package/dist/react/schemaRenderer/action/helpers.js.map +1 -0
  197. package/dist/react/schemaRenderer/action/renderAction.d.ts +21 -0
  198. package/dist/react/schemaRenderer/action/renderAction.d.ts.map +1 -0
  199. package/dist/react/schemaRenderer/action/renderAction.js +102 -0
  200. package/dist/react/schemaRenderer/action/renderAction.js.map +1 -0
  201. package/dist/react/schemaRenderer/columnFormat.d.ts +10 -0
  202. package/dist/react/schemaRenderer/columnFormat.d.ts.map +1 -0
  203. package/dist/react/schemaRenderer/columnFormat.js +76 -0
  204. package/dist/react/schemaRenderer/columnFormat.js.map +1 -0
  205. package/dist/react/schemaRenderer/constants.d.ts +8 -0
  206. package/dist/react/schemaRenderer/constants.d.ts.map +1 -0
  207. package/dist/react/schemaRenderer/constants.js +45 -0
  208. package/dist/react/schemaRenderer/constants.js.map +1 -0
  209. package/dist/react/schemaRenderer/form/FormRenderer.d.ts +29 -0
  210. package/dist/react/schemaRenderer/form/FormRenderer.d.ts.map +1 -0
  211. package/dist/react/schemaRenderer/form/FormRenderer.js +152 -0
  212. package/dist/react/schemaRenderer/form/FormRenderer.js.map +1 -0
  213. package/dist/react/schemaRenderer/form/renderField.d.ts +6 -0
  214. package/dist/react/schemaRenderer/form/renderField.d.ts.map +1 -0
  215. package/dist/react/schemaRenderer/form/renderField.js +239 -0
  216. package/dist/react/schemaRenderer/form/renderField.js.map +1 -0
  217. package/dist/react/schemaRenderer/helpers.d.ts +32 -0
  218. package/dist/react/schemaRenderer/helpers.d.ts.map +1 -0
  219. package/dist/react/schemaRenderer/helpers.js +52 -0
  220. package/dist/react/schemaRenderer/helpers.js.map +1 -0
  221. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts +60 -0
  222. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts.map +1 -0
  223. package/dist/react/schemaRenderer/table/CardsLayoutBody.js +189 -0
  224. package/dist/react/schemaRenderer/table/CardsLayoutBody.js.map +1 -0
  225. package/dist/react/schemaRenderer/table/TableRenderer.d.ts +29 -0
  226. package/dist/react/schemaRenderer/table/TableRenderer.d.ts.map +1 -0
  227. package/dist/react/schemaRenderer/table/TableRenderer.js +85 -0
  228. package/dist/react/schemaRenderer/table/TableRenderer.js.map +1 -0
  229. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts +18 -0
  230. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts.map +1 -0
  231. package/dist/react/schemaRenderer/table/TableRendererBody.js +555 -0
  232. package/dist/react/schemaRenderer/table/TableRendererBody.js.map +1 -0
  233. package/dist/react/schemaRenderer/table/filters.d.ts +263 -0
  234. package/dist/react/schemaRenderer/table/filters.d.ts.map +1 -0
  235. package/dist/react/schemaRenderer/table/filters.js +497 -0
  236. package/dist/react/schemaRenderer/table/filters.js.map +1 -0
  237. package/dist/react/schemaRenderer/table/formatCell.d.ts +11 -0
  238. package/dist/react/schemaRenderer/table/formatCell.d.ts.map +1 -0
  239. package/dist/react/schemaRenderer/table/formatCell.js +172 -0
  240. package/dist/react/schemaRenderer/table/formatCell.js.map +1 -0
  241. package/dist/react/schemaRenderer/table/links.d.ts +42 -0
  242. package/dist/react/schemaRenderer/table/links.d.ts.map +1 -0
  243. package/dist/react/schemaRenderer/table/links.js +55 -0
  244. package/dist/react/schemaRenderer/table/links.js.map +1 -0
  245. package/dist/react/schemaRenderer/table/renderRowActions.d.ts +13 -0
  246. package/dist/react/schemaRenderer/table/renderRowActions.d.ts.map +1 -0
  247. package/dist/react/schemaRenderer/table/renderRowActions.js +25 -0
  248. package/dist/react/schemaRenderer/table/renderRowActions.js.map +1 -0
  249. package/dist/react/schemaRenderer/table/url.d.ts +41 -0
  250. package/dist/react/schemaRenderer/table/url.d.ts.map +1 -0
  251. package/dist/react/schemaRenderer/table/url.js +114 -0
  252. package/dist/react/schemaRenderer/table/url.js.map +1 -0
  253. package/dist/routes/globals.d.ts +13 -0
  254. package/dist/routes/globals.d.ts.map +1 -0
  255. package/dist/routes/globals.js +131 -0
  256. package/dist/routes/globals.js.map +1 -0
  257. package/dist/routes/helpers.d.ts +217 -0
  258. package/dist/routes/helpers.d.ts.map +1 -0
  259. package/dist/routes/helpers.js +498 -0
  260. package/dist/routes/helpers.js.map +1 -0
  261. package/dist/routes/pages.d.ts +15 -0
  262. package/dist/routes/pages.d.ts.map +1 -0
  263. package/dist/routes/pages.js +145 -0
  264. package/dist/routes/pages.js.map +1 -0
  265. package/dist/routes/panel.d.ts +19 -0
  266. package/dist/routes/panel.d.ts.map +1 -0
  267. package/dist/routes/panel.js +191 -0
  268. package/dist/routes/panel.js.map +1 -0
  269. package/dist/routes/relations.d.ts +21 -0
  270. package/dist/routes/relations.d.ts.map +1 -0
  271. package/dist/routes/relations.js +1239 -0
  272. package/dist/routes/relations.js.map +1 -0
  273. package/dist/routes/resources.d.ts +28 -0
  274. package/dist/routes/resources.d.ts.map +1 -0
  275. package/dist/routes/resources.js +741 -0
  276. package/dist/routes/resources.js.map +1 -0
  277. package/dist/routes/theme.d.ts +12 -0
  278. package/dist/routes/theme.d.ts.map +1 -0
  279. package/dist/routes/theme.js +82 -0
  280. package/dist/routes/theme.js.map +1 -0
  281. package/dist/routes.d.ts.map +1 -1
  282. package/dist/routes.js +64 -3078
  283. package/dist/routes.js.map +1 -1
  284. package/dist/vite.d.ts +1 -0
  285. package/dist/vite.d.ts.map +1 -1
  286. package/dist/vite.js +31 -10
  287. package/dist/vite.js.map +1 -1
  288. package/package.json +2 -1
  289. package/src/Pilotiq.ts +95 -0
  290. package/src/actions/Action.ts +79 -723
  291. package/src/actions/bulkFactories.ts +168 -0
  292. package/src/actions/crudFactories.ts +220 -0
  293. package/src/actions/factoryHelpers.ts +177 -0
  294. package/src/actions/m2mFactories.ts +193 -0
  295. package/src/actions/relationFactories.ts +372 -0
  296. package/src/elements/dispatchForm.ts +1 -1
  297. package/src/elements/dispatchTable.ts +1 -1
  298. package/src/fields/Field.ts +39 -0
  299. package/src/pageData/breadcrumbs.ts +288 -0
  300. package/src/pageData/forms.ts +578 -0
  301. package/src/pageData/helpers.ts +764 -0
  302. package/src/pageData/misc.ts +347 -0
  303. package/src/pageData/navigation.ts +779 -0
  304. package/src/pageData/relationPages.ts +1246 -0
  305. package/src/pageData/relationTabs.ts +286 -0
  306. package/src/pageData/resourcePages.ts +593 -0
  307. package/src/pageData.ts +122 -4731
  308. package/src/react/AppShell.tsx +27 -1
  309. package/src/react/CollabExtensionFactoryRegistry.ts +55 -0
  310. package/src/react/CollabRoomContext.ts +42 -0
  311. package/src/react/FormCollabBindingRegistry.ts +72 -0
  312. package/src/react/RecordWrapperGate.tsx +40 -0
  313. package/src/react/RecordWrapperRegistry.ts +39 -0
  314. package/src/react/SchemaRenderer.tsx +230 -6479
  315. package/src/react/component-slots.test.ts +103 -0
  316. package/src/react/component-slots.ts +116 -0
  317. package/src/react/fields/BuilderInput.tsx +29 -117
  318. package/src/react/fields/MarkdownInput.tsx +0 -1
  319. package/src/react/fields/RepeaterInput.tsx +29 -130
  320. package/src/react/fields/rowState.ts +106 -0
  321. package/src/react/fields/useRowReorderDnd.ts +78 -0
  322. package/src/react/index.ts +38 -0
  323. package/src/react/layouts/SidebarLayout.tsx +39 -28
  324. package/src/react/layouts/TopbarLayout.tsx +70 -57
  325. package/src/react/parseRecordEditUrl.test.ts +75 -0
  326. package/src/react/parseRecordEditUrl.ts +55 -0
  327. package/src/react/persistedState.ts +40 -0
  328. package/src/react/schemaRenderer/AlertRenderer.tsx +112 -0
  329. package/src/react/schemaRenderer/EntryRenderer.tsx +501 -0
  330. package/src/react/schemaRenderer/SectionRenderer.tsx +120 -0
  331. package/src/react/schemaRenderer/SimpleElements.tsx +306 -0
  332. package/src/react/schemaRenderer/TabsRenderer.tsx +62 -0
  333. package/src/react/schemaRenderer/WizardRenderer.tsx +338 -0
  334. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +177 -0
  335. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +273 -0
  336. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +61 -0
  337. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +43 -0
  338. package/src/react/schemaRenderer/action/MethodActionButton.tsx +64 -0
  339. package/src/react/schemaRenderer/action/buttons.tsx +99 -0
  340. package/src/react/schemaRenderer/action/helpers.ts +140 -0
  341. package/src/react/schemaRenderer/action/renderAction.tsx +245 -0
  342. package/src/react/schemaRenderer/columnFormat.ts +65 -0
  343. package/src/react/schemaRenderer/constants.ts +50 -0
  344. package/src/react/schemaRenderer/form/FormRenderer.tsx +233 -0
  345. package/src/react/schemaRenderer/form/renderField.tsx +511 -0
  346. package/src/react/schemaRenderer/helpers.tsx +81 -0
  347. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +308 -0
  348. package/src/react/schemaRenderer/table/TableRenderer.tsx +123 -0
  349. package/src/react/schemaRenderer/table/TableRendererBody.tsx +974 -0
  350. package/src/react/schemaRenderer/table/filters.tsx +1233 -0
  351. package/src/react/schemaRenderer/table/formatCell.tsx +264 -0
  352. package/src/react/schemaRenderer/table/links.tsx +112 -0
  353. package/src/react/schemaRenderer/table/renderRowActions.tsx +52 -0
  354. package/src/react/schemaRenderer/table/url.tsx +143 -0
  355. package/src/routes/globals.ts +154 -0
  356. package/src/routes/helpers.ts +668 -0
  357. package/src/routes/pages.ts +173 -0
  358. package/src/routes/panel.ts +204 -0
  359. package/src/routes/relations.ts +1219 -0
  360. package/src/routes/resources.ts +786 -0
  361. package/src/routes/theme.ts +109 -0
  362. package/src/routes.test.ts +1 -1
  363. package/src/routes.ts +64 -3176
  364. package/src/schema/TableWidget.test.ts +2 -2
  365. package/src/theme/migrate.test.ts +178 -0
  366. package/src/vite.test.ts +184 -0
  367. package/src/vite.ts +31 -9
@@ -0,0 +1,511 @@
1
+ import React from 'react'
2
+ import type { ElementMeta } from '../../../schema/Element.js'
3
+ import { FieldShell } from '../../fields/FieldShell.js'
4
+ import { TextLikeInput } from '../../fields/TextLikeInput.js'
5
+ import { useTextInputControls } from '../../fields/textInputControls.js'
6
+ import { SelectFieldInput } from '../../fields/SelectFieldInput.js'
7
+ import { ToggleFieldInput } from '../../fields/ToggleFieldInput.js'
8
+ import { DateFieldInput } from '../../fields/DateFieldInput.js'
9
+ import { HiddenInput } from '../../fields/HiddenInput.js'
10
+ import { CheckboxInput } from '../../fields/CheckboxInput.js'
11
+ import { RadioInput } from '../../fields/RadioInput.js'
12
+ import { ToggleButtonsInput } from '../../fields/ToggleButtonsInput.js'
13
+ import { CheckboxListInput } from '../../fields/CheckboxListInput.js'
14
+ import { SliderInput } from '../../fields/SliderInput.js'
15
+ import { ColorInput } from '../../fields/ColorInput.js'
16
+ import { DateTimeInput } from '../../fields/DateTimeInput.js'
17
+ import { KeyValueInput } from '../../fields/KeyValueInput.js'
18
+ import { TagsInput } from '../../fields/TagsInput.js'
19
+ import { FileUploadInput } from '../../fields/FileUploadInput.js'
20
+ import { MarkdownInput } from '../../fields/MarkdownInput.js'
21
+ import { RepeaterInput } from '../../fields/RepeaterInput.js'
22
+ import { BuilderInput } from '../../fields/BuilderInput.js'
23
+ import { getFieldRenderer } from '../../registry.js'
24
+ import { getFieldLabelSlot } from '../../FieldLabelSlotRegistry.js'
25
+
26
+ // ─── Field rendering ────────────────────────────────────────
27
+ //
28
+ // Each input lives in its own file under `react/fields/`. This file
29
+ // stays a thin dispatcher: parse meta → pick component → wrap in
30
+ // `<FieldShell>`. The only renderElement coupling is inside
31
+ // `TextFieldShell` (suffix-action Element rendering) — injected to
32
+ // avoid the SchemaRenderer ↔ form cycle.
33
+
34
+ type RenderElement = (el: ElementMeta, index: number) => React.ReactNode
35
+
36
+ export function renderField(
37
+ el: ElementMeta,
38
+ index: number,
39
+ renderElement: RenderElement,
40
+ ): React.ReactNode {
41
+ const fieldType = String(el['fieldType'] ?? 'text')
42
+ const name = String(el['name'] ?? '')
43
+ const label = String(el['label'] ?? name)
44
+ const required = Boolean(el['required'])
45
+ const disabled = Boolean(el['disabled'])
46
+ const placeholder = el['placeholder'] ? String(el['placeholder']) : undefined
47
+ const defaultValue = el['defaultValue']
48
+ const defaultStr = defaultValue !== undefined && defaultValue !== null ? String(defaultValue) : undefined
49
+
50
+ // Hidden fields render bare — no label, no shell, no chrome. Bail
51
+ // before the renderField switch + FieldShell wrap.
52
+ if (fieldType === 'hidden') {
53
+ return <HiddenInput key={index} name={name} defaultValue={defaultValue} />
54
+ }
55
+
56
+ // Field label slot — rendered next to the label when a plugin registered
57
+ // a component via registerFieldLabelSlot() and the field has aiActions +
58
+ // _agentRunBase stamped on its meta (set by tagFieldAiUrls in pageData).
59
+ const LabelSlot = getFieldLabelSlot()
60
+ const aiActions = Array.isArray(el['aiActions']) ? el['aiActions'] as Array<{ slug: string; label: string; icon?: string }> : undefined
61
+ const agentRunBase = typeof el['_agentRunBase'] === 'string' ? el['_agentRunBase'] : undefined
62
+ const labelSlot = (LabelSlot && aiActions?.length && agentRunBase)
63
+ ? <LabelSlot fieldName={name} actions={aiActions} agentRunBase={agentRunBase} />
64
+ : undefined
65
+
66
+ const autofocus = el['autofocus'] === true
67
+ const extraInput = el['extraInputAttributes'] as Record<string, string | number | boolean> | undefined
68
+ const common = {
69
+ id: name,
70
+ name,
71
+ disabled,
72
+ placeholder,
73
+ required,
74
+ ...(defaultStr !== undefined ? { defaultValue: defaultStr } : {}),
75
+ ...(autofocus ? { autoFocus: true } : {}),
76
+ ...(extraInput ?? {}),
77
+ }
78
+
79
+ // External packages (e.g. @pilotiq/tiptap) register custom renderers
80
+ // for non-built-in fieldTypes. The registry wins over the built-in
81
+ // switch so consumers can override built-ins too if they want.
82
+ const Custom = getFieldRenderer(fieldType)
83
+ if (Custom) {
84
+ return (
85
+ <FieldShell key={index} el={el} name={name} label={label} required={required} labelSlot={labelSlot}>
86
+ <Custom
87
+ el={el}
88
+ name={name}
89
+ defaultValue={defaultValue}
90
+ required={required}
91
+ disabled={disabled}
92
+ placeholder={placeholder}
93
+ />
94
+ </FieldShell>
95
+ )
96
+ }
97
+
98
+ // TextField (and slug) rich affordances live in a dedicated shell so
99
+ // `useTextInputControls` can hold reveal-toggle / mask state via React
100
+ // hooks (renderField itself is a plain function, hooks would violate
101
+ // rules-of-hooks here).
102
+ if (fieldType === 'text' || fieldType === 'slug') {
103
+ return (
104
+ <TextFieldShell
105
+ key={index}
106
+ el={el}
107
+ name={name}
108
+ label={label}
109
+ required={required}
110
+ common={common}
111
+ labelSlot={labelSlot}
112
+ renderElement={renderElement}
113
+ />
114
+ )
115
+ }
116
+
117
+ const input = renderFieldInput(fieldType, el, name, defaultValue, defaultStr, common, disabled, required, placeholder)
118
+
119
+ return (
120
+ <FieldShell key={index} el={el} name={name} label={label} required={required} labelSlot={labelSlot}>
121
+ {input}
122
+ </FieldShell>
123
+ )
124
+ }
125
+
126
+ /**
127
+ * Component-shape TextField renderer — wraps the input shell so we can
128
+ * use `useTextInputControls()` (which holds the eye-toggle / mask state).
129
+ * Keeps `renderField` itself hook-free.
130
+ */
131
+ function TextFieldShell({
132
+ el, name, label, required, common, labelSlot, renderElement,
133
+ }: {
134
+ el: ElementMeta
135
+ name: string
136
+ label: string
137
+ required: boolean
138
+ common: Record<string, unknown>
139
+ labelSlot?: React.ReactNode
140
+ renderElement: RenderElement
141
+ }): React.ReactElement {
142
+ const controls = useTextInputControls(el, name, (m) => renderElement(m, 0))
143
+
144
+ // Build the input with all the new HTML attrs (inputMode /
145
+ // autocapitalize / list / maxLength + the password/text type from
146
+ // the controls hook).
147
+ const textExtra: Record<string, unknown> = {}
148
+ if (el['maxLength'] !== undefined) textExtra['maxLength'] = Number(el['maxLength'])
149
+ if (el['inputMode'] !== undefined) textExtra['inputMode'] = String(el['inputMode'])
150
+ if (el['autocapitalize'] !== undefined) textExtra['autoCapitalize'] = String(el['autocapitalize'])
151
+ if (Array.isArray(el['datalist'])) textExtra['list'] = `${name}__datalist`
152
+
153
+ const datalist = Array.isArray(el['datalist']) ? (el['datalist'] as string[]) : undefined
154
+
155
+ const input = (
156
+ <>
157
+ <TextLikeInput
158
+ el={el}
159
+ name={name}
160
+ common={common}
161
+ type={controls.type}
162
+ extraProps={textExtra}
163
+ multiline={false}
164
+ applyMask={controls.applyMask}
165
+ />
166
+ {datalist && (
167
+ <datalist id={`${name}__datalist`}>
168
+ {datalist.map((v, i) => <option key={i} value={v} />)}
169
+ </datalist>
170
+ )}
171
+ </>
172
+ )
173
+
174
+ return (
175
+ <FieldShell
176
+ el={el}
177
+ name={name}
178
+ label={label}
179
+ required={required}
180
+ before={controls.before}
181
+ after={controls.after}
182
+ labelSlot={labelSlot}
183
+ >
184
+ {input}
185
+ </FieldShell>
186
+ )
187
+ }
188
+
189
+ function renderFieldInput(
190
+ fieldType: string,
191
+ el: ElementMeta,
192
+ name: string,
193
+ defaultValue: unknown,
194
+ defaultStr: string | undefined,
195
+ common: Record<string, unknown>,
196
+ disabled: boolean,
197
+ required: boolean,
198
+ placeholder: string | undefined,
199
+ ): React.ReactNode {
200
+ switch (fieldType) {
201
+ case 'textarea': {
202
+ const autosize = el['autosize'] === true
203
+ const cols = typeof el['cols'] === 'number' ? Number(el['cols']) : undefined
204
+ const extra: Record<string, unknown> = {}
205
+ // `field-sizing-content` on the Textarea component already grows
206
+ // the box with content; `autosize()` just unsets the explicit
207
+ // `rows` so the browser doesn't reserve a fixed minimum height.
208
+ if (!autosize) extra['rows'] = Number(el['rows']) || 4
209
+ if (cols !== undefined) extra['cols'] = cols
210
+ if (el['disableGrammarly'] === true) {
211
+ extra['data-gramm'] = 'false'
212
+ extra['data-gramm_editor'] = 'false'
213
+ extra['data-enable-grammarly'] = 'false'
214
+ }
215
+ return (
216
+ <TextLikeInput
217
+ el={el}
218
+ name={name}
219
+ common={common}
220
+ type="text"
221
+ extraProps={extra}
222
+ multiline
223
+ />
224
+ )
225
+ }
226
+
227
+ case 'select': {
228
+ const options = (el['options'] as Array<{ value: string; label: string; disabled?: boolean }>) ?? []
229
+ const createOption = el['createOption'] as { formId: string; schema: ElementMeta[]; url?: string } | undefined
230
+ const fieldLabel = String(el['label'] ?? name)
231
+ return (
232
+ <SelectFieldInput
233
+ name={name}
234
+ defaultValue={defaultStr}
235
+ disabled={disabled}
236
+ required={required}
237
+ placeholder={placeholder}
238
+ options={options}
239
+ fieldLabel={fieldLabel}
240
+ {...(createOption ? { createOption } : {})}
241
+ />
242
+ )
243
+ }
244
+
245
+ case 'toggle': {
246
+ const initialChecked = defaultValue === true || defaultValue === 'true' || defaultValue === 1 || defaultValue === '1'
247
+ return <ToggleFieldInput name={name} defaultChecked={initialChecked} disabled={disabled} />
248
+ }
249
+
250
+ case 'checkbox': {
251
+ const initialChecked = defaultValue === true || defaultValue === 'true' || defaultValue === 1 || defaultValue === '1'
252
+ return <CheckboxInput name={name} defaultChecked={initialChecked} disabled={disabled} />
253
+ }
254
+
255
+ case 'radio': {
256
+ const options = (el['options'] as Array<{ value: string; label: string; disabled?: boolean }>) ?? []
257
+ const inline = Boolean(el['inline'])
258
+ return (
259
+ <RadioInput
260
+ name={name}
261
+ defaultValue={defaultStr}
262
+ disabled={disabled}
263
+ options={options}
264
+ inline={inline}
265
+ />
266
+ )
267
+ }
268
+
269
+ case 'toggleButtons': {
270
+ const options = (el['options'] as Array<{ value: string; label: string; disabled?: boolean }>) ?? []
271
+ return (
272
+ <ToggleButtonsInput
273
+ name={name}
274
+ defaultValue={defaultStr}
275
+ disabled={disabled}
276
+ options={options}
277
+ />
278
+ )
279
+ }
280
+
281
+ case 'checkboxList': {
282
+ const options = (el['options'] as Array<{ value: string; label: string; disabled?: boolean }>) ?? []
283
+ const columns = Number(el['columns']) || 1
284
+ return (
285
+ <CheckboxListInput
286
+ name={name}
287
+ defaultValue={defaultValue}
288
+ disabled={disabled}
289
+ options={options}
290
+ columns={columns}
291
+ />
292
+ )
293
+ }
294
+
295
+ case 'slider': {
296
+ return (
297
+ <SliderInput
298
+ name={name}
299
+ defaultValue={defaultValue}
300
+ disabled={disabled}
301
+ min={Number(el['min']) || 0}
302
+ max={Number(el['max']) || 100}
303
+ step={Number(el['step']) || 1}
304
+ showValue={Boolean(el['showValue'])}
305
+ />
306
+ )
307
+ }
308
+
309
+ case 'color': {
310
+ return (
311
+ <ColorInput
312
+ name={name}
313
+ defaultValue={defaultValue}
314
+ disabled={disabled}
315
+ />
316
+ )
317
+ }
318
+
319
+ case 'keyValue': {
320
+ return (
321
+ <KeyValueInput
322
+ name={name}
323
+ defaultValue={defaultValue}
324
+ disabled={disabled}
325
+ keyLabel={String(el['keyLabel'] ?? 'Key')}
326
+ valueLabel={String(el['valueLabel'] ?? 'Value')}
327
+ addLabel={String(el['addLabel'] ?? 'Add row')}
328
+ reorderable={Boolean(el['reorderable'])}
329
+ />
330
+ )
331
+ }
332
+
333
+ case 'tagsInput': {
334
+ const suggestions = (el['suggestions'] as string[] | undefined) ?? []
335
+ // separator: omitted → ',' (default); explicit null → null (disabled).
336
+ const separator = 'separator' in el
337
+ ? (el['separator'] as string | null)
338
+ : ','
339
+ const splitKeys = (el['splitKeys'] as string[] | undefined) ?? ['Enter']
340
+ const maxTags = typeof el['maxTags'] === 'number' ? el['maxTags'] as number : null
341
+ const reorderable = Boolean(el['reorderable'])
342
+ return (
343
+ <TagsInput
344
+ name={name}
345
+ defaultValue={defaultValue}
346
+ disabled={disabled}
347
+ placeholder={placeholder}
348
+ suggestions={suggestions}
349
+ separator={separator}
350
+ splitKeys={splitKeys}
351
+ maxTags={maxTags}
352
+ reorderable={reorderable}
353
+ />
354
+ )
355
+ }
356
+
357
+ case 'fileUpload': {
358
+ return (
359
+ <FileUploadInput
360
+ name={name}
361
+ defaultValue={defaultValue}
362
+ disabled={disabled}
363
+ accept={el['accept'] as string[] | undefined}
364
+ maxSize={typeof el['maxSize'] === 'number' ? el['maxSize'] : undefined}
365
+ multiple={Boolean(el['multiple'])}
366
+ preview={el['preview'] !== false}
367
+ directory={typeof el['directory'] === 'string' ? el['directory'] : undefined}
368
+ uploadUrl={typeof el['uploadUrl'] === 'string' ? el['uploadUrl'] : undefined}
369
+ downloadable={Boolean(el['downloadable'])}
370
+ openable={Boolean(el['openable'])}
371
+ reorderable={Boolean(el['reorderable'])}
372
+ appendFiles={Boolean(el['appendFiles'])}
373
+ panelLayout={
374
+ el['panelLayout'] === 'grid' ? 'grid'
375
+ : el['panelLayout'] === 'integrated' ? 'integrated'
376
+ : 'list'
377
+ }
378
+ {...(el['automaticallyResize'] && typeof el['automaticallyResize'] === 'object'
379
+ ? { automaticallyResize: el['automaticallyResize'] as { width: number; height: number } }
380
+ : {})}
381
+ imageEditor={Boolean(el['imageEditor'])}
382
+ circleCropper={Boolean(el['circleCropper'])}
383
+ automaticallyCropImagesToAspectRatio={Boolean(el['automaticallyCropImagesToAspectRatio'])}
384
+ {...(Array.isArray(el['imageEditorAspectRatioOptions'])
385
+ ? { imageEditorAspectRatioOptions: el['imageEditorAspectRatioOptions'] as Array<{ ratio: number; label: string }> }
386
+ : {})}
387
+ />
388
+ )
389
+ }
390
+
391
+ case 'markdown': {
392
+ const toolbarButtons = (el['toolbarButtons'] as Array<
393
+ 'bold' | 'italic' | 'strike' | 'link' | 'heading' | 'bulletList'
394
+ | 'orderedList' | 'blockquote' | 'codeBlock' | 'attachFiles'
395
+ > | undefined) ?? []
396
+ return (
397
+ <MarkdownInput
398
+ name={name}
399
+ defaultValue={defaultValue}
400
+ disabled={disabled}
401
+ placeholder={placeholder}
402
+ toolbarButtons={toolbarButtons}
403
+ minHeight={typeof el['minHeight'] === 'string' ? el['minHeight'] : undefined}
404
+ maxHeight={typeof el['maxHeight'] === 'string' ? el['maxHeight'] : undefined}
405
+ fileAttachmentsDirectory={typeof el['fileAttachmentsDirectory'] === 'string' ? el['fileAttachmentsDirectory'] : undefined}
406
+ fileAttachmentsVisibility={typeof el['fileAttachmentsVisibility'] === 'string' ? el['fileAttachmentsVisibility'] : undefined}
407
+ uploadUrl={typeof el['uploadUrl'] === 'string' ? el['uploadUrl'] : undefined}
408
+ />
409
+ )
410
+ }
411
+
412
+ case 'repeater':
413
+ return <RepeaterInput el={el} name={name} disabled={disabled} />
414
+
415
+ case 'builder':
416
+ return <BuilderInput el={el} name={name} disabled={disabled} />
417
+
418
+ case 'dateTime': {
419
+ // Normalize various input shapes to YYYY-MM-DDTHH:mm.
420
+ let local: string | undefined
421
+ if (defaultValue instanceof Date) {
422
+ local = isNaN(defaultValue.getTime())
423
+ ? undefined
424
+ : defaultValue.toISOString().slice(0, 16)
425
+ } else if (typeof defaultValue === 'string' && defaultValue) {
426
+ const parsed = new Date(defaultValue)
427
+ local = isNaN(parsed.getTime()) ? undefined : parsed.toISOString().slice(0, 16)
428
+ }
429
+ return (
430
+ <DateTimeInput
431
+ name={name}
432
+ defaultValue={local}
433
+ disabled={disabled}
434
+ placeholder={placeholder}
435
+ />
436
+ )
437
+ }
438
+
439
+ case 'number': {
440
+ const numProps: Record<string, unknown> = {}
441
+ if (el['min'] !== undefined) numProps['min'] = Number(el['min'])
442
+ if (el['max'] !== undefined) numProps['max'] = Number(el['max'])
443
+ if (el['step'] !== undefined) numProps['step'] = Number(el['step'])
444
+ return (
445
+ <TextLikeInput
446
+ el={el}
447
+ name={name}
448
+ common={common}
449
+ type="number"
450
+ extraProps={numProps}
451
+ multiline={false}
452
+ />
453
+ )
454
+ }
455
+
456
+ case 'email':
457
+ return (
458
+ <TextLikeInput
459
+ el={el}
460
+ name={name}
461
+ common={common}
462
+ type="email"
463
+ extraProps={{}}
464
+ multiline={false}
465
+ />
466
+ )
467
+
468
+ case 'date': {
469
+ // SSR may hand us a JS Date object directly; SPA JSON nav arrives as
470
+ // an ISO string. Normalize both into a `YYYY-MM-DD` slice — naive
471
+ // string slicing on `Date.toString()` ("Mon Apr 27 2026 ...") gives
472
+ // garbage when re-parsed, so handle the Date branch explicitly.
473
+ let iso: string | undefined
474
+ if (defaultValue instanceof Date) {
475
+ iso = isNaN(defaultValue.getTime())
476
+ ? undefined
477
+ : defaultValue.toISOString().slice(0, 10)
478
+ } else if (typeof defaultValue === 'string' && defaultValue) {
479
+ const parsed = new Date(defaultValue)
480
+ iso = isNaN(parsed.getTime())
481
+ ? undefined
482
+ : parsed.toISOString().slice(0, 10)
483
+ }
484
+ return (
485
+ <DateFieldInput
486
+ name={name}
487
+ defaultValue={iso}
488
+ disabled={disabled}
489
+ placeholder={placeholder}
490
+ />
491
+ )
492
+ }
493
+
494
+ // `text` / `slug` short-circuit through `TextFieldShell` above; the
495
+ // bare default covers unknown fieldType strings.
496
+ default: {
497
+ const textExtra: Record<string, unknown> = {}
498
+ if (el['maxLength'] !== undefined) textExtra['maxLength'] = Number(el['maxLength'])
499
+ return (
500
+ <TextLikeInput
501
+ el={el}
502
+ name={name}
503
+ common={common}
504
+ type="text"
505
+ extraProps={textExtra}
506
+ multiline={false}
507
+ />
508
+ )
509
+ }
510
+ }
511
+ }
@@ -0,0 +1,81 @@
1
+ import React from 'react'
2
+ import type { ComponentType } from 'react'
3
+ import type { ElementMeta } from '../../schema/Element.js'
4
+ import { getIcon } from '../../icons/registry.js'
5
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip.js'
6
+
7
+ /** Resolve an icon name through the user-extensible registry. Returns
8
+ * `undefined` when the name isn't registered — callers fall back to
9
+ * their own default. Pilotiq's own chrome never depends on the
10
+ * registry. */
11
+ export type IconCmp = ComponentType<{
12
+ className?: string
13
+ style?: React.CSSProperties
14
+ 'aria-hidden'?: boolean | 'true' | 'false'
15
+ 'aria-label'?: string
16
+ }>
17
+
18
+ export function resolveIcon(name: string | undefined): IconCmp | undefined {
19
+ if (!name) return undefined
20
+ return getIcon(name) as IconCmp | undefined
21
+ }
22
+
23
+ /** Map an element's optional `_layout` hints (Plan #8 — columnSpan /
24
+ * columnStart / columnOrder) to Tailwind utility classes. Returns an
25
+ * empty string when the element has no layout hints. Outside of a
26
+ * parent Grid/Split the classes have no effect — Tailwind generates
27
+ * `col-span-*` / `col-start-*` / `order-*` regardless of context.
28
+ *
29
+ * Tailwind's JIT only ships utilities up to a fixed range; clamp here
30
+ * to the safe defaults (1..12 for col, 1..12 for order). */
31
+ export function layoutClasses(el: ElementMeta): string {
32
+ const layout = el['_layout'] as
33
+ | { columnSpan?: number; columnStart?: number; columnOrder?: number }
34
+ | undefined
35
+ if (!layout) return ''
36
+ const out: string[] = []
37
+ if (typeof layout.columnSpan === 'number') {
38
+ const span = Math.max(1, Math.min(12, layout.columnSpan))
39
+ out.push(`col-span-${span}`)
40
+ }
41
+ if (typeof layout.columnStart === 'number') {
42
+ const start = Math.max(1, Math.min(12, layout.columnStart))
43
+ out.push(`col-start-${start}`)
44
+ }
45
+ if (typeof layout.columnOrder === 'number') {
46
+ const order = Math.max(1, Math.min(12, layout.columnOrder))
47
+ out.push(`order-${order}`)
48
+ }
49
+ return out.join(' ')
50
+ }
51
+
52
+ /** Wrap a child list in a flex column with consistent gap. Caller
53
+ * passes its own `renderElement` so this helper stays free of the
54
+ * SchemaRenderer ↔ leaf-renderers cycle. */
55
+ export function renderChildren(
56
+ children: ElementMeta[] | undefined,
57
+ gap: string,
58
+ renderElement: (el: ElementMeta, index: number) => React.ReactNode,
59
+ ): React.ReactNode {
60
+ if (!children || children.length === 0) return null
61
+ return (
62
+ <div className={`flex flex-col ${gap}`}>
63
+ {children.map((child, i) => renderElement(child, i))}
64
+ </div>
65
+ )
66
+ }
67
+
68
+ /** Wrap a node in the standard hover tooltip. Returns the node as-is when
69
+ * `tooltip` is falsy. Used wherever a button/affordance needs an
70
+ * on-hover label without owning its own Popover. */
71
+ export function withTooltip(node: React.ReactNode, tooltip: string | undefined): React.ReactNode {
72
+ if (!tooltip) return node
73
+ return (
74
+ <TooltipProvider>
75
+ <Tooltip>
76
+ <TooltipTrigger render={() => node as React.ReactElement} />
77
+ <TooltipContent>{tooltip}</TooltipContent>
78
+ </Tooltip>
79
+ </TooltipProvider>
80
+ )
81
+ }