@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,103 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { Pilotiq } from '../Pilotiq.js'
4
+ import { isNavItemActive } from './component-slots.js'
5
+
6
+ describe('Pilotiq.components({ nav, header, footer })', () => {
7
+ it('starts empty', () => {
8
+ const panel = Pilotiq.make('Admin')
9
+ assert.deepEqual(panel.getComponentSlots(), {})
10
+ })
11
+
12
+ it('stores a registered nav slot', () => {
13
+ const Nav = () => null
14
+ const panel = Pilotiq.make('Admin').components({ nav: Nav })
15
+ const slots = panel.getComponentSlots()
16
+ assert.equal(slots.nav, Nav)
17
+ })
18
+
19
+ it('stores a registered header slot', () => {
20
+ const Header = () => null
21
+ const panel = Pilotiq.make('Admin').components({ header: Header })
22
+ assert.equal(panel.getComponentSlots().header, Header)
23
+ })
24
+
25
+ it('stores a registered footer slot', () => {
26
+ const Footer = () => null
27
+ const panel = Pilotiq.make('Admin').components({ footer: Footer })
28
+ assert.equal(panel.getComponentSlots().footer, Footer)
29
+ })
30
+
31
+ it('merges across multiple calls — later wins per slot, unset keys preserved', () => {
32
+ const A = () => null
33
+ const B = () => null
34
+ const panel = Pilotiq.make('Admin')
35
+ .components({ nav: A })
36
+ .components({}) // empty object keeps existing
37
+ .components({ nav: B }) // overrides
38
+ assert.equal(panel.getComponentSlots().nav, B)
39
+ })
40
+
41
+ it('preserves unset slots when overriding only one slot', () => {
42
+ const Nav = () => null
43
+ const Header = () => null
44
+ const Footer = () => null
45
+ const Nav2 = () => null
46
+ const panel = Pilotiq.make('Admin')
47
+ .components({ nav: Nav, header: Header, footer: Footer })
48
+ .components({ nav: Nav2 }) // only nav changes
49
+ const slots = panel.getComponentSlots()
50
+ assert.equal(slots.nav, Nav2)
51
+ assert.equal(slots.header, Header)
52
+ assert.equal(slots.footer, Footer)
53
+ })
54
+
55
+ it('allows registering all three slots together', () => {
56
+ const Nav = () => null
57
+ const Header = () => null
58
+ const Footer = () => null
59
+ const panel = Pilotiq.make('Admin')
60
+ .components({ nav: Nav, header: Header, footer: Footer })
61
+ const slots = panel.getComponentSlots()
62
+ assert.equal(slots.nav, Nav)
63
+ assert.equal(slots.header, Header)
64
+ assert.equal(slots.footer, Footer)
65
+ })
66
+
67
+ it('survives plugin registration without interference', () => {
68
+ const Nav = () => null
69
+ const panel = Pilotiq.make('Admin')
70
+ .components({ nav: Nav })
71
+ .use({ name: 'noop', register() { /* no-op */ } })
72
+ assert.equal(panel.getComponentSlots().nav, Nav)
73
+ })
74
+ })
75
+
76
+ describe('isNavItemActive', () => {
77
+ const basePath = '/admin'
78
+
79
+ it('returns false when currentPath is undefined', () => {
80
+ assert.equal(isNavItemActive('/admin/users', undefined, basePath), false)
81
+ })
82
+
83
+ it('matches the dashboard URL only on exact equality', () => {
84
+ assert.equal(isNavItemActive(basePath, basePath, basePath), true)
85
+ // Dashboard URL is a prefix of every panel URL; it must NOT light up
86
+ // for non-root pages — otherwise it'd always read as active.
87
+ assert.equal(isNavItemActive(basePath, '/admin/users', basePath), false)
88
+ })
89
+
90
+ it('matches non-dashboard URLs on exact equality', () => {
91
+ assert.equal(isNavItemActive('/admin/users', '/admin/users', basePath), true)
92
+ })
93
+
94
+ it('matches non-dashboard URLs as a prefix followed by `/`', () => {
95
+ assert.equal(isNavItemActive('/admin/users', '/admin/users/42', basePath), true)
96
+ assert.equal(isNavItemActive('/admin/users', '/admin/users/42/edit', basePath), true)
97
+ })
98
+
99
+ it('does NOT match a sibling URL that shares a prefix without `/`', () => {
100
+ // `/admin/users` must not activate when on `/admin/user` (singular).
101
+ assert.equal(isNavItemActive('/admin/user', '/admin/users', basePath), false)
102
+ })
103
+ })
@@ -0,0 +1,116 @@
1
+ import type * as React from 'react'
2
+ import type { NavItem } from '../pageData.js'
3
+
4
+ /**
5
+ * Props passed to a component registered at `Pilotiq.components({ nav })`.
6
+ *
7
+ * The component owns the entire nav region — for `SidebarLayout` that
8
+ * means the `<SidebarContent>` body (the panel chrome's header / footer
9
+ * still render); for `TopbarLayout` that means the `<nav>` element
10
+ * between the brand cluster and the right-side controls.
11
+ *
12
+ * `navigation` is the pre-grouped, pre-sorted tree built by
13
+ * `panelInfo()` — same shape the default renderers consume — so a
14
+ * custom nav can opt into the framework's `navigationGroup` / `sort` /
15
+ * badge metadata for free, or ignore it and walk a custom tree.
16
+ */
17
+ export interface NavComponentProps {
18
+ /**
19
+ * Pre-grouped navigation items. May be empty when nothing the user
20
+ * can access is registered. Items with `children` represent nested
21
+ * sub-navigation (the default sidebar renders these under
22
+ * `SidebarMenuSub`).
23
+ */
24
+ navigation: NavItem[]
25
+ /** Panel base path (e.g. `/admin`). */
26
+ basePath: string
27
+ /** Current request pathname — undefined when not in a request scope
28
+ * (e.g. unit-testing the layout standalone). */
29
+ currentPath?: string
30
+ }
31
+
32
+ /**
33
+ * Props passed to a component registered at `Pilotiq.components({ header })`.
34
+ *
35
+ * The component owns the entire `<header>` chrome bar — in `SidebarLayout`
36
+ * that's the top bar above the main content (sidebar trigger, search,
37
+ * theme toggle, bell, user menu); in `TopbarLayout` that's the *whole*
38
+ * topbar including the brand cluster and the nav region. Setting `header`
39
+ * on `TopbarLayout` makes the `nav` slot irrelevant for that layout —
40
+ * the consumer's header owns everything between page edges.
41
+ *
42
+ * Render hooks that splice INSIDE the default header
43
+ * (`panels::topbar.start`, `panels::topbar.end`, `panels::user-menu.before`,
44
+ * `panels::user-menu.after`) don't fire when the header is replaced —
45
+ * the surrounding container is gone, so there's nowhere to splice into.
46
+ * Hooks rooted outside the header (`panels::sidebar.start` / `.footer`,
47
+ * `panels::sidebar.nav.start` / `.end`, `panels::footer`) keep firing.
48
+ *
49
+ * Shape mirrors `NavComponentProps` so a header that wants to render
50
+ * the nav inline (the TopbarLayout case) can do so without juggling a
51
+ * second slot.
52
+ */
53
+ export interface HeaderComponentProps {
54
+ /**
55
+ * Pre-grouped navigation items — same tree the `nav` slot receives.
56
+ * Header consumers that want to mount the topbar nav inline read
57
+ * this; sidebar-layout consumers can ignore it.
58
+ */
59
+ navigation: NavItem[]
60
+ /** Panel base path (e.g. `/admin`). */
61
+ basePath: string
62
+ /** Current request pathname — undefined when not in a request scope. */
63
+ currentPath?: string
64
+ }
65
+
66
+ /**
67
+ * Props passed to a component registered at `Pilotiq.components({ footer })`.
68
+ *
69
+ * The component mounts as a `<footer>` element BELOW the main content
70
+ * area — outside the scrolling region in both layouts. It is a separate
71
+ * surface from the `panels::footer` render hook, which continues to
72
+ * fire INSIDE the main content area (use the hook to append per-page
73
+ * trailing chrome; use this slot for site-chrome that frames every
74
+ * page like a status bar or copyright row).
75
+ */
76
+ export interface FooterComponentProps {
77
+ /** Panel base path (e.g. `/admin`). */
78
+ basePath: string
79
+ /** Current request pathname — undefined when not in a request scope. */
80
+ currentPath?: string
81
+ }
82
+
83
+ /**
84
+ * Registry of build-time component overrides registered through
85
+ * `Pilotiq.components({ nav, header, footer, … })`. The Vite plugin
86
+ * harvests the actual React refs into `pages/(pilotiq)/_components.ts`
87
+ * and forwards them to `<AppShell>`; component refs never travel over
88
+ * the wire.
89
+ *
90
+ * The shape is open-ended so additional slots can land without a
91
+ * breaking change when a concrete consumer asks for them.
92
+ */
93
+ export interface ComponentSlotRegistry {
94
+ nav?: React.ComponentType<NavComponentProps>
95
+ header?: React.ComponentType<HeaderComponentProps>
96
+ footer?: React.ComponentType<FooterComponentProps>
97
+ }
98
+
99
+ /**
100
+ * Active-link match identical to the default sidebar's. Exported so
101
+ * custom nav components can reuse the framework's longest-prefix
102
+ * semantics: the dashboard URL (`basePath`) only matches on exact
103
+ * equality — otherwise it would light up on every panel page — and
104
+ * non-dashboard URLs match as a prefix followed by `/` or end-of-string
105
+ * so `/admin/users` doesn't activate `/admin/user`.
106
+ */
107
+ export function isNavItemActive(
108
+ url: string,
109
+ currentPath: string | undefined,
110
+ basePath: string,
111
+ ): boolean {
112
+ if (!currentPath) return false
113
+ if (url === basePath) return currentPath === basePath
114
+ if (url === currentPath) return true
115
+ return currentPath.startsWith(url + '/')
116
+ }
@@ -20,6 +20,18 @@ import {
20
20
  DEFAULT_CLONE,
21
21
  DEFAULT_DELETE,
22
22
  } from './rowChromeButton.js'
23
+ import {
24
+ generateRowId, makeAccordionStorage, makeCollapsedStorage,
25
+ } from './rowState.js'
26
+ import { useRowReorderDnd } from './useRowReorderDnd.js'
27
+
28
+ const collapsedStorage = makeCollapsedStorage('builder')
29
+ const accordionStorage = makeAccordionStorage('builder')
30
+ const initSeedCollapsed = collapsedStorage.seed
31
+ const writeCollapsedToStorage = collapsedStorage.write
32
+ const deleteCollapsedFromStorage = collapsedStorage.remove
33
+ const readAccordionFromStorage = accordionStorage.read
34
+ const writeAccordionToStorage = accordionStorage.write
23
35
 
24
36
  interface BlockShape {
25
37
  name: string
@@ -288,42 +300,23 @@ export function BuilderInput({
288
300
  }
289
301
 
290
302
  // ── DnD state (skipped when buttonsOnly) ────────────────
291
- const [dragId, setDragId] = useState<string | null>(null)
292
- const [dropAt, setDropAt] = useState<number | null>(null)
293
- const dndEnabled = reorderable && !buttonsOnly
294
-
295
- const onRowDragStart = (id: string) => (e: React.DragEvent<HTMLDivElement>): void => {
296
- if (!dndEnabled || disabled) return
297
- setDragId(id)
298
- e.dataTransfer.effectAllowed = 'move'
299
- try { e.dataTransfer.setData('text/plain', id) } catch { /* IE quirk */ }
300
- }
301
-
302
- const onRowDragOver = (idx: number) => (e: React.DragEvent<HTMLDivElement>): void => {
303
- if (!dndEnabled || disabled || dragId === null) return
304
- e.preventDefault()
305
- e.dataTransfer.dropEffect = 'move'
306
- const rect = e.currentTarget.getBoundingClientRect()
307
- const aboveHalf = e.clientY < rect.top + rect.height / 2
308
- setDropAt(aboveHalf ? idx : idx + 1)
309
- }
310
-
311
- const onRowDrop = (e: React.DragEvent<HTMLDivElement>): void => {
312
- if (!dndEnabled || disabled || dragId === null || dropAt === null) {
313
- setDragId(null); setDropAt(null); return
314
- }
315
- e.preventDefault()
316
- setRows(prev => {
317
- const fromIdx = prev.findIndex(r => r.id === dragId)
318
- if (fromIdx < 0) return prev
319
- return reorderRows(prev, fromIdx, dropAt)
320
- })
321
- setDragId(null); setDropAt(null)
322
- }
323
-
324
- const onRowDragEnd = (): void => {
325
- setDragId(null); setDropAt(null)
326
- }
303
+ const dndEnabled = reorderable && !buttonsOnly && !disabled
304
+ const {
305
+ dragId, dropAt,
306
+ onDragStart: onRowDragStart,
307
+ onDragOver: onRowDragOver,
308
+ onDrop: onRowDrop,
309
+ onDragEnd: onRowDragEnd,
310
+ } = useRowReorderDnd({
311
+ enabled: dndEnabled,
312
+ onDrop: (fromId, at) => {
313
+ setRows(prev => {
314
+ const fromIdx = prev.findIndex(r => r.id === fromId)
315
+ if (fromIdx < 0) return prev
316
+ return reorderRows(prev, fromIdx, at)
317
+ })
318
+ },
319
+ })
327
320
 
328
321
  // ── Inner-field live re-resolve (mirrors RepeaterInput) ─
329
322
  const formState = useFormState()
@@ -949,84 +942,3 @@ function prefixFieldNames(el: ElementMeta, prefix: string): ElementMeta {
949
942
  return el
950
943
  }
951
944
 
952
- let _rowSeqFallback = 0
953
- function generateRowId(): string {
954
- type CryptoLike = { randomUUID?: () => string }
955
- const c = (globalThis as { crypto?: CryptoLike }).crypto
956
- if (c?.randomUUID) return c.randomUUID()
957
- return `row-${Date.now()}-${++_rowSeqFallback}`
958
- }
959
-
960
- function collapsedStorageKey(formId: string, name: string, rowId: string): string {
961
- return `pilotiq.builder.${formId}.${name}.${rowId}`
962
- }
963
-
964
- function initSeedCollapsed(
965
- rows: RowState[],
966
- formId: string,
967
- name: string,
968
- defaultValue: boolean,
969
- collapsible: boolean,
970
- ): Record<string, boolean> {
971
- if (!collapsible) return {}
972
- const out: Record<string, boolean> = {}
973
- for (const row of rows) {
974
- out[row.id] = readCollapsedFromStorage(formId, name, row.id, defaultValue)
975
- }
976
- return out
977
- }
978
-
979
- function readCollapsedFromStorage(
980
- formId: string,
981
- name: string,
982
- rowId: string,
983
- defaultValue: boolean,
984
- ): boolean {
985
- if (typeof window === 'undefined') return defaultValue
986
- try {
987
- const raw = window.localStorage.getItem(collapsedStorageKey(formId, name, rowId))
988
- if (raw === null) return defaultValue
989
- return raw === 'true'
990
- } catch { return defaultValue }
991
- }
992
-
993
- function writeCollapsedToStorage(
994
- formId: string,
995
- name: string,
996
- rowId: string,
997
- value: boolean,
998
- ): void {
999
- if (typeof window === 'undefined') return
1000
- try {
1001
- window.localStorage.setItem(collapsedStorageKey(formId, name, rowId), String(value))
1002
- } catch { /* quota exceeded; silent */ }
1003
- }
1004
-
1005
- function deleteCollapsedFromStorage(formId: string, name: string, rowId: string): void {
1006
- if (typeof window === 'undefined') return
1007
- try { window.localStorage.removeItem(collapsedStorageKey(formId, name, rowId)) } catch { /* */ }
1008
- }
1009
-
1010
- function accordionStorageKey(formId: string, name: string): string {
1011
- return `pilotiq.builder.${formId}.${name}.accordion`
1012
- }
1013
-
1014
- /**
1015
- * Read the persisted accordion-open row id. Mirror of
1016
- * `RepeaterInput.readAccordionFromStorage` — `undefined` = no value
1017
- * stored, `''` = explicitly all-collapsed, anything else = open row id.
1018
- */
1019
- function readAccordionFromStorage(formId: string, name: string): string | undefined {
1020
- if (typeof window === 'undefined') return undefined
1021
- try {
1022
- const raw = window.localStorage.getItem(accordionStorageKey(formId, name))
1023
- return raw === null ? undefined : raw
1024
- } catch { return undefined }
1025
- }
1026
-
1027
- function writeAccordionToStorage(formId: string, name: string, openId: string | null): void {
1028
- if (typeof window === 'undefined') return
1029
- try {
1030
- window.localStorage.setItem(accordionStorageKey(formId, name), openId ?? '')
1031
- } catch { /* quota exceeded — fall back to in-memory only */ }
1032
- }
@@ -296,7 +296,6 @@ export function MarkdownInput({
296
296
  <div
297
297
  className="prose prose-sm dark:prose-invert max-w-none px-3 py-2"
298
298
  style={taStyle}
299
- // eslint-disable-next-line react/no-danger
300
299
  dangerouslySetInnerHTML={{ __html: previewHtml || '<p class="text-muted-foreground italic">Nothing to preview</p>' }}
301
300
  />
302
301
  </>
@@ -20,6 +20,18 @@ import {
20
20
  DEFAULT_DELETE,
21
21
  } from './rowChromeButton.js'
22
22
  import { syncRowGates } from './syncRowGates.js'
23
+ import {
24
+ generateRowId, makeAccordionStorage, makeCollapsedStorage,
25
+ } from './rowState.js'
26
+ import { useRowReorderDnd } from './useRowReorderDnd.js'
27
+
28
+ const collapsedStorage = makeCollapsedStorage('repeater')
29
+ const accordionStorage = makeAccordionStorage('repeater')
30
+ const initSeedCollapsed = collapsedStorage.seed
31
+ const writeCollapsedToStorage = collapsedStorage.write
32
+ const deleteCollapsedFromStorage = collapsedStorage.remove
33
+ const readAccordionFromStorage = accordionStorage.read
34
+ const writeAccordionToStorage = accordionStorage.write
23
35
 
24
36
  /**
25
37
  * Pure reorder helper — used by both the HTML5 DnD path and the
@@ -324,49 +336,22 @@ export function RepeaterInput({
324
336
  }
325
337
 
326
338
  // ── DnD state ───────────────────────────────────────────
327
- // dragId — the row being dragged, or null when no drag is active.
328
- // dropAt — the boundary slot the cursor is currently over
329
- // (range 0..rows.length); null when not over a valid drop target.
330
- const [dragId, setDragId] = useState<string | null>(null)
331
- const [dropAt, setDropAt] = useState<number | null>(null)
332
-
333
- // Generic on `HTMLElement` so the same handlers wire onto a `<div>`
334
- // row (card / grid layouts) AND a `<tr>` row (table layout). Concrete
335
- // refinement happens at the consumer's prop boundary.
336
- const onRowDragStart = (id: string) => (e: React.DragEvent<HTMLElement>): void => {
337
- if (!reorderable || disabled) return
338
- setDragId(id)
339
- // dataTransfer needs *something* to register the drag in Firefox.
340
- e.dataTransfer.effectAllowed = 'move'
341
- try { e.dataTransfer.setData('text/plain', id) } catch { /* IE quirk; ignore */ }
342
- }
343
-
344
- const onRowDragOver = (idx: number) => (e: React.DragEvent<HTMLElement>): void => {
345
- if (!reorderable || disabled || dragId === null) return
346
- e.preventDefault()
347
- e.dataTransfer.dropEffect = 'move'
348
- // Drop above this row when cursor is in its top half, below when in its bottom half.
349
- const rect = e.currentTarget.getBoundingClientRect()
350
- const aboveHalf = e.clientY < rect.top + rect.height / 2
351
- setDropAt(aboveHalf ? idx : idx + 1)
352
- }
353
-
354
- const onRowDrop = (e: React.DragEvent<HTMLElement>): void => {
355
- if (!reorderable || disabled || dragId === null || dropAt === null) {
356
- setDragId(null); setDropAt(null); return
357
- }
358
- e.preventDefault()
359
- setRows(prev => {
360
- const fromIdx = prev.findIndex(r => r.id === dragId)
361
- if (fromIdx < 0) return prev
362
- return reorderRows(prev, fromIdx, dropAt)
363
- })
364
- setDragId(null); setDropAt(null)
365
- }
366
-
367
- const onRowDragEnd = (): void => {
368
- setDragId(null); setDropAt(null)
369
- }
339
+ const {
340
+ dragId, dropAt,
341
+ onDragStart: onRowDragStart,
342
+ onDragOver: onRowDragOver,
343
+ onDrop: onRowDrop,
344
+ onDragEnd: onRowDragEnd,
345
+ } = useRowReorderDnd({
346
+ enabled: reorderable && !disabled,
347
+ onDrop: (fromId, at) => {
348
+ setRows(prev => {
349
+ const fromIdx = prev.findIndex(r => r.id === fromId)
350
+ if (fromIdx < 0) return prev
351
+ return reorderRows(prev, fromIdx, at)
352
+ })
353
+ },
354
+ })
370
355
 
371
356
  // ── Inner-field live re-resolve (Plan #14 v1.1) ─────────────
372
357
  // Inner Repeater inputs are uncontrolled (so reorder/clone preserves
@@ -487,7 +472,6 @@ export function RepeaterInput({
487
472
  <RepeaterTableLayout
488
473
  rows={rows}
489
474
  name={name}
490
- formId={formId}
491
475
  disabled={disabled}
492
476
  columns={tableColumns}
493
477
  addLabel={addLabel}
@@ -939,7 +923,7 @@ function DropIndicator(): React.ReactElement {
939
923
  * stays stable across rows even when individual buttons disable.
940
924
  */
941
925
  function RepeaterTableLayout({
942
- rows, name, formId: _formId, disabled, columns, addLabel, buttons, atMin, atMax,
926
+ rows, name, disabled, columns, addLabel, buttons, atMin, atMax,
943
927
  reorderable, cloneable,
944
928
  firstVisibleIdx, lastVisibleIdx, hasVisibleRow,
945
929
  dragId,
@@ -949,7 +933,6 @@ function RepeaterTableLayout({
949
933
  }: {
950
934
  rows: RowState[]
951
935
  name: string
952
- formId: string
953
936
  disabled: boolean
954
937
  columns: TableColumnShape[]
955
938
  addLabel: string
@@ -1265,88 +1248,4 @@ function prefixFieldNames(el: ElementMeta, prefix: string): ElementMeta {
1265
1248
  return el
1266
1249
  }
1267
1250
 
1268
- let _rowSeqFallback = 0
1269
- function generateRowId(): string {
1270
- type CryptoLike = { randomUUID?: () => string }
1271
- const c = (globalThis as { crypto?: CryptoLike }).crypto
1272
- if (c?.randomUUID) return c.randomUUID()
1273
- return `row-${Date.now()}-${++_rowSeqFallback}`
1274
- }
1275
-
1276
- function collapsedStorageKey(formId: string, name: string, rowId: string): string {
1277
- return `pilotiq.repeater.${formId}.${name}.${rowId}`
1278
- }
1279
-
1280
- function initSeedCollapsed(
1281
- rows: RowState[],
1282
- formId: string,
1283
- name: string,
1284
- defaultValue: boolean,
1285
- collapsible: boolean,
1286
- ): Record<string, boolean> {
1287
- if (!collapsible) return {}
1288
- const out: Record<string, boolean> = {}
1289
- for (const row of rows) {
1290
- out[row.id] = readCollapsedFromStorage(formId, name, row.id, defaultValue)
1291
- }
1292
- return out
1293
- }
1294
-
1295
- function readCollapsedFromStorage(
1296
- formId: string,
1297
- name: string,
1298
- rowId: string,
1299
- defaultValue: boolean,
1300
- ): boolean {
1301
- if (typeof window === 'undefined') return defaultValue
1302
- try {
1303
- const raw = window.localStorage.getItem(collapsedStorageKey(formId, name, rowId))
1304
- if (raw === null) return defaultValue
1305
- return raw === 'true'
1306
- } catch { return defaultValue }
1307
- }
1308
-
1309
- function writeCollapsedToStorage(
1310
- formId: string,
1311
- name: string,
1312
- rowId: string,
1313
- value: boolean,
1314
- ): void {
1315
- if (typeof window === 'undefined') return
1316
- try {
1317
- window.localStorage.setItem(collapsedStorageKey(formId, name, rowId), String(value))
1318
- } catch { /* quota exceeded — fall back to in-memory only */ }
1319
- }
1320
-
1321
- function deleteCollapsedFromStorage(formId: string, name: string, rowId: string): void {
1322
- if (typeof window === 'undefined') return
1323
- try {
1324
- window.localStorage.removeItem(collapsedStorageKey(formId, name, rowId))
1325
- } catch { /* ignore */ }
1326
- }
1327
-
1328
- function accordionStorageKey(formId: string, name: string): string {
1329
- return `pilotiq.repeater.${formId}.${name}.accordion`
1330
- }
1331
-
1332
- /**
1333
- * Read the persisted accordion-open row id. Returns `undefined` when no
1334
- * value is stored (so the caller can fall back to the default-open
1335
- * heuristic). Returns `''` when the user explicitly closed every row —
1336
- * the caller maps that to `null` openId.
1337
- */
1338
- function readAccordionFromStorage(formId: string, name: string): string | undefined {
1339
- if (typeof window === 'undefined') return undefined
1340
- try {
1341
- const raw = window.localStorage.getItem(accordionStorageKey(formId, name))
1342
- return raw === null ? undefined : raw
1343
- } catch { return undefined }
1344
- }
1345
-
1346
- function writeAccordionToStorage(formId: string, name: string, openId: string | null): void {
1347
- if (typeof window === 'undefined') return
1348
- try {
1349
- window.localStorage.setItem(accordionStorageKey(formId, name), openId ?? '')
1350
- } catch { /* quota exceeded — fall back to in-memory only */ }
1351
- }
1352
1251