@pilotiq/pilotiq 0.7.2 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (371) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/CHANGELOG.md +208 -0
  3. package/CLAUDE.md +59 -3
  4. package/dist/Pilotiq.d.ts +83 -0
  5. package/dist/Pilotiq.d.ts.map +1 -1
  6. package/dist/Pilotiq.js +39 -0
  7. package/dist/Pilotiq.js.map +1 -1
  8. package/dist/actions/Action.d.ts +27 -99
  9. package/dist/actions/Action.d.ts.map +1 -1
  10. package/dist/actions/Action.js +52 -754
  11. package/dist/actions/Action.js.map +1 -1
  12. package/dist/actions/bulkFactories.d.ts +46 -0
  13. package/dist/actions/bulkFactories.d.ts.map +1 -0
  14. package/dist/actions/bulkFactories.js +144 -0
  15. package/dist/actions/bulkFactories.js.map +1 -0
  16. package/dist/actions/crudFactories.d.ts +94 -0
  17. package/dist/actions/crudFactories.d.ts.map +1 -0
  18. package/dist/actions/crudFactories.js +209 -0
  19. package/dist/actions/crudFactories.js.map +1 -0
  20. package/dist/actions/factoryHelpers.d.ts +108 -0
  21. package/dist/actions/factoryHelpers.d.ts.map +1 -0
  22. package/dist/actions/factoryHelpers.js +138 -0
  23. package/dist/actions/factoryHelpers.js.map +1 -0
  24. package/dist/actions/m2mFactories.d.ts +47 -0
  25. package/dist/actions/m2mFactories.d.ts.map +1 -0
  26. package/dist/actions/m2mFactories.js +173 -0
  27. package/dist/actions/m2mFactories.js.map +1 -0
  28. package/dist/actions/relationFactories.d.ts +93 -0
  29. package/dist/actions/relationFactories.d.ts.map +1 -0
  30. package/dist/actions/relationFactories.js +321 -0
  31. package/dist/actions/relationFactories.js.map +1 -0
  32. package/dist/elements/dispatchForm.js +1 -1
  33. package/dist/elements/dispatchForm.js.map +1 -1
  34. package/dist/elements/dispatchTable.js +1 -1
  35. package/dist/elements/dispatchTable.js.map +1 -1
  36. package/dist/fields/Field.d.ts +31 -0
  37. package/dist/fields/Field.d.ts.map +1 -1
  38. package/dist/fields/Field.js +25 -0
  39. package/dist/fields/Field.js.map +1 -1
  40. package/dist/pageData/breadcrumbs.d.ts +42 -0
  41. package/dist/pageData/breadcrumbs.d.ts.map +1 -0
  42. package/dist/pageData/breadcrumbs.js +172 -0
  43. package/dist/pageData/breadcrumbs.js.map +1 -0
  44. package/dist/pageData/forms.d.ts +137 -0
  45. package/dist/pageData/forms.d.ts.map +1 -0
  46. package/dist/pageData/forms.js +427 -0
  47. package/dist/pageData/forms.js.map +1 -0
  48. package/dist/pageData/helpers.d.ts +239 -0
  49. package/dist/pageData/helpers.d.ts.map +1 -0
  50. package/dist/pageData/helpers.js +703 -0
  51. package/dist/pageData/helpers.js.map +1 -0
  52. package/dist/pageData/misc.d.ts +76 -0
  53. package/dist/pageData/misc.d.ts.map +1 -0
  54. package/dist/pageData/misc.js +263 -0
  55. package/dist/pageData/misc.js.map +1 -0
  56. package/dist/pageData/navigation.d.ts +292 -0
  57. package/dist/pageData/navigation.d.ts.map +1 -0
  58. package/dist/pageData/navigation.js +591 -0
  59. package/dist/pageData/navigation.js.map +1 -0
  60. package/dist/pageData/relationPages.d.ts +172 -0
  61. package/dist/pageData/relationPages.d.ts.map +1 -0
  62. package/dist/pageData/relationPages.js +867 -0
  63. package/dist/pageData/relationPages.js.map +1 -0
  64. package/dist/pageData/relationTabs.d.ts +65 -0
  65. package/dist/pageData/relationTabs.d.ts.map +1 -0
  66. package/dist/pageData/relationTabs.js +258 -0
  67. package/dist/pageData/relationTabs.js.map +1 -0
  68. package/dist/pageData/resourcePages.d.ts +48 -0
  69. package/dist/pageData/resourcePages.d.ts.map +1 -0
  70. package/dist/pageData/resourcePages.js +504 -0
  71. package/dist/pageData/resourcePages.js.map +1 -0
  72. package/dist/pageData.d.ts +12 -792
  73. package/dist/pageData.d.ts.map +1 -1
  74. package/dist/pageData.js +24 -3797
  75. package/dist/pageData.js.map +1 -1
  76. package/dist/react/AppShell.d.ts +8 -0
  77. package/dist/react/AppShell.d.ts.map +1 -1
  78. package/dist/react/AppShell.js +11 -1
  79. package/dist/react/AppShell.js.map +1 -1
  80. package/dist/react/CollabExtensionFactoryRegistry.d.ts +47 -0
  81. package/dist/react/CollabExtensionFactoryRegistry.d.ts.map +1 -0
  82. package/dist/react/CollabExtensionFactoryRegistry.js +14 -0
  83. package/dist/react/CollabExtensionFactoryRegistry.js.map +1 -0
  84. package/dist/react/CollabRoomContext.d.ts +37 -0
  85. package/dist/react/CollabRoomContext.d.ts.map +1 -0
  86. package/dist/react/CollabRoomContext.js +12 -0
  87. package/dist/react/CollabRoomContext.js.map +1 -0
  88. package/dist/react/FormCollabBindingRegistry.d.ts +62 -0
  89. package/dist/react/FormCollabBindingRegistry.d.ts.map +1 -0
  90. package/dist/react/FormCollabBindingRegistry.js +14 -0
  91. package/dist/react/FormCollabBindingRegistry.js.map +1 -0
  92. package/dist/react/FormStateContext.d.ts.map +1 -1
  93. package/dist/react/FormStateContext.js +87 -0
  94. package/dist/react/FormStateContext.js.map +1 -1
  95. package/dist/react/RecordWrapperGate.d.ts +25 -0
  96. package/dist/react/RecordWrapperGate.d.ts.map +1 -0
  97. package/dist/react/RecordWrapperGate.js +30 -0
  98. package/dist/react/RecordWrapperGate.js.map +1 -0
  99. package/dist/react/RecordWrapperRegistry.d.ts +31 -0
  100. package/dist/react/RecordWrapperRegistry.d.ts.map +1 -0
  101. package/dist/react/RecordWrapperRegistry.js +15 -0
  102. package/dist/react/RecordWrapperRegistry.js.map +1 -0
  103. package/dist/react/SchemaRenderer.d.ts +17 -23
  104. package/dist/react/SchemaRenderer.d.ts.map +1 -1
  105. package/dist/react/SchemaRenderer.js +71 -3647
  106. package/dist/react/SchemaRenderer.js.map +1 -1
  107. package/dist/react/component-slots.d.ts +103 -0
  108. package/dist/react/component-slots.d.ts.map +1 -0
  109. package/dist/react/component-slots.js +18 -0
  110. package/dist/react/component-slots.js.map +1 -0
  111. package/dist/react/fields/BuilderInput.d.ts.map +1 -1
  112. package/dist/react/fields/BuilderInput.js +21 -117
  113. package/dist/react/fields/BuilderInput.js.map +1 -1
  114. package/dist/react/fields/MarkdownInput.d.ts.map +1 -1
  115. package/dist/react/fields/MarkdownInput.js +1 -3
  116. package/dist/react/fields/MarkdownInput.js.map +1 -1
  117. package/dist/react/fields/RepeaterInput.d.ts.map +1 -1
  118. package/dist/react/fields/RepeaterInput.js +22 -127
  119. package/dist/react/fields/RepeaterInput.js.map +1 -1
  120. package/dist/react/fields/rowState.d.ts +40 -0
  121. package/dist/react/fields/rowState.d.ts.map +1 -0
  122. package/dist/react/fields/rowState.js +60 -0
  123. package/dist/react/fields/rowState.js.map +1 -0
  124. package/dist/react/fields/useRowReorderDnd.d.ts +28 -0
  125. package/dist/react/fields/useRowReorderDnd.d.ts.map +1 -0
  126. package/dist/react/fields/useRowReorderDnd.js +51 -0
  127. package/dist/react/fields/useRowReorderDnd.js.map +1 -0
  128. package/dist/react/index.d.ts +9 -0
  129. package/dist/react/index.d.ts.map +1 -1
  130. package/dist/react/index.js +8 -0
  131. package/dist/react/index.js.map +1 -1
  132. package/dist/react/layouts/SidebarLayout.d.ts +1 -1
  133. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  134. package/dist/react/layouts/SidebarLayout.js +10 -2
  135. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  136. package/dist/react/layouts/TopbarLayout.d.ts +1 -1
  137. package/dist/react/layouts/TopbarLayout.d.ts.map +1 -1
  138. package/dist/react/layouts/TopbarLayout.js +19 -11
  139. package/dist/react/layouts/TopbarLayout.js.map +1 -1
  140. package/dist/react/parseRecordEditUrl.d.ts +29 -0
  141. package/dist/react/parseRecordEditUrl.d.ts.map +1 -0
  142. package/dist/react/parseRecordEditUrl.js +25 -0
  143. package/dist/react/parseRecordEditUrl.js.map +1 -0
  144. package/dist/react/persistedState.d.ts +19 -0
  145. package/dist/react/persistedState.d.ts.map +1 -0
  146. package/dist/react/persistedState.js +51 -0
  147. package/dist/react/persistedState.js.map +1 -0
  148. package/dist/react/schemaRenderer/AlertRenderer.d.ts +12 -0
  149. package/dist/react/schemaRenderer/AlertRenderer.d.ts.map +1 -0
  150. package/dist/react/schemaRenderer/AlertRenderer.js +61 -0
  151. package/dist/react/schemaRenderer/AlertRenderer.js.map +1 -0
  152. package/dist/react/schemaRenderer/EntryRenderer.d.ts +13 -0
  153. package/dist/react/schemaRenderer/EntryRenderer.d.ts.map +1 -0
  154. package/dist/react/schemaRenderer/EntryRenderer.js +277 -0
  155. package/dist/react/schemaRenderer/EntryRenderer.js.map +1 -0
  156. package/dist/react/schemaRenderer/SectionRenderer.d.ts +16 -0
  157. package/dist/react/schemaRenderer/SectionRenderer.d.ts.map +1 -0
  158. package/dist/react/schemaRenderer/SectionRenderer.js +62 -0
  159. package/dist/react/schemaRenderer/SectionRenderer.js.map +1 -0
  160. package/dist/react/schemaRenderer/SimpleElements.d.ts +25 -0
  161. package/dist/react/schemaRenderer/SimpleElements.d.ts.map +1 -0
  162. package/dist/react/schemaRenderer/SimpleElements.js +147 -0
  163. package/dist/react/schemaRenderer/SimpleElements.js.map +1 -0
  164. package/dist/react/schemaRenderer/TabsRenderer.d.ts +17 -0
  165. package/dist/react/schemaRenderer/TabsRenderer.d.ts.map +1 -0
  166. package/dist/react/schemaRenderer/TabsRenderer.js +31 -0
  167. package/dist/react/schemaRenderer/TabsRenderer.js.map +1 -0
  168. package/dist/react/schemaRenderer/WizardRenderer.d.ts +34 -0
  169. package/dist/react/schemaRenderer/WizardRenderer.d.ts.map +1 -0
  170. package/dist/react/schemaRenderer/WizardRenderer.js +208 -0
  171. package/dist/react/schemaRenderer/WizardRenderer.js.map +1 -0
  172. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts +21 -0
  173. package/dist/react/schemaRenderer/action/ActionGroupTrigger.d.ts.map +1 -0
  174. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js +82 -0
  175. package/dist/react/schemaRenderer/action/ActionGroupTrigger.js.map +1 -0
  176. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts +30 -0
  177. package/dist/react/schemaRenderer/action/ActionModalDialog.d.ts.map +1 -0
  178. package/dist/react/schemaRenderer/action/ActionModalDialog.js +182 -0
  179. package/dist/react/schemaRenderer/action/ActionModalDialog.js.map +1 -0
  180. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts +17 -0
  181. package/dist/react/schemaRenderer/action/ConfirmActionDialog.d.ts.map +1 -0
  182. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js +19 -0
  183. package/dist/react/schemaRenderer/action/ConfirmActionDialog.js.map +1 -0
  184. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts +16 -0
  185. package/dist/react/schemaRenderer/action/HandlerActionButton.d.ts.map +1 -0
  186. package/dist/react/schemaRenderer/action/HandlerActionButton.js +16 -0
  187. package/dist/react/schemaRenderer/action/HandlerActionButton.js.map +1 -0
  188. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts +22 -0
  189. package/dist/react/schemaRenderer/action/MethodActionButton.d.ts.map +1 -0
  190. package/dist/react/schemaRenderer/action/MethodActionButton.js +26 -0
  191. package/dist/react/schemaRenderer/action/MethodActionButton.js.map +1 -0
  192. package/dist/react/schemaRenderer/action/buttons.d.ts +18 -0
  193. package/dist/react/schemaRenderer/action/buttons.d.ts.map +1 -0
  194. package/dist/react/schemaRenderer/action/buttons.js +74 -0
  195. package/dist/react/schemaRenderer/action/buttons.js.map +1 -0
  196. package/dist/react/schemaRenderer/action/helpers.d.ts +26 -0
  197. package/dist/react/schemaRenderer/action/helpers.d.ts.map +1 -0
  198. package/dist/react/schemaRenderer/action/helpers.js +126 -0
  199. package/dist/react/schemaRenderer/action/helpers.js.map +1 -0
  200. package/dist/react/schemaRenderer/action/renderAction.d.ts +21 -0
  201. package/dist/react/schemaRenderer/action/renderAction.d.ts.map +1 -0
  202. package/dist/react/schemaRenderer/action/renderAction.js +102 -0
  203. package/dist/react/schemaRenderer/action/renderAction.js.map +1 -0
  204. package/dist/react/schemaRenderer/columnFormat.d.ts +10 -0
  205. package/dist/react/schemaRenderer/columnFormat.d.ts.map +1 -0
  206. package/dist/react/schemaRenderer/columnFormat.js +76 -0
  207. package/dist/react/schemaRenderer/columnFormat.js.map +1 -0
  208. package/dist/react/schemaRenderer/constants.d.ts +8 -0
  209. package/dist/react/schemaRenderer/constants.d.ts.map +1 -0
  210. package/dist/react/schemaRenderer/constants.js +45 -0
  211. package/dist/react/schemaRenderer/constants.js.map +1 -0
  212. package/dist/react/schemaRenderer/form/FormRenderer.d.ts +29 -0
  213. package/dist/react/schemaRenderer/form/FormRenderer.d.ts.map +1 -0
  214. package/dist/react/schemaRenderer/form/FormRenderer.js +163 -0
  215. package/dist/react/schemaRenderer/form/FormRenderer.js.map +1 -0
  216. package/dist/react/schemaRenderer/form/renderField.d.ts +6 -0
  217. package/dist/react/schemaRenderer/form/renderField.d.ts.map +1 -0
  218. package/dist/react/schemaRenderer/form/renderField.js +239 -0
  219. package/dist/react/schemaRenderer/form/renderField.js.map +1 -0
  220. package/dist/react/schemaRenderer/helpers.d.ts +32 -0
  221. package/dist/react/schemaRenderer/helpers.d.ts.map +1 -0
  222. package/dist/react/schemaRenderer/helpers.js +52 -0
  223. package/dist/react/schemaRenderer/helpers.js.map +1 -0
  224. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts +60 -0
  225. package/dist/react/schemaRenderer/table/CardsLayoutBody.d.ts.map +1 -0
  226. package/dist/react/schemaRenderer/table/CardsLayoutBody.js +189 -0
  227. package/dist/react/schemaRenderer/table/CardsLayoutBody.js.map +1 -0
  228. package/dist/react/schemaRenderer/table/TableRenderer.d.ts +29 -0
  229. package/dist/react/schemaRenderer/table/TableRenderer.d.ts.map +1 -0
  230. package/dist/react/schemaRenderer/table/TableRenderer.js +85 -0
  231. package/dist/react/schemaRenderer/table/TableRenderer.js.map +1 -0
  232. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts +18 -0
  233. package/dist/react/schemaRenderer/table/TableRendererBody.d.ts.map +1 -0
  234. package/dist/react/schemaRenderer/table/TableRendererBody.js +555 -0
  235. package/dist/react/schemaRenderer/table/TableRendererBody.js.map +1 -0
  236. package/dist/react/schemaRenderer/table/filters.d.ts +263 -0
  237. package/dist/react/schemaRenderer/table/filters.d.ts.map +1 -0
  238. package/dist/react/schemaRenderer/table/filters.js +497 -0
  239. package/dist/react/schemaRenderer/table/filters.js.map +1 -0
  240. package/dist/react/schemaRenderer/table/formatCell.d.ts +11 -0
  241. package/dist/react/schemaRenderer/table/formatCell.d.ts.map +1 -0
  242. package/dist/react/schemaRenderer/table/formatCell.js +172 -0
  243. package/dist/react/schemaRenderer/table/formatCell.js.map +1 -0
  244. package/dist/react/schemaRenderer/table/links.d.ts +42 -0
  245. package/dist/react/schemaRenderer/table/links.d.ts.map +1 -0
  246. package/dist/react/schemaRenderer/table/links.js +55 -0
  247. package/dist/react/schemaRenderer/table/links.js.map +1 -0
  248. package/dist/react/schemaRenderer/table/renderRowActions.d.ts +13 -0
  249. package/dist/react/schemaRenderer/table/renderRowActions.d.ts.map +1 -0
  250. package/dist/react/schemaRenderer/table/renderRowActions.js +25 -0
  251. package/dist/react/schemaRenderer/table/renderRowActions.js.map +1 -0
  252. package/dist/react/schemaRenderer/table/url.d.ts +41 -0
  253. package/dist/react/schemaRenderer/table/url.d.ts.map +1 -0
  254. package/dist/react/schemaRenderer/table/url.js +114 -0
  255. package/dist/react/schemaRenderer/table/url.js.map +1 -0
  256. package/dist/routes/globals.d.ts +13 -0
  257. package/dist/routes/globals.d.ts.map +1 -0
  258. package/dist/routes/globals.js +131 -0
  259. package/dist/routes/globals.js.map +1 -0
  260. package/dist/routes/helpers.d.ts +217 -0
  261. package/dist/routes/helpers.d.ts.map +1 -0
  262. package/dist/routes/helpers.js +498 -0
  263. package/dist/routes/helpers.js.map +1 -0
  264. package/dist/routes/pages.d.ts +15 -0
  265. package/dist/routes/pages.d.ts.map +1 -0
  266. package/dist/routes/pages.js +145 -0
  267. package/dist/routes/pages.js.map +1 -0
  268. package/dist/routes/panel.d.ts +19 -0
  269. package/dist/routes/panel.d.ts.map +1 -0
  270. package/dist/routes/panel.js +191 -0
  271. package/dist/routes/panel.js.map +1 -0
  272. package/dist/routes/relations.d.ts +21 -0
  273. package/dist/routes/relations.d.ts.map +1 -0
  274. package/dist/routes/relations.js +1239 -0
  275. package/dist/routes/relations.js.map +1 -0
  276. package/dist/routes/resources.d.ts +28 -0
  277. package/dist/routes/resources.d.ts.map +1 -0
  278. package/dist/routes/resources.js +741 -0
  279. package/dist/routes/resources.js.map +1 -0
  280. package/dist/routes/theme.d.ts +12 -0
  281. package/dist/routes/theme.d.ts.map +1 -0
  282. package/dist/routes/theme.js +82 -0
  283. package/dist/routes/theme.js.map +1 -0
  284. package/dist/routes.d.ts.map +1 -1
  285. package/dist/routes.js +64 -3078
  286. package/dist/routes.js.map +1 -1
  287. package/dist/vite.d.ts +1 -0
  288. package/dist/vite.d.ts.map +1 -1
  289. package/dist/vite.js +26 -5
  290. package/dist/vite.js.map +1 -1
  291. package/package.json +2 -1
  292. package/src/Pilotiq.ts +95 -0
  293. package/src/actions/Action.ts +79 -723
  294. package/src/actions/bulkFactories.ts +168 -0
  295. package/src/actions/crudFactories.ts +220 -0
  296. package/src/actions/factoryHelpers.ts +177 -0
  297. package/src/actions/m2mFactories.ts +193 -0
  298. package/src/actions/relationFactories.ts +372 -0
  299. package/src/elements/dispatchForm.ts +1 -1
  300. package/src/elements/dispatchTable.ts +1 -1
  301. package/src/fields/Field.ts +39 -0
  302. package/src/pageData/breadcrumbs.ts +288 -0
  303. package/src/pageData/forms.ts +578 -0
  304. package/src/pageData/helpers.ts +764 -0
  305. package/src/pageData/misc.ts +347 -0
  306. package/src/pageData/navigation.ts +779 -0
  307. package/src/pageData/relationPages.ts +1246 -0
  308. package/src/pageData/relationTabs.ts +286 -0
  309. package/src/pageData/resourcePages.ts +593 -0
  310. package/src/pageData.ts +122 -4731
  311. package/src/react/AppShell.tsx +27 -1
  312. package/src/react/CollabExtensionFactoryRegistry.ts +55 -0
  313. package/src/react/CollabRoomContext.ts +42 -0
  314. package/src/react/FormCollabBindingRegistry.ts +72 -0
  315. package/src/react/FormStateContext.tsx +91 -0
  316. package/src/react/RecordWrapperGate.tsx +40 -0
  317. package/src/react/RecordWrapperRegistry.ts +39 -0
  318. package/src/react/SchemaRenderer.tsx +230 -6479
  319. package/src/react/component-slots.test.ts +103 -0
  320. package/src/react/component-slots.ts +116 -0
  321. package/src/react/fields/BuilderInput.tsx +29 -117
  322. package/src/react/fields/MarkdownInput.tsx +0 -1
  323. package/src/react/fields/RepeaterInput.tsx +29 -130
  324. package/src/react/fields/rowState.ts +106 -0
  325. package/src/react/fields/useRowReorderDnd.ts +78 -0
  326. package/src/react/index.ts +38 -0
  327. package/src/react/layouts/SidebarLayout.tsx +39 -28
  328. package/src/react/layouts/TopbarLayout.tsx +70 -57
  329. package/src/react/parseRecordEditUrl.test.ts +75 -0
  330. package/src/react/parseRecordEditUrl.ts +55 -0
  331. package/src/react/persistedState.ts +40 -0
  332. package/src/react/schemaRenderer/AlertRenderer.tsx +112 -0
  333. package/src/react/schemaRenderer/EntryRenderer.tsx +501 -0
  334. package/src/react/schemaRenderer/SectionRenderer.tsx +120 -0
  335. package/src/react/schemaRenderer/SimpleElements.tsx +306 -0
  336. package/src/react/schemaRenderer/TabsRenderer.tsx +62 -0
  337. package/src/react/schemaRenderer/WizardRenderer.tsx +338 -0
  338. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +177 -0
  339. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +273 -0
  340. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +61 -0
  341. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +43 -0
  342. package/src/react/schemaRenderer/action/MethodActionButton.tsx +64 -0
  343. package/src/react/schemaRenderer/action/buttons.tsx +99 -0
  344. package/src/react/schemaRenderer/action/helpers.ts +140 -0
  345. package/src/react/schemaRenderer/action/renderAction.tsx +245 -0
  346. package/src/react/schemaRenderer/columnFormat.ts +65 -0
  347. package/src/react/schemaRenderer/constants.ts +50 -0
  348. package/src/react/schemaRenderer/form/FormRenderer.tsx +245 -0
  349. package/src/react/schemaRenderer/form/renderField.tsx +511 -0
  350. package/src/react/schemaRenderer/helpers.tsx +81 -0
  351. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +308 -0
  352. package/src/react/schemaRenderer/table/TableRenderer.tsx +123 -0
  353. package/src/react/schemaRenderer/table/TableRendererBody.tsx +974 -0
  354. package/src/react/schemaRenderer/table/filters.tsx +1233 -0
  355. package/src/react/schemaRenderer/table/formatCell.tsx +264 -0
  356. package/src/react/schemaRenderer/table/links.tsx +112 -0
  357. package/src/react/schemaRenderer/table/renderRowActions.tsx +52 -0
  358. package/src/react/schemaRenderer/table/url.tsx +143 -0
  359. package/src/routes/globals.ts +154 -0
  360. package/src/routes/helpers.ts +668 -0
  361. package/src/routes/pages.ts +173 -0
  362. package/src/routes/panel.ts +204 -0
  363. package/src/routes/relations.ts +1219 -0
  364. package/src/routes/resources.ts +786 -0
  365. package/src/routes/theme.ts +109 -0
  366. package/src/routes.test.ts +1 -1
  367. package/src/routes.ts +64 -3176
  368. package/src/schema/TableWidget.test.ts +2 -2
  369. package/src/theme/migrate.test.ts +178 -0
  370. package/src/vite.test.ts +184 -0
  371. package/src/vite.ts +26 -4
@@ -0,0 +1,112 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import type { ComponentType } from 'react'
3
+ import {
4
+ CircleAlertIcon,
5
+ CircleCheckIcon,
6
+ InfoIcon,
7
+ TriangleAlertIcon,
8
+ XIcon,
9
+ } from 'lucide-react'
10
+ import { readStoredString, writeStoredString } from '../persistedState.js'
11
+ import { alertStyles, TEXT_COLOR_CLASSES } from './constants.js'
12
+
13
+ // ─── Alert renderer ─────────────────────────────────────────
14
+ //
15
+ // Owns dismissal state (per-mount + optional localStorage persistence)
16
+ // + icon dispatch + footer-actions alignment. Lifted out of the inline
17
+ // `case 'alert'` branch when Alert gained `dismissible() / iconColor() /
18
+ // footerActionsAlignment()` setters — those need component-local state
19
+ // (the dismiss button, the persisted-dismissal hydration on mount), and
20
+ // inlining the hooks under a switch arm is fragile.
21
+
22
+ const ALERT_TYPE_ICONS: Record<string, ComponentType<{ className?: string; 'aria-hidden'?: boolean | 'true' | 'false' }>> = {
23
+ info: InfoIcon,
24
+ warning: TriangleAlertIcon,
25
+ success: CircleCheckIcon,
26
+ danger: CircleAlertIcon,
27
+ }
28
+
29
+ const ALERT_TYPE_DEFAULT_ICON_COLOR: Record<string, string> = {
30
+ info: 'info',
31
+ warning: 'warning',
32
+ success: 'success',
33
+ danger: 'destructive',
34
+ }
35
+
36
+ const ALERT_ACTIONS_ALIGNMENT: Record<string, string> = {
37
+ start: 'justify-start',
38
+ center: 'justify-center',
39
+ end: 'justify-end',
40
+ }
41
+
42
+ function alertPersistKey(persistKey: string): string {
43
+ return `pilotiq.alert.${persistKey}`
44
+ }
45
+
46
+ export function AlertRenderer(props: {
47
+ alertType: string
48
+ content: string
49
+ title?: string
50
+ dismissible?: boolean
51
+ persistDismissal?: string
52
+ iconColor?: string
53
+ actionsAlignment?: string
54
+ footer: React.ReactNode[]
55
+ }): React.ReactNode {
56
+ const {
57
+ alertType, content, title, dismissible,
58
+ persistDismissal, iconColor, actionsAlignment, footer,
59
+ } = props
60
+ const [dismissed, setDismissed] = useState(false)
61
+
62
+ // Hydrate persisted-dismissal on first paint. `useState(false)` keeps
63
+ // SSR + first client paint identical (Hydration safe); the effect
64
+ // flips to dismissed if localStorage has the flag set.
65
+ useEffect(() => {
66
+ if (!persistDismissal) return
67
+ if (readStoredString(alertPersistKey(persistDismissal)) === '1') {
68
+ setDismissed(true)
69
+ }
70
+ }, [persistDismissal])
71
+
72
+ if (dismissed) return null
73
+
74
+ const styles = alertStyles[alertType] ?? alertStyles['info']!
75
+ const Icon = ALERT_TYPE_ICONS[alertType] ?? InfoIcon
76
+ const iconColorKey = iconColor ?? ALERT_TYPE_DEFAULT_ICON_COLOR[alertType] ?? 'info'
77
+ const iconColorCls = TEXT_COLOR_CLASSES[iconColorKey] ?? ''
78
+ const alignCls = ALERT_ACTIONS_ALIGNMENT[actionsAlignment ?? 'start'] ?? 'justify-start'
79
+
80
+ const handleDismiss = (): void => {
81
+ setDismissed(true)
82
+ if (persistDismissal) writeStoredString(alertPersistKey(persistDismissal), '1')
83
+ }
84
+
85
+ return (
86
+ <div className={`relative rounded-lg border p-4 ${styles} ${dismissible ? 'pr-9' : ''}`}>
87
+ <div className="flex gap-3">
88
+ <Icon className={`size-5 shrink-0 mt-0.5 ${iconColorCls}`} aria-hidden="true" />
89
+ <div className="flex-1 min-w-0">
90
+ {title !== undefined && <p className="font-medium mb-1">{title}</p>}
91
+ <p className="text-sm">{content}</p>
92
+ {footer.length > 0 && (
93
+ <div className={`flex items-center gap-2 mt-3 ${alignCls}`}>
94
+ {footer}
95
+ </div>
96
+ )}
97
+ </div>
98
+ </div>
99
+ {dismissible && (
100
+ <button
101
+ type="button"
102
+ onClick={handleDismiss}
103
+ aria-label="Dismiss"
104
+ title="Dismiss"
105
+ className="absolute top-3 right-3 inline-flex h-6 w-6 items-center justify-center rounded opacity-70 hover:opacity-100 transition-opacity"
106
+ >
107
+ <XIcon className="size-4" aria-hidden="true" />
108
+ </button>
109
+ )}
110
+ </div>
111
+ )
112
+ }
@@ -0,0 +1,501 @@
1
+ import React, { useState } from 'react'
2
+ import type { ElementMeta } from '../../schema/Element.js'
3
+ import { CheckIcon, CircleIcon, CopyIcon } from 'lucide-react'
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip.js'
5
+ import { getEntryComponent } from '../../entries/registry.js'
6
+ import {
7
+ BADGE_COLOR_CLASSES,
8
+ COLUMN_COLOR_CLASSES,
9
+ TEXT_COLOR_CLASSES,
10
+ TEXT_SIZE_CLASSES,
11
+ TEXT_WEIGHT_CLASSES,
12
+ } from './constants.js'
13
+ import { resolveIcon } from './helpers.js'
14
+ import { applyColumnFormat } from './columnFormat.js'
15
+
16
+ // ─── Entry rendering (Plan #16 — read-only label/value pairs) ───
17
+
18
+ /** Coerce a `KeyValueEntry` state value (object | JSON string | …) into a
19
+ * flat record. Returns `null` when the value is empty or non-decodable. */
20
+ function normalizeKeyValueValue(value: unknown): Record<string, unknown> | null {
21
+ if (value === null || value === undefined || value === '') return null
22
+ if (typeof value === 'string') {
23
+ try {
24
+ const parsed = JSON.parse(value) as unknown
25
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
26
+ return parsed as Record<string, unknown>
27
+ }
28
+ } catch {
29
+ // Non-JSON string — fall through to null so the renderer shows the
30
+ // fallback rather than misrepresenting it as a one-row map.
31
+ }
32
+ return null
33
+ }
34
+ if (Array.isArray(value)) return null
35
+ if (typeof value === 'object') return value as Record<string, unknown>
36
+ return null
37
+ }
38
+
39
+ /** Render a single kv cell value — primitives become their string form;
40
+ * nested objects/arrays JSON-stringify for compactness. */
41
+ function formatKeyValueCell(value: unknown): string {
42
+ if (value === null || value === undefined) return ''
43
+ if (typeof value === 'object') return JSON.stringify(value)
44
+ return String(value)
45
+ }
46
+
47
+ /**
48
+ * Plan #16 — read-only label-value pair for `Resource.detail()` schemas.
49
+ * Dispatches on `meta.entryType` (`'text' | 'badge' | 'icon' | 'image' | 'keyValue' | 'color'`).
50
+ * Wraps the rendered value in `<EntryShell>` for the shared chrome
51
+ * (label / helperText / tooltip / copyable trigger).
52
+ *
53
+ * `renderElement` is injected so the `repeatable` branch can recurse into
54
+ * row children without re-importing the main switch.
55
+ */
56
+ export function renderEntry(
57
+ el: ElementMeta,
58
+ index: number,
59
+ renderElement: (el: ElementMeta, index: number) => React.ReactNode,
60
+ ): React.ReactNode {
61
+ const entryType = String(el['entryType'] ?? 'text')
62
+ const value = el['value']
63
+ const fallback = el['default'] ? String(el['default']) : '—'
64
+
65
+ let body: React.ReactNode
66
+ switch (entryType) {
67
+ case 'text': {
68
+ const formatted = el['_formatted'] !== undefined
69
+ ? String(el['_formatted'])
70
+ : (el['format']
71
+ ? applyColumnFormat(value, el['format'] as { kind: string; [k: string]: unknown })
72
+ : (value === null || value === undefined || value === '' ? '' : String(value)))
73
+
74
+ const display = formatted === '' ? fallback : formatted
75
+ const isFallback = formatted === ''
76
+ const isRichText = el['richtext'] === true && !isFallback
77
+ const sizeKey = el['size'] ? String(el['size']) : 'sm'
78
+ const colorKey = el['color'] ? String(el['color']) : (isFallback ? 'muted' : 'default')
79
+ const weightKey = el['weight'] ? String(el['weight']) : 'normal'
80
+ const sizeCls = TEXT_SIZE_CLASSES[sizeKey] ?? 'text-sm'
81
+ const colorCls = TEXT_COLOR_CLASSES[colorKey] ?? ''
82
+ const weightCls = TEXT_WEIGHT_CLASSES[weightKey] ?? ''
83
+ const lineClamp = el['lineClamp'] as number | undefined
84
+ const wrap = el['wrap'] === true
85
+
86
+ const style: React.CSSProperties = {}
87
+ if (lineClamp !== undefined) {
88
+ style.display = '-webkit-box'
89
+ style.WebkitLineClamp = lineClamp
90
+ ;(style as { WebkitBoxOrient?: string }).WebkitBoxOrient = 'vertical'
91
+ style.overflow = 'hidden'
92
+ }
93
+ const wrapCls = wrap ? 'whitespace-pre-wrap' : (lineClamp !== undefined ? '' : 'whitespace-nowrap')
94
+
95
+ if (isRichText) {
96
+ // Server-rendered HTML from a registered richtext renderer (e.g.
97
+ // `@pilotiq/tiptap`). Wrap in `prose` for sensible default
98
+ // styling — matches the read-only `Markdown` / `Html` primes.
99
+ const proseSize = sizeKey === 'lg' || sizeKey === 'xl'
100
+ ? 'prose-lg'
101
+ : sizeKey === 'sm' || sizeKey === 'xs'
102
+ ? 'prose-sm'
103
+ : ''
104
+ body = (
105
+ <div
106
+ className={`prose max-w-none dark:prose-invert ${proseSize} ${colorCls} ${weightCls}`.trim()}
107
+ style={style}
108
+ dangerouslySetInnerHTML={{ __html: display }}
109
+ />
110
+ )
111
+ break
112
+ }
113
+
114
+ body = (
115
+ <span className={`${sizeCls} ${colorCls} ${weightCls} ${wrapCls}`.trim()} style={style}>
116
+ {display}
117
+ </span>
118
+ )
119
+ break
120
+ }
121
+
122
+ case 'badge': {
123
+ const isBlank = value === null || value === undefined || value === ''
124
+ if (isBlank) {
125
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
126
+ break
127
+ }
128
+ const map = (el['colors'] as Record<string, string> | undefined) ?? {}
129
+ const colorKey = map[String(value)] ?? 'gray'
130
+ const cls = BADGE_COLOR_CLASSES[colorKey] ?? BADGE_COLOR_CLASSES['gray']
131
+ body = (
132
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${cls}`}>
133
+ {String(value)}
134
+ </span>
135
+ )
136
+ break
137
+ }
138
+
139
+ case 'icon': {
140
+ const isBlank = value === null || value === undefined || value === ''
141
+ const map = (el['options'] as Record<string, { icon: string; color?: string; label?: string }> | undefined) ?? {}
142
+ const opt = isBlank ? undefined : map[String(value)]
143
+ if (!opt) {
144
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
145
+ break
146
+ }
147
+ const Icon = resolveIcon(opt.icon) ?? CircleIcon
148
+ const colorClass = opt.color ? (COLUMN_COLOR_CLASSES[opt.color] ?? '') : ''
149
+ const ariaLabel = opt.label ?? String(value)
150
+ body = <Icon className={`inline size-5 ${colorClass}`.trim()} aria-label={ariaLabel} />
151
+ break
152
+ }
153
+
154
+ case 'image': {
155
+ const isBlank = value === null || value === undefined || value === ''
156
+ if (isBlank) {
157
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
158
+ break
159
+ }
160
+ const url = String(value)
161
+ const width = (el['imageWidth'] as number | undefined) ?? (el['imageSize'] as number | undefined) ?? 64
162
+ const height = (el['imageHeight'] as number | undefined) ?? (el['imageSize'] as number | undefined) ?? 64
163
+ const shape = String(el['imageShape'] ?? 'rounded')
164
+ const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'square' ? '' : 'rounded-md'
165
+ body = (
166
+ <img
167
+ src={url}
168
+ alt=""
169
+ width={width}
170
+ height={height}
171
+ className={`inline-block object-cover ${shapeCls}`.trim()}
172
+ />
173
+ )
174
+ break
175
+ }
176
+
177
+ case 'keyValue': {
178
+ const parsed = normalizeKeyValueValue(value)
179
+ const keys = parsed ? Object.keys(parsed) : []
180
+ if (!parsed || keys.length === 0) {
181
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
182
+ break
183
+ }
184
+ const keyLabel = el['keyLabel'] ? String(el['keyLabel']) : 'Key'
185
+ const valueLabel = el['valueLabel'] ? String(el['valueLabel']) : 'Value'
186
+ body = (
187
+ <table className="w-full border border-border text-sm">
188
+ <thead>
189
+ <tr className="bg-muted/50 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground">
190
+ <th className="border-b border-border px-2 py-1">{keyLabel}</th>
191
+ <th className="border-b border-border px-2 py-1">{valueLabel}</th>
192
+ </tr>
193
+ </thead>
194
+ <tbody>
195
+ {keys.map(k => (
196
+ <tr key={k} className="border-t border-border first:border-t-0">
197
+ <td className="px-2 py-1 align-top font-mono text-xs">{k}</td>
198
+ <td className="px-2 py-1 align-top font-mono text-xs break-all">
199
+ {formatKeyValueCell(parsed[k])}
200
+ </td>
201
+ </tr>
202
+ ))}
203
+ </tbody>
204
+ </table>
205
+ )
206
+ break
207
+ }
208
+
209
+ case 'color': {
210
+ const isBlank = value === null || value === undefined || value === ''
211
+ if (isBlank) {
212
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
213
+ break
214
+ }
215
+ const hex = String(value)
216
+ const width = (el['colorWidth'] as number | undefined) ?? (el['colorSize'] as number | undefined) ?? 24
217
+ const height = (el['colorHeight'] as number | undefined) ?? (el['colorSize'] as number | undefined) ?? 24
218
+ const shape = String(el['colorShape'] ?? 'rounded')
219
+ const shapeCls = shape === 'circle' ? 'rounded-full' : shape === 'square' ? '' : 'rounded-md'
220
+ const showValue = el['showValue'] !== false
221
+ body = (
222
+ <span className="inline-flex items-center gap-2">
223
+ <span
224
+ className={`inline-block border border-border ${shapeCls}`.trim()}
225
+ style={{ width, height, backgroundColor: hex }}
226
+ aria-label={hex}
227
+ />
228
+ {showValue && (
229
+ <span className="font-mono text-xs text-muted-foreground">{hex}</span>
230
+ )}
231
+ </span>
232
+ )
233
+ break
234
+ }
235
+
236
+ case 'code': {
237
+ const isBlank = value === null || value === undefined || value === ''
238
+ if (isBlank) {
239
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
240
+ break
241
+ }
242
+ const text = typeof value === 'string' ? value : String(value)
243
+ const lang = el['language'] ? String(el['language']) : undefined
244
+ body = (
245
+ <pre
246
+ className="rounded-md border border-border bg-muted/40 p-3 text-xs overflow-x-auto"
247
+ data-language={lang}
248
+ >
249
+ <code className="font-mono">{text}</code>
250
+ </pre>
251
+ )
252
+ break
253
+ }
254
+
255
+ case 'component': {
256
+ const componentName = String(el['component'] ?? '')
257
+ if (!componentName) {
258
+ body = (
259
+ <EntryComponentError>
260
+ ComponentEntry is missing its <code className="font-mono">component</code> name —
261
+ set <code className="font-mono">static componentName = '...'</code> on the
262
+ subclass or call <code className="font-mono">.component('...')</code> in the
263
+ fluent form.
264
+ </EntryComponentError>
265
+ )
266
+ break
267
+ }
268
+ const Component = getEntryComponent(componentName)
269
+ if (!Component) {
270
+ body = (
271
+ <EntryComponentError>
272
+ No component registered under name <code className="font-mono">{componentName}</code>.
273
+ Register it at app boot:
274
+ <pre className="mt-2 overflow-x-auto rounded bg-amber-100/60 p-2 text-xs dark:bg-amber-900/30">{`import { registerEntryComponents } from '@pilotiq/pilotiq/entries'\nregisterEntryComponents({ ${componentName}: ${componentName} })`}</pre>
275
+ </EntryComponentError>
276
+ )
277
+ break
278
+ }
279
+ // Render-time errors propagate to React's nearest error boundary —
280
+ // surfacing them inline here would require wrapping every entry in
281
+ // its own boundary, which v1 doesn't ship. The two pre-render
282
+ // sentinels above (missing name / missing registration) cover the
283
+ // typical wiring mistakes.
284
+ body = <Component value={value} />
285
+ break
286
+ }
287
+
288
+ case 'repeatable': {
289
+ // Read-only sibling of `Repeater`. Reads `meta.rows` (resolved by
290
+ // `resolveRepeatableRows`) and dispatches on the chosen layout —
291
+ // `table > grid > stack`. Empty / non-array state falls through to
292
+ // the inherited `default()` placeholder, same as every other entry.
293
+ const rows = (el['rows'] as Array<{ id: string; children: ElementMeta[] }> | undefined) ?? []
294
+ if (rows.length === 0) {
295
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
296
+ break
297
+ }
298
+
299
+ const tableCfg = el['table'] as { columns: Array<{ label: string; alignment?: 'left' | 'center' | 'right'; width?: string }> } | undefined
300
+ const gridN = el['grid'] as number | undefined
301
+ const innerCols = el['columns'] as number | undefined
302
+ const contained = el['contained'] !== false
303
+
304
+ if (tableCfg && tableCfg.columns.length > 0) {
305
+ const cols = tableCfg.columns
306
+ body = (
307
+ <table className="w-full border border-border text-sm">
308
+ {cols.some(c => c.width) && (
309
+ <colgroup>
310
+ {cols.map((c, i) => (
311
+ <col key={i} style={c.width ? { width: c.width } : undefined} />
312
+ ))}
313
+ </colgroup>
314
+ )}
315
+ <thead>
316
+ <tr className="bg-muted/50 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground">
317
+ {cols.map((c, i) => (
318
+ <th
319
+ key={i}
320
+ className={`border-b border-border px-2 py-1 ${c.alignment === 'right' ? 'text-right' : c.alignment === 'center' ? 'text-center' : ''}`.trim()}
321
+ >
322
+ {c.label}
323
+ </th>
324
+ ))}
325
+ </tr>
326
+ </thead>
327
+ <tbody>
328
+ {rows.map(row => (
329
+ <tr key={row.id} className="border-t border-border first:border-t-0 align-top">
330
+ {row.children.map((child, i) => {
331
+ const align = cols[i]?.alignment
332
+ const alignCls = align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : ''
333
+ return (
334
+ <td key={i} className={`px-2 py-1 ${alignCls}`.trim()}>
335
+ {renderElement(child, i)}
336
+ </td>
337
+ )
338
+ })}
339
+ </tr>
340
+ ))}
341
+ </tbody>
342
+ </table>
343
+ )
344
+ break
345
+ }
346
+
347
+ const cardCls = contained
348
+ ? 'rounded-md border border-border p-3 bg-background'
349
+ : ''
350
+ const innerColsCls = innerCols && innerCols >= 2
351
+ ? `grid gap-3 grid-cols-1 md:grid-cols-${Math.min(innerCols, 6)}`
352
+ : 'space-y-2'
353
+
354
+ const cards = rows.map(row => (
355
+ <div key={row.id} className={`${cardCls} ${innerColsCls}`.trim()}>
356
+ {row.children.map((child, i) => renderElement(child, i))}
357
+ </div>
358
+ ))
359
+
360
+ if (gridN && gridN >= 2) {
361
+ const cap = Math.min(gridN, 6)
362
+ body = (
363
+ <div className={`w-full grid gap-3 grid-cols-1 md:grid-cols-${cap}`}>
364
+ {cards}
365
+ </div>
366
+ )
367
+ break
368
+ }
369
+
370
+ body = <div className="w-full space-y-3">{cards}</div>
371
+ break
372
+ }
373
+
374
+ default:
375
+ body = <span className="text-sm text-muted-foreground">{fallback}</span>
376
+ }
377
+
378
+ const copyable = el['copyable'] as { label?: string } | undefined
379
+ const copyValue = el['_formatted'] !== undefined
380
+ ? String(el['_formatted'])
381
+ : value === null || value === undefined
382
+ ? ''
383
+ : typeof value === 'object'
384
+ ? JSON.stringify(value)
385
+ : String(value)
386
+
387
+ return (
388
+ <EntryShell
389
+ key={index}
390
+ el={el}
391
+ copyValue={copyable !== undefined ? copyValue : undefined}
392
+ copyableLabel={copyable?.label}
393
+ >
394
+ {body}
395
+ </EntryShell>
396
+ )
397
+ }
398
+
399
+ interface EntryShellProps {
400
+ el: ElementMeta
401
+ copyValue?: string | undefined
402
+ copyableLabel?: string | undefined
403
+ children: React.ReactNode
404
+ }
405
+
406
+ function EntryShell({ el, copyValue, copyableLabel, children }: EntryShellProps): React.ReactNode {
407
+ const label = String(el['label'] ?? '')
408
+ const helperText = el['helperText'] ? String(el['helperText']) : undefined
409
+ const tooltipText = el['tooltip'] ? String(el['tooltip']) : undefined
410
+ const inline = el['inlineLabel'] === true
411
+
412
+ const labelNode = label ? (
413
+ <div className="flex items-center gap-1.5 text-sm font-medium text-muted-foreground">
414
+ <span>{label}</span>
415
+ {tooltipText && <EntryTooltip text={tooltipText} />}
416
+ </div>
417
+ ) : null
418
+
419
+ const valueRow = (
420
+ <div className="flex items-center gap-2">
421
+ {children}
422
+ {copyValue !== undefined && (
423
+ <EntryCopyButton text={copyValue} label={copyableLabel ?? 'Copy'} />
424
+ )}
425
+ </div>
426
+ )
427
+
428
+ if (inline) {
429
+ return (
430
+ <div className="flex items-baseline gap-3">
431
+ {labelNode && <div className="min-w-32">{labelNode}</div>}
432
+ <div className="min-w-0 flex-1">
433
+ {valueRow}
434
+ {helperText && <p className="mt-1 text-xs text-muted-foreground">{helperText}</p>}
435
+ </div>
436
+ </div>
437
+ )
438
+ }
439
+
440
+ return (
441
+ <div className="space-y-1">
442
+ {labelNode}
443
+ {valueRow}
444
+ {helperText && <p className="text-xs text-muted-foreground">{helperText}</p>}
445
+ </div>
446
+ )
447
+ }
448
+
449
+ function EntryComponentError({ children }: { children: React.ReactNode }): React.ReactNode {
450
+ return (
451
+ <div
452
+ role="alert"
453
+ 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"
454
+ >
455
+ {children}
456
+ </div>
457
+ )
458
+ }
459
+
460
+ function EntryTooltip({ text }: { text: string }): React.ReactNode {
461
+ const trigger = (
462
+ <button
463
+ type="button"
464
+ className="inline-flex h-3.5 w-3.5 items-center justify-center rounded-full border text-[10px] text-muted-foreground"
465
+ aria-label={text}
466
+ >
467
+ ?
468
+ </button>
469
+ )
470
+ return (
471
+ <TooltipProvider>
472
+ <Tooltip>
473
+ <TooltipTrigger render={() => trigger} />
474
+ <TooltipContent>{text}</TooltipContent>
475
+ </Tooltip>
476
+ </TooltipProvider>
477
+ )
478
+ }
479
+
480
+ function EntryCopyButton({ text, label }: { text: string; label: string }): React.ReactNode {
481
+ const [copied, setCopied] = useState(false)
482
+ const handleClick = () => {
483
+ if (typeof navigator !== 'undefined' && navigator.clipboard) {
484
+ navigator.clipboard.writeText(text).then(() => {
485
+ setCopied(true)
486
+ setTimeout(() => setCopied(false), 1500)
487
+ }).catch(() => { /* ignore — older browser / permission denied */ })
488
+ }
489
+ }
490
+ return (
491
+ <button
492
+ type="button"
493
+ onClick={handleClick}
494
+ aria-label={label}
495
+ title={label}
496
+ className="inline-flex h-6 w-6 items-center justify-center rounded text-muted-foreground hover:text-foreground hover:bg-muted"
497
+ >
498
+ {copied ? <CheckIcon className="size-3.5" /> : <CopyIcon className="size-3.5" />}
499
+ </button>
500
+ )
501
+ }