@pilotiq/pilotiq 0.24.1 → 0.24.3

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 (518) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/boost/guidelines.md +571 -0
  3. package/boost/skills/pilotiq-actions/SKILL.md +49 -0
  4. package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
  5. package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
  6. package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -0
  7. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  9. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  10. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  11. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  12. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  13. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  14. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  15. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  16. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  17. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  18. package/dist/Pilotiq.d.ts +31 -0
  19. package/dist/Pilotiq.d.ts.map +1 -1
  20. package/dist/Pilotiq.js +3 -1
  21. package/dist/Pilotiq.js.map +1 -1
  22. package/dist/PilotiqRegistry.d.ts +13 -0
  23. package/dist/PilotiqRegistry.d.ts.map +1 -1
  24. package/dist/PilotiqRegistry.js +15 -0
  25. package/dist/PilotiqRegistry.js.map +1 -1
  26. package/dist/pageData/misc.d.ts.map +1 -1
  27. package/dist/pageData/misc.js +6 -0
  28. package/dist/pageData/misc.js.map +1 -1
  29. package/dist/pageData/navigation.d.ts +1 -0
  30. package/dist/pageData/navigation.d.ts.map +1 -1
  31. package/dist/pageData/navigation.js +3 -0
  32. package/dist/pageData/navigation.js.map +1 -1
  33. package/dist/pageData/relationPages.d.ts.map +1 -1
  34. package/dist/pageData/relationPages.js +3 -0
  35. package/dist/pageData/relationPages.js.map +1 -1
  36. package/dist/pageData/resourcePages.d.ts.map +1 -1
  37. package/dist/pageData/resourcePages.js +8 -0
  38. package/dist/pageData/resourcePages.js.map +1 -1
  39. package/dist/react/AppShell.d.ts +8 -0
  40. package/dist/react/AppShell.d.ts.map +1 -1
  41. package/dist/react/AppShell.js.map +1 -1
  42. package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
  43. package/dist/react/layouts/SidebarLayout.js +10 -2
  44. package/dist/react/layouts/SidebarLayout.js.map +1 -1
  45. package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
  46. package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
  47. package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
  48. package/dist/routes/relations.d.ts.map +1 -1
  49. package/dist/routes/relations.js +25 -18
  50. package/dist/routes/relations.js.map +1 -1
  51. package/dist/routes/resources.js.map +1 -1
  52. package/package.json +10 -5
  53. package/.turbo/turbo-build.log +0 -8
  54. package/CLAUDE.md +0 -265
  55. package/src/Cluster.test.ts +0 -283
  56. package/src/Cluster.ts +0 -83
  57. package/src/Column.test.ts +0 -199
  58. package/src/Column.ts +0 -710
  59. package/src/Global.test.ts +0 -367
  60. package/src/Global.ts +0 -169
  61. package/src/Page.test.ts +0 -114
  62. package/src/Page.ts +0 -208
  63. package/src/Pilotiq.perf.test.ts +0 -252
  64. package/src/Pilotiq.test.ts +0 -129
  65. package/src/Pilotiq.ts +0 -1158
  66. package/src/PilotiqRegistry.ts +0 -36
  67. package/src/PilotiqServiceProvider.ts +0 -121
  68. package/src/RelationManager.test.ts +0 -400
  69. package/src/RelationManager.ts +0 -527
  70. package/src/RenderHook.test.ts +0 -252
  71. package/src/RenderHook.ts +0 -242
  72. package/src/Resource.test.ts +0 -284
  73. package/src/Resource.ts +0 -526
  74. package/src/RightPanel.test.ts +0 -202
  75. package/src/RightPanel.ts +0 -132
  76. package/src/Tab.test.ts +0 -91
  77. package/src/Tab.ts +0 -156
  78. package/src/UserMenuItem.ts +0 -145
  79. package/src/actions/Action.test.ts +0 -2526
  80. package/src/actions/Action.ts +0 -1515
  81. package/src/actions/ActionGroup.test.ts +0 -112
  82. package/src/actions/ActionGroup.ts +0 -173
  83. package/src/actions/attachFactory.ts +0 -172
  84. package/src/actions/bulkFactories.ts +0 -168
  85. package/src/actions/crudFactories.ts +0 -220
  86. package/src/actions/exportFactory.ts +0 -225
  87. package/src/actions/factoryHelpers.ts +0 -177
  88. package/src/actions/importFactory.ts +0 -243
  89. package/src/actions/index.ts +0 -17
  90. package/src/actions/m2mFactories.ts +0 -193
  91. package/src/actions/relationFactories.ts +0 -372
  92. package/src/applyPageHooks.test.ts +0 -463
  93. package/src/applyPageHooks.ts +0 -330
  94. package/src/authorization.test.ts +0 -483
  95. package/src/breadcrumbs.test.ts +0 -238
  96. package/src/cells/coerce.test.ts +0 -85
  97. package/src/cells/coerce.ts +0 -84
  98. package/src/clusterPaths.ts +0 -35
  99. package/src/columns/BadgeColumn.test.ts +0 -54
  100. package/src/columns/BadgeColumn.ts +0 -32
  101. package/src/columns/BooleanColumn.test.ts +0 -41
  102. package/src/columns/BooleanColumn.ts +0 -18
  103. package/src/columns/ColorColumn.test.ts +0 -37
  104. package/src/columns/ColorColumn.ts +0 -38
  105. package/src/columns/IconColumn.test.ts +0 -54
  106. package/src/columns/IconColumn.ts +0 -37
  107. package/src/columns/ImageColumn.test.ts +0 -41
  108. package/src/columns/ImageColumn.ts +0 -28
  109. package/src/columns/SelectColumn.ts +0 -98
  110. package/src/columns/TextColumn.test.ts +0 -190
  111. package/src/columns/TextColumn.ts +0 -20
  112. package/src/columns/TextInputColumn.ts +0 -68
  113. package/src/columns/ToggleColumn.ts +0 -46
  114. package/src/columns/editableColumns.test.ts +0 -238
  115. package/src/columns/index.ts +0 -9
  116. package/src/defaultGlobalPages.ts +0 -95
  117. package/src/defaultPages.test.ts +0 -634
  118. package/src/defaultPages.ts +0 -617
  119. package/src/defaultViewPage.test.ts +0 -147
  120. package/src/elements/Form.test.ts +0 -223
  121. package/src/elements/Form.ts +0 -416
  122. package/src/elements/ListTabs.ts +0 -28
  123. package/src/elements/Table.test.ts +0 -422
  124. package/src/elements/Table.ts +0 -850
  125. package/src/elements/TableGroup.test.ts +0 -260
  126. package/src/elements/TableGroup.ts +0 -334
  127. package/src/elements/dispatchAction.test.ts +0 -463
  128. package/src/elements/dispatchAction.ts +0 -355
  129. package/src/elements/dispatchForm.test.ts +0 -477
  130. package/src/elements/dispatchForm.ts +0 -1993
  131. package/src/elements/dispatchTable.test.ts +0 -1514
  132. package/src/elements/dispatchTable.ts +0 -745
  133. package/src/elements/index.ts +0 -21
  134. package/src/entries/BadgeEntry.ts +0 -39
  135. package/src/entries/CodeEntry.test.ts +0 -40
  136. package/src/entries/CodeEntry.ts +0 -52
  137. package/src/entries/ColorEntry.ts +0 -63
  138. package/src/entries/ComponentEntry.test.ts +0 -173
  139. package/src/entries/ComponentEntry.ts +0 -95
  140. package/src/entries/Entry.ts +0 -304
  141. package/src/entries/IconEntry.ts +0 -49
  142. package/src/entries/ImageEntry.ts +0 -61
  143. package/src/entries/KeyValueEntry.ts +0 -47
  144. package/src/entries/RepeatableEntry.test.ts +0 -239
  145. package/src/entries/RepeatableEntry.ts +0 -173
  146. package/src/entries/TextEntry.test.ts +0 -394
  147. package/src/entries/TextEntry.ts +0 -60
  148. package/src/entries/index.ts +0 -12
  149. package/src/entries/leaves.test.ts +0 -306
  150. package/src/entries/registry.ts +0 -54
  151. package/src/fields/BuilderField.test.ts +0 -1188
  152. package/src/fields/BuilderField.ts +0 -605
  153. package/src/fields/BuilderRelationship.test.ts +0 -811
  154. package/src/fields/CheckboxField.test.ts +0 -44
  155. package/src/fields/CheckboxField.ts +0 -27
  156. package/src/fields/CheckboxListField.test.ts +0 -99
  157. package/src/fields/CheckboxListField.ts +0 -66
  158. package/src/fields/ColorPickerField.test.ts +0 -33
  159. package/src/fields/ColorPickerField.ts +0 -25
  160. package/src/fields/DateField.ts +0 -54
  161. package/src/fields/DateTimeField.test.ts +0 -55
  162. package/src/fields/EmailField.ts +0 -16
  163. package/src/fields/Field.test.ts +0 -654
  164. package/src/fields/Field.ts +0 -817
  165. package/src/fields/FileUploadField.test.ts +0 -143
  166. package/src/fields/FileUploadField.ts +0 -159
  167. package/src/fields/HiddenField.test.ts +0 -27
  168. package/src/fields/HiddenField.ts +0 -28
  169. package/src/fields/KeyValueField.test.ts +0 -105
  170. package/src/fields/KeyValueField.ts +0 -55
  171. package/src/fields/MarkdownField.test.ts +0 -167
  172. package/src/fields/MarkdownField.ts +0 -162
  173. package/src/fields/NumberField.ts +0 -33
  174. package/src/fields/RadioField.test.ts +0 -94
  175. package/src/fields/RadioField.ts +0 -67
  176. package/src/fields/RepeaterField.test.ts +0 -1806
  177. package/src/fields/RepeaterField.ts +0 -939
  178. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  179. package/src/fields/RepeaterSimple.test.ts +0 -248
  180. package/src/fields/RowButton.test.ts +0 -219
  181. package/src/fields/RowButton.ts +0 -135
  182. package/src/fields/SelectField.test.ts +0 -192
  183. package/src/fields/SelectField.ts +0 -235
  184. package/src/fields/SliderField.test.ts +0 -50
  185. package/src/fields/SliderField.ts +0 -53
  186. package/src/fields/SlugField.ts +0 -24
  187. package/src/fields/TagsInputField.test.ts +0 -154
  188. package/src/fields/TagsInputField.ts +0 -133
  189. package/src/fields/TextField.test.ts +0 -213
  190. package/src/fields/TextField.ts +0 -177
  191. package/src/fields/TextareaField.test.ts +0 -58
  192. package/src/fields/TextareaField.ts +0 -59
  193. package/src/fields/ToggleButtonsField.test.ts +0 -106
  194. package/src/fields/ToggleButtonsField.ts +0 -59
  195. package/src/fields/ToggleField.ts +0 -16
  196. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  197. package/src/fields/optionsResolver.ts +0 -95
  198. package/src/fields/resolveField.ts +0 -28
  199. package/src/filters/BooleanFilter.ts +0 -35
  200. package/src/filters/DateRangeFilter.test.ts +0 -194
  201. package/src/filters/DateRangeFilter.ts +0 -148
  202. package/src/filters/Filter.test.ts +0 -268
  203. package/src/filters/Filter.ts +0 -184
  204. package/src/filters/FormFilter.test.ts +0 -238
  205. package/src/filters/FormFilter.ts +0 -215
  206. package/src/filters/MultiSelectFilter.test.ts +0 -119
  207. package/src/filters/MultiSelectFilter.ts +0 -78
  208. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  209. package/src/filters/QueryBuilderFilter.ts +0 -398
  210. package/src/filters/SelectFilter.ts +0 -46
  211. package/src/filters/TernaryFilter.test.ts +0 -160
  212. package/src/filters/TernaryFilter.ts +0 -72
  213. package/src/filters/TrashedFilter.test.ts +0 -149
  214. package/src/filters/TrashedFilter.ts +0 -55
  215. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  216. package/src/filters/queryBuilder/Constraint.ts +0 -115
  217. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  218. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  219. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  220. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  221. package/src/filters/queryBuilder/index.ts +0 -12
  222. package/src/icons/index.ts +0 -2
  223. package/src/icons/lucide.ts +0 -204
  224. package/src/icons/registry.test.ts +0 -56
  225. package/src/icons/registry.ts +0 -41
  226. package/src/icons/types.ts +0 -47
  227. package/src/index.ts +0 -525
  228. package/src/io/csv.test.ts +0 -142
  229. package/src/io/csv.ts +0 -170
  230. package/src/nestedRelationManagerData.test.ts +0 -547
  231. package/src/notifications/Notification.test.ts +0 -210
  232. package/src/notifications/Notification.ts +0 -354
  233. package/src/notifications/broadcast.test.ts +0 -110
  234. package/src/notifications/broadcast.ts +0 -95
  235. package/src/notifications/database.test.ts +0 -383
  236. package/src/notifications/database.ts +0 -398
  237. package/src/notifications/databaseNotifications.test.ts +0 -187
  238. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  239. package/src/notifications/dispatchNotificationAction.ts +0 -142
  240. package/src/notifications/flash.test.ts +0 -89
  241. package/src/notifications/flash.ts +0 -71
  242. package/src/notifications/index.ts +0 -45
  243. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  244. package/src/notifications/registerBroadcastAuth.ts +0 -100
  245. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  246. package/src/notifications/resolveSavedNotification.ts +0 -59
  247. package/src/notifications/types.ts +0 -93
  248. package/src/orm/m2mAccessor.ts +0 -66
  249. package/src/orm/modelDefaults.test.ts +0 -633
  250. package/src/orm/modelDefaults.ts +0 -666
  251. package/src/pageData/breadcrumbs.ts +0 -288
  252. package/src/pageData/forms.ts +0 -578
  253. package/src/pageData/helpers.ts +0 -857
  254. package/src/pageData/misc.ts +0 -347
  255. package/src/pageData/navigation.ts +0 -842
  256. package/src/pageData/relationPages.ts +0 -1248
  257. package/src/pageData/relationTabs.ts +0 -286
  258. package/src/pageData/resourcePages.ts +0 -609
  259. package/src/pageData.test.ts +0 -1545
  260. package/src/pageData.ts +0 -341
  261. package/src/plugins/index.ts +0 -8
  262. package/src/plugins/themeEditor.test.ts +0 -36
  263. package/src/plugins/themeEditor.ts +0 -45
  264. package/src/react/AppShell.tsx +0 -251
  265. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  266. package/src/react/CollabRoomContext.ts +0 -98
  267. package/src/react/CollabTextRendererRegistry.ts +0 -102
  268. package/src/react/CommandPalette.tsx +0 -375
  269. package/src/react/CurrentUserContext.tsx +0 -50
  270. package/src/react/CustomPageWrapperGate.tsx +0 -69
  271. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  272. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  273. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  274. package/src/react/FieldPresenceRegistry.ts +0 -46
  275. package/src/react/FormCollabBindingRegistry.ts +0 -242
  276. package/src/react/FormStateContext.tsx +0 -591
  277. package/src/react/HeadHooks.tsx +0 -126
  278. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  279. package/src/react/MarkdownEditorRegistry.ts +0 -107
  280. package/src/react/NotificationActionStrip.tsx +0 -263
  281. package/src/react/NotificationBell.tsx +0 -426
  282. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  283. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  284. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  285. package/src/react/PendingSuggestionsContext.tsx +0 -172
  286. package/src/react/RecordWrapperGate.tsx +0 -58
  287. package/src/react/RecordWrapperRegistry.ts +0 -39
  288. package/src/react/RenderHookSlot.tsx +0 -32
  289. package/src/react/RightSidebar.tsx +0 -257
  290. package/src/react/RightSidebarContext.tsx +0 -234
  291. package/src/react/RightSidebarTrigger.tsx +0 -53
  292. package/src/react/RowCoordsContext.tsx +0 -23
  293. package/src/react/SchemaRenderer.tsx +0 -549
  294. package/src/react/SearchTrigger.tsx +0 -46
  295. package/src/react/ThemeProvider.tsx +0 -93
  296. package/src/react/ThemeSettingsPage.tsx +0 -579
  297. package/src/react/ThemeToggle.tsx +0 -20
  298. package/src/react/Toaster.tsx +0 -158
  299. package/src/react/UserMenu.tsx +0 -196
  300. package/src/react/WidgetDataContext.tsx +0 -157
  301. package/src/react/cells/EditableCell.tsx +0 -389
  302. package/src/react/component-slots.test.ts +0 -103
  303. package/src/react/component-slots.ts +0 -116
  304. package/src/react/fieldJsHandler.test.ts +0 -166
  305. package/src/react/fieldJsHandler.ts +0 -79
  306. package/src/react/fields/BuilderInput.tsx +0 -1078
  307. package/src/react/fields/CheckboxInput.tsx +0 -39
  308. package/src/react/fields/CheckboxListInput.tsx +0 -102
  309. package/src/react/fields/ColorInput.tsx +0 -71
  310. package/src/react/fields/DateFieldInput.tsx +0 -70
  311. package/src/react/fields/DateTimeInput.tsx +0 -62
  312. package/src/react/fields/FieldShell.tsx +0 -348
  313. package/src/react/fields/FileUploadInput.tsx +0 -639
  314. package/src/react/fields/HiddenInput.tsx +0 -17
  315. package/src/react/fields/KeyValueInput.tsx +0 -230
  316. package/src/react/fields/MarkdownInput.tsx +0 -560
  317. package/src/react/fields/RadioInput.tsx +0 -81
  318. package/src/react/fields/RepeaterInput.test.ts +0 -116
  319. package/src/react/fields/RepeaterInput.tsx +0 -1420
  320. package/src/react/fields/SelectFieldInput.tsx +0 -280
  321. package/src/react/fields/SliderInput.tsx +0 -81
  322. package/src/react/fields/TagsInput.tsx +0 -283
  323. package/src/react/fields/TextLikeInput.tsx +0 -256
  324. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  325. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  326. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  327. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  328. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  329. package/src/react/fields/repeaterReconcile.ts +0 -104
  330. package/src/react/fields/rowChromeButton.tsx +0 -336
  331. package/src/react/fields/rowState.ts +0 -106
  332. package/src/react/fields/syncRowGates.test.ts +0 -202
  333. package/src/react/fields/syncRowGates.ts +0 -66
  334. package/src/react/fields/textInputControls.tsx +0 -238
  335. package/src/react/fields/useRowReorderDnd.ts +0 -78
  336. package/src/react/formStateHelpers.test.ts +0 -508
  337. package/src/react/formStateHelpers.ts +0 -381
  338. package/src/react/hooks/use-mobile.ts +0 -19
  339. package/src/react/icon-context.tsx +0 -60
  340. package/src/react/index.ts +0 -194
  341. package/src/react/layouts/SidebarLayout.tsx +0 -250
  342. package/src/react/layouts/TopbarLayout.tsx +0 -258
  343. package/src/react/navigate.tsx +0 -37
  344. package/src/react/onProviderSynced.test.ts +0 -90
  345. package/src/react/parseRecordEditUrl.test.ts +0 -122
  346. package/src/react/parseRecordEditUrl.ts +0 -94
  347. package/src/react/persistedState.ts +0 -40
  348. package/src/react/registry.ts +0 -48
  349. package/src/react/right-panel-registry.tsx +0 -47
  350. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  351. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  352. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  353. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  354. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  355. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  356. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  357. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  358. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  359. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  360. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  361. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  362. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  363. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  364. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  365. package/src/react/schemaRenderer/constants.ts +0 -50
  366. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  367. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  368. package/src/react/schemaRenderer/helpers.tsx +0 -81
  369. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  370. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  371. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  372. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  373. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  374. package/src/react/schemaRenderer/table/links.tsx +0 -112
  375. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  376. package/src/react/schemaRenderer/table/url.tsx +0 -143
  377. package/src/react/theme-preview/apply.ts +0 -99
  378. package/src/react/theme-preview/build-html.ts +0 -436
  379. package/src/react/ui/button.tsx +0 -51
  380. package/src/react/ui/calendar.tsx +0 -67
  381. package/src/react/ui/checkbox.tsx +0 -29
  382. package/src/react/ui/dialog.tsx +0 -108
  383. package/src/react/ui/dropdown-menu.tsx +0 -97
  384. package/src/react/ui/input.tsx +0 -20
  385. package/src/react/ui/label.tsx +0 -21
  386. package/src/react/ui/popover.tsx +0 -50
  387. package/src/react/ui/select.tsx +0 -169
  388. package/src/react/ui/separator.tsx +0 -25
  389. package/src/react/ui/sheet.tsx +0 -136
  390. package/src/react/ui/sidebar.tsx +0 -723
  391. package/src/react/ui/skeleton.tsx +0 -13
  392. package/src/react/ui/slider.tsx +0 -34
  393. package/src/react/ui/switch.tsx +0 -28
  394. package/src/react/ui/table.tsx +0 -105
  395. package/src/react/ui/tabs.tsx +0 -63
  396. package/src/react/ui/textarea.tsx +0 -18
  397. package/src/react/ui/tooltip.tsx +0 -64
  398. package/src/react/useResizableWidth.ts +0 -139
  399. package/src/react/utils.ts +0 -6
  400. package/src/react/widgetRegistry.test.ts +0 -43
  401. package/src/react/widgetRegistry.ts +0 -50
  402. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  403. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  404. package/src/react/widgets/ViewRenderer.tsx +0 -71
  405. package/src/relationManagerData.test.ts +0 -1595
  406. package/src/richtext/index.ts +0 -8
  407. package/src/richtext/registry.ts +0 -89
  408. package/src/routes/globals.ts +0 -148
  409. package/src/routes/guard.test.ts +0 -325
  410. package/src/routes/helpers.ts +0 -704
  411. package/src/routes/pages.ts +0 -175
  412. package/src/routes/panel.ts +0 -204
  413. package/src/routes/relations.ts +0 -1243
  414. package/src/routes/resources.ts +0 -781
  415. package/src/routes/theme.ts +0 -91
  416. package/src/routes-nested-relations.test.ts +0 -676
  417. package/src/routes-relations.test.ts +0 -972
  418. package/src/routes.test.ts +0 -2027
  419. package/src/routes.ts +0 -303
  420. package/src/schema/Alert.test.ts +0 -109
  421. package/src/schema/Alert.ts +0 -131
  422. package/src/schema/Block.ts +0 -169
  423. package/src/schema/Breadcrumbs.ts +0 -40
  424. package/src/schema/Card.ts +0 -35
  425. package/src/schema/Divider.ts +0 -20
  426. package/src/schema/Element.ts +0 -219
  427. package/src/schema/EmptyState.test.ts +0 -37
  428. package/src/schema/EmptyState.ts +0 -63
  429. package/src/schema/Fieldset.ts +0 -43
  430. package/src/schema/Grid.ts +0 -43
  431. package/src/schema/Group.ts +0 -30
  432. package/src/schema/Heading.ts +0 -39
  433. package/src/schema/Html.ts +0 -67
  434. package/src/schema/Icon.ts +0 -54
  435. package/src/schema/Image.ts +0 -57
  436. package/src/schema/LinkTag.ts +0 -41
  437. package/src/schema/Markdown.ts +0 -85
  438. package/src/schema/MetaTag.ts +0 -41
  439. package/src/schema/RelationTabs.ts +0 -71
  440. package/src/schema/ScriptTag.ts +0 -55
  441. package/src/schema/Section.ts +0 -160
  442. package/src/schema/ServerDataElement.test.ts +0 -140
  443. package/src/schema/ServerDataElement.ts +0 -156
  444. package/src/schema/SlotComponent.test.ts +0 -77
  445. package/src/schema/SlotComponent.ts +0 -71
  446. package/src/schema/Split.ts +0 -50
  447. package/src/schema/Stat.test.ts +0 -118
  448. package/src/schema/Stat.ts +0 -154
  449. package/src/schema/StatsOverview.test.ts +0 -141
  450. package/src/schema/StatsOverview.ts +0 -119
  451. package/src/schema/StyleTag.ts +0 -35
  452. package/src/schema/TableWidget.test.ts +0 -297
  453. package/src/schema/TableWidget.ts +0 -289
  454. package/src/schema/Tabs.ts +0 -79
  455. package/src/schema/Text.ts +0 -58
  456. package/src/schema/UnorderedList.ts +0 -49
  457. package/src/schema/View.test.ts +0 -111
  458. package/src/schema/View.ts +0 -127
  459. package/src/schema/Wizard.ts +0 -220
  460. package/src/schema/containers.test.ts +0 -564
  461. package/src/schema/headTags.test.ts +0 -134
  462. package/src/schema/index.ts +0 -40
  463. package/src/schema/primes.test.ts +0 -269
  464. package/src/schema/resolveSchema.test.ts +0 -379
  465. package/src/schema/resolveSchema.ts +0 -917
  466. package/src/schema/sanitize.ts +0 -58
  467. package/src/search.test.ts +0 -446
  468. package/src/search.ts +0 -178
  469. package/src/sessionFilters.test.ts +0 -375
  470. package/src/sessionFilters.ts +0 -143
  471. package/src/slot-components/index.ts +0 -10
  472. package/src/slot-components/registry.ts +0 -56
  473. package/src/styles/file-upload.css +0 -13
  474. package/src/summarizers/Summarizer.test.ts +0 -84
  475. package/src/summarizers/Summarizer.ts +0 -123
  476. package/src/summarizers/index.ts +0 -11
  477. package/src/theme/base-colors.ts +0 -68
  478. package/src/theme/chart-colors.ts +0 -50
  479. package/src/theme/colors.ts +0 -447
  480. package/src/theme/generate-css.test.ts +0 -139
  481. package/src/theme/generate-css.ts +0 -44
  482. package/src/theme/generate-scale.test.ts +0 -106
  483. package/src/theme/generate-scale.ts +0 -97
  484. package/src/theme/icon-map.ts +0 -42
  485. package/src/theme/index.ts +0 -34
  486. package/src/theme/migrate.test.ts +0 -178
  487. package/src/theme/migrate.ts +0 -81
  488. package/src/theme/presets.ts +0 -135
  489. package/src/theme/radius.ts +0 -18
  490. package/src/theme/resolve.test.ts +0 -238
  491. package/src/theme/resolve.ts +0 -96
  492. package/src/theme/spacing.ts +0 -18
  493. package/src/theme/storage.test.ts +0 -126
  494. package/src/theme/storage.ts +0 -106
  495. package/src/theme/theme-colors.ts +0 -88
  496. package/src/theme/types.ts +0 -125
  497. package/src/uploads/UploadAdapter.ts +0 -35
  498. package/src/uploads/index.ts +0 -2
  499. package/src/uploads/localUpload.test.ts +0 -70
  500. package/src/uploads/localUpload.ts +0 -84
  501. package/src/validation/Validator.ts +0 -49
  502. package/src/validation/index.ts +0 -28
  503. package/src/validation/rules.ts +0 -78
  504. package/src/validation/runValidators.ts +0 -435
  505. package/src/validation/uniqueValidator.test.ts +0 -196
  506. package/src/validation/uniqueValidator.ts +0 -133
  507. package/src/validation/validators.test.ts +0 -268
  508. package/src/vite.test.ts +0 -184
  509. package/src/vite.ts +0 -787
  510. package/src/widgets/index.ts +0 -10
  511. package/src/widgets/registry.ts +0 -45
  512. package/src/widgets.test.ts +0 -592
  513. package/tsconfig.build.json +0 -11
  514. package/tsconfig.json +0 -4
  515. package/tsconfig.test.json +0 -10
  516. package/views/react/Dashboard.tsx +0 -27
  517. package/views/react/Resources/Form.tsx +0 -102
  518. package/views/react/Resources/Index.tsx +0 -49
@@ -1,1078 +0,0 @@
1
- import React, { useContext, useEffect, useId, useMemo, useRef, useState } from 'react'
2
- import { ChevronDownIcon, PlusIcon } from 'lucide-react'
3
- import type { ElementMeta } from '../../schema/Element.js'
4
- import { Button } from '../ui/button.js'
5
- import { SchemaRenderer } from '../SchemaRenderer.js'
6
- import { FormIdContext, useFormState, useRowBinding } from '../FormStateContext.js'
7
- import { findFieldMeta } from '../formStateHelpers.js'
8
- import { RowCoordsContext } from '../RowCoordsContext.js'
9
- import { useIconFor } from '../icon-context.js'
10
- import { reorderRows, ExtraActionStrip, buildGridContainer } from './RepeaterInput.js'
11
- import { syncRowGates } from './syncRowGates.js'
12
- import { consumeReconcileFlag, computeReconcilePlan } from './repeaterReconcile.js'
13
- import type { RowButtonsMeta } from '../../fields/RowButton.js'
14
- import {
15
- RowChromeIconButton,
16
- ReorderGrip,
17
- CollapseChevron,
18
- BulkCollapseHeader,
19
- resolveRowChrome,
20
- DEFAULT_MOVE_UP,
21
- DEFAULT_MOVE_DOWN,
22
- DEFAULT_CLONE,
23
- DEFAULT_DELETE,
24
- } from './rowChromeButton.js'
25
- import {
26
- generateRowId, makeAccordionStorage, makeCollapsedStorage,
27
- } from './rowState.js'
28
- import { useRowReorderDnd } from './useRowReorderDnd.js'
29
-
30
- const collapsedStorage = makeCollapsedStorage('builder')
31
- const accordionStorage = makeAccordionStorage('builder')
32
- const initSeedCollapsed = collapsedStorage.seed
33
- const writeCollapsedToStorage = collapsedStorage.write
34
- const deleteCollapsedFromStorage = collapsedStorage.remove
35
- const readAccordionFromStorage = accordionStorage.read
36
- const writeAccordionToStorage = accordionStorage.write
37
-
38
- interface BlockShape {
39
- name: string
40
- label: string
41
- icon?: string
42
- columns?: number
43
- maxItems?: number
44
- template: ElementMeta[]
45
- }
46
-
47
- interface BuilderRowShape {
48
- id: string
49
- type: string
50
- children: ElementMeta[]
51
- itemLabel?: string
52
- hidden?: boolean
53
- unknownType?: boolean
54
- extraActions?: ElementMeta[]
55
- canDelete?: false
56
- canClone?: false
57
- canReorder?: false
58
- }
59
-
60
- interface BuilderMetaShape {
61
- rows?: BuilderRowShape[]
62
- blocks?: BlockShape[]
63
- minItems?: number
64
- maxItems?: number
65
- reorderable?: boolean
66
- reorderableWithButtons?: boolean
67
- collapsible?: boolean
68
- defaultCollapsed?: boolean
69
- accordion?: boolean
70
- cloneable?: boolean
71
- addable?: boolean
72
- deletable?: boolean
73
- addBetween?: boolean
74
- blockNumbers?: boolean
75
- itemNumbers?: boolean
76
- blockIcons?: boolean
77
- blockPickerColumns?: number
78
- addActionLabel?: string
79
- addActionAlignment?: 'start' | 'center' | 'end'
80
- defaultBlock?: string
81
- grid?: number
82
- buttons?: RowButtonsMeta
83
- }
84
-
85
- interface RowState {
86
- id: string
87
- type: string
88
- children: ElementMeta[]
89
- itemLabel?: string
90
- hidden?: boolean
91
- unknownType?: boolean
92
- extraActions?: ElementMeta[]
93
- // Per-row capability flags from `itemCan*(rule)`. Stamped only when the
94
- // rule resolved falsy server-side. See `RepeaterField`'s RowState for
95
- // the full contract — semantics are identical.
96
- canDelete?: false
97
- canClone?: false
98
- canReorder?: false
99
- }
100
-
101
- /**
102
- * Builder renderer (Plan #14 follow-up).
103
- *
104
- * Heterogeneous rows: each row's children come from one of the
105
- * registered blocks (picked at add-time). Per-row name prefixing emits
106
- * `{name}.{i}.data.{innerField}` for inner inputs, plus
107
- * `{name}.{i}.__id` and `{name}.{i}.type` hidden inputs so the row's
108
- * envelope round-trips through FormData.
109
- *
110
- * Mirrors `RepeaterInput`'s reorder / collapse / clone / inner-field
111
- * live-resolve plumbing — the array-row primitives (`reorderRows`)
112
- * are imported directly to keep the two fields' behavior aligned.
113
- */
114
- export function BuilderInput({
115
- el,
116
- name,
117
- disabled,
118
- }: {
119
- el: ElementMeta
120
- name: string
121
- disabled: boolean
122
- }): React.ReactElement {
123
- const formIdFromCtx = useContext(FormIdContext)
124
- const formId = formIdFromCtx || `builder-${name}`
125
- const meta = el as BuilderMetaShape
126
- const blocks = meta.blocks ?? []
127
- const blocksByName = useMemo(
128
- () => new Map(blocks.map(b => [b.name, b])),
129
- [blocks],
130
- )
131
- const minItems = typeof meta.minItems === 'number' ? meta.minItems : undefined
132
- const maxItems = typeof meta.maxItems === 'number' ? meta.maxItems : undefined
133
- const collapsible = Boolean(meta.collapsible)
134
- const defaultCollapsed = Boolean(meta.defaultCollapsed)
135
- const accordion = Boolean(meta.accordion)
136
- const reorderable = Boolean(meta.reorderable)
137
- const cloneable = Boolean(meta.cloneable)
138
- const addable = meta.addable !== false
139
- const deletable = meta.deletable !== false
140
- const addBetween = Boolean(meta.addBetween)
141
- const buttonsOnly = Boolean(meta.reorderableWithButtons)
142
- const showNumbers = Boolean(meta.blockNumbers || meta.itemNumbers)
143
- const showIcons = meta.blockIcons !== false
144
- const pickerColumns = typeof meta.blockPickerColumns === 'number' && meta.blockPickerColumns > 1
145
- ? meta.blockPickerColumns
146
- : 1
147
- const buttons = meta.buttons
148
- // Customizer wins over the legacy `addActionLabel`. Default 'Add block'
149
- // is the final fallback (mirrors `BuilderField.addActionLabel`).
150
- const addLabel = buttons?.add?.label
151
- ?? (typeof meta.addActionLabel === 'string' ? meta.addActionLabel : 'Add block')
152
- const addAlignment = meta.addActionAlignment ?? 'start'
153
- // Row-grid mode mirrors RepeaterField.grid() — n-column grid for the
154
- // ROWS themselves (distinct from per-block `Block.columns(n)` which
155
- // grids fields *inside* a block body). DnD drop indicator is
156
- // suppressed in grid mode (see RepeaterInput for the same caveat).
157
- // Accepts the scalar form (`grid(2)`) or a responsive object — see
158
- // `buildGridContainer` for the breakpoint mapping.
159
- const gridScopeId = useId()
160
- const gridContainer = useMemo(
161
- () => buildGridContainer(
162
- meta.grid as number | Record<string, number | undefined> | undefined,
163
- gridScopeId,
164
- ),
165
- [meta.grid, gridScopeId],
166
- )
167
-
168
- const initialRows: RowState[] = useMemo(
169
- () => (meta.rows ?? []).map(r => ({
170
- id: r.id,
171
- type: r.type,
172
- children: r.children,
173
- ...(r.itemLabel !== undefined ? { itemLabel: r.itemLabel } : {}),
174
- ...(r.hidden ? { hidden: true } : {}),
175
- ...(r.unknownType ? { unknownType: true } : {}),
176
- ...(r.extraActions && r.extraActions.length > 0 ? { extraActions: r.extraActions } : {}),
177
- ...(r.canDelete === false ? { canDelete: false as const } : {}),
178
- ...(r.canClone === false ? { canClone: false as const } : {}),
179
- ...(r.canReorder === false ? { canReorder: false as const } : {}),
180
- })),
181
- // eslint-disable-next-line react-hooks/exhaustive-deps
182
- [],
183
- )
184
- const [rows, setRows] = useState<RowState[]>(initialRows)
185
- const metaRows = meta.rows
186
- useEffect(() => {
187
- if (!metaRows) return
188
- setRows(prev => syncRowGates(prev, metaRows))
189
- }, [metaRows])
190
- // Phase F.5 — row-array CRDT binding (mirrors `RepeaterInput`). The
191
- // initial-row payload that lands on `add()` carries the block's `type`
192
- // alongside the empty field map so peers see the discriminator from
193
- // the first event — without it, the picker dropdown choice doesn't
194
- // propagate until the user makes their first inner-field edit.
195
- const rowBinding = useRowBinding(name)
196
- // Mirror row identities into the form's values map so dotted row-leaf
197
- // consumers can resolve the row's `__id` via `rowIdAtIndex(ctx.values,
198
- // name, i)`. Mirrors the same plumbing in RepeaterInput.
199
- const formStateForIds = useFormState()
200
- const ctxSetValue = formStateForIds?.setValue
201
- useEffect(() => {
202
- if (!ctxSetValue) return
203
- for (let i = 0; i < rows.length; i++) {
204
- const row = rows[i]
205
- if (!row) continue
206
- ctxSetValue(`${name}.${i}.__id`, row.id)
207
- }
208
- }, [rows, name, ctxSetValue])
209
- // Phase F.5 — reconcile remote row events. Builder mirrors
210
- // RepeaterInput but reads `event.values.type` to pick the block whose
211
- // template seeds the new row's children. Falls back to the first
212
- // registered block when the remote `type` doesn't match a known one;
213
- // the row still mounts so the user sees the change rather than a
214
- // silent drop (matches the server-side `unknownType` fallback).
215
- useEffect(() => {
216
- if (!rowBinding) return
217
- return rowBinding.subscribe((event) => {
218
- if (event.kind === 'add') {
219
- setRows((prev) => {
220
- if (prev.some(r => r.id === event.rowId)) return prev
221
- const blockType = typeof event.values['type'] === 'string'
222
- ? event.values['type'] as string
223
- : (meta.blocks?.[0]?.name ?? '')
224
- const block = blocksByName.get(blockType)
225
- const incoming: RowState = {
226
- id: event.rowId,
227
- type: blockType,
228
- children: block?.template ?? [],
229
- }
230
- const next = prev.slice()
231
- const at = Math.max(0, Math.min(event.index, next.length))
232
- next.splice(at, 0, incoming)
233
- return next
234
- })
235
- return
236
- }
237
- if (event.kind === 'remove') {
238
- setRows((prev) => {
239
- if (!prev.some(r => r.id === event.rowId)) return prev
240
- return prev.filter(r => r.id !== event.rowId)
241
- })
242
- return
243
- }
244
- setRows((prev) => {
245
- const fromIdx = prev.findIndex(r => r.id === event.rowId)
246
- if (fromIdx < 0) return prev
247
- if (fromIdx === event.to) return prev
248
- const next = prev.slice()
249
- const [moved] = next.splice(fromIdx, 1)
250
- if (!moved) return prev
251
- next.splice(event.to, 0, moved)
252
- return next
253
- })
254
- })
255
- }, [rowBinding, blocksByName, meta.blocks])
256
-
257
- // Phase A reconciliation for `Builder.relationship` PK-switch — mirrors
258
- // the effect in `RepeaterInput`. See its comment + the plan doc:
259
- // `pilotiq-pro/docs/plans/repeater-relationship-pk-switch.md`.
260
- useEffect(() => {
261
- if (!rowBinding) return
262
- if (!consumeReconcileFlag(formId)) return
263
- const timer = setTimeout(() => {
264
- const plan = computeReconcilePlan({
265
- current: rowBinding.current(),
266
- authoritative: initialRows.map(r => r.id),
267
- })
268
- for (const id of plan.toRemove) rowBinding.remove(id)
269
- // For Builder, the row carries a block `type` discriminator; seed
270
- // it on the add path so peers' picker dropdowns see the right
271
- // block (matches the existing add path in `addBlock`). The block
272
- // type comes from initialRows when the row is server-rendered.
273
- for (const id of plan.toAdd) {
274
- const row = initialRows.find(r => r.id === id)
275
- rowBinding.add(id, row?.type ? { type: row.type } : {})
276
- }
277
- }, 1500)
278
- return () => clearTimeout(timer)
279
- // eslint-disable-next-line react-hooks/exhaustive-deps
280
- }, [rowBinding, formId])
281
- const [collapsed, setCollapsed] = useState<Record<string, boolean>>(() =>
282
- accordion ? {} : initSeedCollapsed(initialRows, formId, name, defaultCollapsed, collapsible),
283
- )
284
- // Accordion mode: single open row id (null = all collapsed). See
285
- // RepeaterInput for the seeding semantics — Builder mirrors it exactly.
286
- const [accordionOpenId, setAccordionOpenId] = useState<string | null>(() => {
287
- if (!accordion) return null
288
- const stored = readAccordionFromStorage(formId, name)
289
- if (stored !== undefined) {
290
- if (stored === '' || initialRows.some(r => r.id === stored)) return stored === '' ? null : stored
291
- }
292
- if (defaultCollapsed) return null
293
- const firstVisible = initialRows.find(r => !r.hidden)
294
- return firstVisible?.id ?? null
295
- })
296
-
297
- const atMin = minItems !== undefined && rows.length <= minItems
298
- const atMax = maxItems !== undefined && rows.length >= maxItems
299
-
300
- // Per-block-type cap: greys out the picker option once the cap is
301
- // hit. Counted across ALL rows (including hidden ones — `itemHidden`
302
- // is purely UX, not a data filter).
303
- const typeCounts = useMemo(() => {
304
- const m = new Map<string, number>()
305
- for (const r of rows) m.set(r.type, (m.get(r.type) ?? 0) + 1)
306
- return m
307
- }, [rows])
308
-
309
- // `atIndex` (when defined) splices the new row at that position,
310
- // shifting existing rows down. Used by the inline `addBetween` zones;
311
- // bottom Add button leaves it undefined → append.
312
- const addRowOfType = (blockName: string, atIndex?: number): void => {
313
- if (atMax) return
314
- const block = blocksByName.get(blockName)
315
- if (!block) return
316
- const cap = block.maxItems
317
- if (cap !== undefined && (typeCounts.get(blockName) ?? 0) >= cap) return
318
- const newRow: RowState = {
319
- id: generateRowId(),
320
- type: block.name,
321
- children: block.template,
322
- }
323
- setRows(prev => {
324
- if (atIndex === undefined) return [...prev, newRow]
325
- const i = Math.max(0, Math.min(atIndex, prev.length))
326
- return [...prev.slice(0, i), newRow, ...prev.slice(i)]
327
- })
328
- // F.5 — seed the new block's discriminator on the CRDT side so peers
329
- // pick the right inner schema without waiting for the user's first edit.
330
- rowBinding?.add(newRow.id, { type: block.name })
331
- if (accordion) {
332
- setAccordionOpenId(newRow.id)
333
- writeAccordionToStorage(formId, name, newRow.id)
334
- return
335
- }
336
- if (collapsible && defaultCollapsed) {
337
- setCollapsed(prev => ({ ...prev, [newRow.id]: true }))
338
- writeCollapsedToStorage(formId, name, newRow.id, true)
339
- }
340
- }
341
-
342
- const removeRow = (id: string): void => {
343
- if (atMin) return
344
- setRows(prev => prev.filter(r => r.id !== id))
345
- rowBinding?.remove(id)
346
- if (accordion) {
347
- if (accordionOpenId === id) {
348
- setAccordionOpenId(null)
349
- writeAccordionToStorage(formId, name, null)
350
- }
351
- return
352
- }
353
- setCollapsed(prev => {
354
- const { [id]: _drop, ...rest } = prev
355
- return rest
356
- })
357
- deleteCollapsedFromStorage(formId, name, id)
358
- }
359
-
360
- const cloneRow = (id: string): void => {
361
- if (atMax) return
362
- let cloneId: string | null = null
363
- let cloneType: string | null = null
364
- setRows(prev => {
365
- const idx = prev.findIndex(r => r.id === id)
366
- if (idx < 0) return prev
367
- const source = prev[idx]!
368
- // Block.maxItems applies to clones too.
369
- const block = blocksByName.get(source.type)
370
- const cap = block?.maxItems
371
- if (cap !== undefined && (typeCounts.get(source.type) ?? 0) >= cap) return prev
372
- cloneId = generateRowId()
373
- cloneType = source.type
374
- const clone: RowState = {
375
- id: cloneId,
376
- type: source.type,
377
- children: source.children,
378
- ...(source.itemLabel !== undefined ? { itemLabel: source.itemLabel } : {}),
379
- }
380
- const next = prev.slice()
381
- next.splice(idx + 1, 0, clone)
382
- return next
383
- })
384
- if (cloneId !== null && cloneType !== null) {
385
- rowBinding?.add(cloneId, { type: cloneType })
386
- }
387
- }
388
-
389
- const moveRow = (id: string, dir: -1 | 1): void => {
390
- const idx = rows.findIndex(r => r.id === id)
391
- if (idx < 0) return
392
- let next: RowState[]
393
- if (dir === -1) {
394
- let target = idx - 1
395
- while (target >= 0 && rows[target]?.hidden) target--
396
- if (target < 0) return
397
- next = reorderRows(rows, idx, target)
398
- } else {
399
- let target = idx + 1
400
- while (target < rows.length && rows[target]?.hidden) target++
401
- if (target >= rows.length) return
402
- next = reorderRows(rows, idx, target + 1)
403
- }
404
- if (next === rows) return
405
- setRows(next)
406
- rowBinding?.reorder(next.map(r => r.id))
407
- }
408
-
409
- // ── DnD state (skipped when buttonsOnly) ────────────────
410
- const dndEnabled = reorderable && !buttonsOnly && !disabled
411
- const {
412
- dragId, dropAt,
413
- onDragStart: onRowDragStart,
414
- onDragOver: onRowDragOver,
415
- onDrop: onRowDrop,
416
- onDragEnd: onRowDragEnd,
417
- } = useRowReorderDnd({
418
- enabled: dndEnabled,
419
- onDrop: (fromId, at) => {
420
- // See RepeaterInput's matching onDrop comment — closure-mutation
421
- // through setRows's updater is unreliable when other state updates
422
- // are batched (useRowReorderDnd nulls dragId/dropAt right before
423
- // calling this).
424
- const fromIdx = rows.findIndex(r => r.id === fromId)
425
- if (fromIdx < 0) return
426
- const next = reorderRows(rows, fromIdx, at)
427
- if (next === rows) return
428
- setRows(next)
429
- rowBinding?.reorder(next.map(r => r.id))
430
- },
431
- })
432
-
433
- // ── Inner-field live re-resolve (mirrors RepeaterInput) ─
434
- const formState = useFormState()
435
- const fireLive = (n: string, value: string, eventKind: 'change' | 'blur'): void => {
436
- if (!formState) return
437
- if (!n.includes('.')) return
438
- const fieldMeta = findFieldMeta(formState.formMeta, n)
439
- const liveCfg = fieldMeta?.['live']
440
- const hasJs = (fieldMeta as { afterStateUpdatedJs?: string } | undefined)?.afterStateUpdatedJs !== undefined
441
- if (!liveCfg && !hasJs) return
442
- if (liveCfg) {
443
- const onBlurMode = typeof liveCfg === 'object' && liveCfg !== null
444
- && (liveCfg as { onBlur?: boolean }).onBlur === true
445
- if (eventKind === 'change' && onBlurMode) return
446
- if (eventKind === 'blur' && !onBlurMode) return
447
- } else {
448
- if (eventKind === 'blur') return
449
- }
450
- formState.triggerLive(n, value)
451
- }
452
- const onContainerChange = (e: React.ChangeEvent<HTMLDivElement>): void => {
453
- const t = e.target as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
454
- if (!t.name) return
455
- fireLive(t.name, t.value, 'change')
456
- }
457
- const onContainerBlur = (e: React.FocusEvent<HTMLDivElement>): void => {
458
- const t = e.target as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
459
- if (!t.name) return
460
- fireLive(t.name, t.value, 'blur')
461
- }
462
-
463
- const toggleCollapsed = (id: string): void => {
464
- if (accordion) {
465
- const next = accordionOpenId === id ? null : id
466
- setAccordionOpenId(next)
467
- writeAccordionToStorage(formId, name, next)
468
- return
469
- }
470
- setCollapsed(prev => {
471
- const nextValue = !prev[id]
472
- writeCollapsedToStorage(formId, name, id, nextValue)
473
- return { ...prev, [id]: nextValue }
474
- })
475
- }
476
-
477
- // Bulk expand / collapse — accordion preserves its "only one open"
478
- // invariant by opening the first visible row on expandAll and clearing
479
- // openId on collapseAll. Per-row mode iterates every row and writes
480
- // the storage slot so reload restores the bulk state.
481
- const expandAll = (): void => {
482
- if (accordion) {
483
- const firstVisible = rows.find(r => !r.hidden)
484
- const next = firstVisible?.id ?? null
485
- setAccordionOpenId(next)
486
- writeAccordionToStorage(formId, name, next)
487
- return
488
- }
489
- setCollapsed({})
490
- for (const r of rows) writeCollapsedToStorage(formId, name, r.id, false)
491
- }
492
- const collapseAll = (): void => {
493
- if (accordion) {
494
- setAccordionOpenId(null)
495
- writeAccordionToStorage(formId, name, null)
496
- return
497
- }
498
- const next: Record<string, boolean> = {}
499
- for (const r of rows) {
500
- next[r.id] = true
501
- writeCollapsedToStorage(formId, name, r.id, true)
502
- }
503
- setCollapsed(next)
504
- }
505
-
506
- const hasVisibleRow = rows.some(r => !r.hidden)
507
- const firstVisibleIdx = rows.findIndex(r => !r.hidden)
508
- const lastVisibleIdx = (() => {
509
- for (let i = rows.length - 1; i >= 0; i--) if (!rows[i]?.hidden) return i
510
- return -1
511
- })()
512
-
513
- const addAlignClass = addAlignment === 'center'
514
- ? 'self-center'
515
- : addAlignment === 'end'
516
- ? 'self-end'
517
- : 'self-start'
518
-
519
- return (
520
- <div
521
- className="flex flex-col gap-3"
522
- style={gridContainer.wrapperStyle}
523
- onChange={onContainerChange}
524
- onBlur={onContainerBlur}
525
- >
526
- <BulkCollapseHeader
527
- buttons={buttons}
528
- disabled={disabled || !hasVisibleRow}
529
- onExpandAll={expandAll}
530
- onCollapseAll={collapseAll}
531
- />
532
-
533
- {!hasVisibleRow && (
534
- <div className="rounded-md border border-dashed px-4 py-6 text-center text-sm text-muted-foreground">
535
- No items yet. Click {addLabel} to start.
536
- </div>
537
- )}
538
-
539
- {gridContainer.styleBlock}
540
- <div
541
- className={gridContainer.className}
542
- style={gridContainer.style}
543
- >
544
- {rows.map((row, i) => (
545
- <React.Fragment key={row.id}>
546
- {!row.hidden && dropAt === i && !gridContainer.hasGrid && <DropIndicator />}
547
- {!row.hidden && addBetween && addable && blocks.length > 0 && !gridContainer.hasGrid && (
548
- <BetweenInserter
549
- blocks={blocks}
550
- typeCounts={typeCounts}
551
- atMax={atMax}
552
- disabled={disabled}
553
- columns={pickerColumns}
554
- onPick={(blockName) => addRowOfType(blockName, i)}
555
- />
556
- )}
557
- <BuilderRow
558
- row={row}
559
- block={blocksByName.get(row.type)}
560
- index={i}
561
- isFirstVisible={i === firstVisibleIdx}
562
- isLastVisible={i === lastVisibleIdx}
563
- name={name}
564
- disabled={disabled}
565
- collapsible={collapsible}
566
- isCollapsed={collapsible && (
567
- accordion
568
- ? accordionOpenId !== row.id
569
- : (collapsed[row.id] ?? false)
570
- )}
571
- reorderable={reorderable}
572
- buttonsOnly={buttonsOnly}
573
- cloneable={cloneable}
574
- deletable={deletable}
575
- atMin={atMin}
576
- atMax={atMax}
577
- showNumbers={showNumbers}
578
- showIcons={showIcons}
579
- buttons={buttons}
580
- isDragging={dragId === row.id}
581
- rowPath={`${name}.${i}`}
582
- onMoveUp={() => moveRow(row.id, -1)}
583
- onMoveDown={() => moveRow(row.id, 1)}
584
- onClone={() => cloneRow(row.id)}
585
- onRemove={() => removeRow(row.id)}
586
- onToggleCollapse={() => toggleCollapsed(row.id)}
587
- onDragStart={onRowDragStart(row.id)}
588
- onDragOver={onRowDragOver(i)}
589
- onDrop={onRowDrop}
590
- onDragEnd={onRowDragEnd}
591
- />
592
- </React.Fragment>
593
- ))}
594
- {dropAt === rows.length && !gridContainer.hasGrid && <DropIndicator />}
595
- </div>
596
-
597
- {addable && blocks.length > 0 && (
598
- <BlockPicker
599
- blocks={blocks}
600
- typeCounts={typeCounts}
601
- atMax={atMax}
602
- disabled={disabled}
603
- label={addLabel}
604
- buttons={buttons}
605
- alignClass={addAlignClass}
606
- columns={pickerColumns}
607
- onPick={addRowOfType}
608
- />
609
- )}
610
- </div>
611
- )
612
- }
613
-
614
- // ─── Block picker dropdown ──────────────────────────────────
615
-
616
- function BlockPicker({
617
- blocks, typeCounts, atMax, disabled, label, buttons, alignClass, columns, onPick,
618
- }: {
619
- blocks: BlockShape[]
620
- typeCounts: Map<string, number>
621
- atMax: boolean
622
- disabled: boolean
623
- label: string
624
- buttons: RowButtonsMeta | undefined
625
- alignClass: string
626
- columns: number
627
- onPick: (blockName: string) => void
628
- }): React.ReactElement {
629
- // Resolve customizer overrides (icon + tooltip) for the bottom Add
630
- // button. Color is intentionally ignored to preserve the outline-button
631
- // visual identity (use a header `Action.color()` if you need a tinted
632
- // chrome elsewhere). Label was already pre-resolved upstream.
633
- const { Icon: AddIcon, tooltip: addTooltip } = resolveRowChrome(
634
- { Icon: PlusIcon, label, tooltip: '', colorClass: '' },
635
- buttons?.add,
636
- )
637
- const [open, setOpen] = useState(false)
638
- const containerRef = useRef<HTMLDivElement>(null)
639
-
640
- // Close on outside click / Escape — keeps the picker UX out of the
641
- // way without pulling in a Popover dependency.
642
- useEffect(() => {
643
- if (!open) return
644
- const onDocPointerDown = (e: PointerEvent): void => {
645
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
646
- setOpen(false)
647
- }
648
- }
649
- const onDocKey = (e: KeyboardEvent): void => {
650
- if (e.key === 'Escape') setOpen(false)
651
- }
652
- document.addEventListener('pointerdown', onDocPointerDown)
653
- document.addEventListener('keydown', onDocKey)
654
- return () => {
655
- document.removeEventListener('pointerdown', onDocPointerDown)
656
- document.removeEventListener('keydown', onDocKey)
657
- }
658
- }, [open])
659
-
660
- // Single-block shortcut — skip the dropdown entirely.
661
- if (blocks.length === 1) {
662
- const only = blocks[0]!
663
- const onlyAtCap = only.maxItems !== undefined && (typeCounts.get(only.name) ?? 0) >= only.maxItems
664
- return (
665
- <Button
666
- type="button"
667
- variant="outline"
668
- size="sm"
669
- onClick={() => onPick(only.name)}
670
- disabled={disabled || atMax || onlyAtCap}
671
- title={addTooltip || undefined}
672
- className={alignClass}
673
- >
674
- <AddIcon className="size-4" />
675
- {label}
676
- </Button>
677
- )
678
- }
679
-
680
- return (
681
- <div ref={containerRef} className={`relative ${alignClass}`}>
682
- <Button
683
- type="button"
684
- variant="outline"
685
- size="sm"
686
- onClick={() => setOpen(o => !o)}
687
- disabled={disabled || atMax}
688
- title={addTooltip || undefined}
689
- aria-haspopup="menu"
690
- aria-expanded={open}
691
- >
692
- <AddIcon className="size-4" />
693
- {label}
694
- <ChevronDownIcon className="size-3 opacity-50" />
695
- </Button>
696
- {open && (
697
- <div
698
- role="menu"
699
- className="absolute z-20 mt-2 min-w-[12rem] rounded-md border bg-popover p-1 shadow-md"
700
- >
701
- <div
702
- className={columns > 1 ? 'grid gap-1' : 'flex flex-col gap-0.5'}
703
- style={columns > 1 ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : undefined}
704
- >
705
- {blocks.map(b => {
706
- const atTypeCap = b.maxItems !== undefined && (typeCounts.get(b.name) ?? 0) >= b.maxItems
707
- return (
708
- <BlockPickerItem
709
- key={b.name}
710
- block={b}
711
- disabled={atTypeCap}
712
- onPick={() => { onPick(b.name); setOpen(false) }}
713
- />
714
- )
715
- })}
716
- </div>
717
- </div>
718
- )}
719
- </div>
720
- )
721
- }
722
-
723
- function BlockPickerItem({
724
- block, disabled, onPick,
725
- }: {
726
- block: BlockShape
727
- disabled: boolean
728
- onPick: () => void
729
- }): React.ReactElement {
730
- const Icon = useIconFor(block.icon)
731
- return (
732
- <button
733
- type="button"
734
- role="menuitem"
735
- onClick={onPick}
736
- disabled={disabled}
737
- className="flex items-center gap-2 rounded px-2 py-1.5 text-left text-sm hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50"
738
- >
739
- {Icon && <Icon className="size-4 shrink-0 text-muted-foreground" />}
740
- <span className="truncate">{block.label}</span>
741
- </button>
742
- )
743
- }
744
-
745
- // ─── Inline insert-between zone (Builder.addBetween) ────────
746
- //
747
- // Hairline horizontal "+" button that lives between rows. Hidden
748
- // (opacity-0) until hovered or focused so the row stack stays calm,
749
- // then surfaces on hover. Clicking opens a compact picker rooted at
750
- // the inserter — the same `BlockPickerItem` shape as the bottom Add
751
- // button. When only one block is registered, clicking the line
752
- // inserts directly without a dropdown.
753
- //
754
- // Insertion index is owned by the parent — the inserter just calls
755
- // `onPick(blockName)` and the caller splices at the right place.
756
-
757
- function BetweenInserter({
758
- blocks, typeCounts, atMax, disabled, columns, onPick,
759
- }: {
760
- blocks: BlockShape[]
761
- typeCounts: Map<string, number>
762
- atMax: boolean
763
- disabled: boolean
764
- columns: number
765
- onPick: (blockName: string) => void
766
- }): React.ReactElement | null {
767
- const [open, setOpen] = useState(false)
768
- const containerRef = useRef<HTMLDivElement>(null)
769
-
770
- useEffect(() => {
771
- if (!open) return
772
- const onDocPointerDown = (e: PointerEvent): void => {
773
- if (containerRef.current && !containerRef.current.contains(e.target as Node)) {
774
- setOpen(false)
775
- }
776
- }
777
- const onDocKey = (e: KeyboardEvent): void => {
778
- if (e.key === 'Escape') setOpen(false)
779
- }
780
- document.addEventListener('pointerdown', onDocPointerDown)
781
- document.addEventListener('keydown', onDocKey)
782
- return () => {
783
- document.removeEventListener('pointerdown', onDocPointerDown)
784
- document.removeEventListener('keydown', onDocKey)
785
- }
786
- }, [open])
787
-
788
- if (blocks.length === 0) return null
789
-
790
- const isDisabled = disabled || atMax
791
-
792
- return (
793
- <div ref={containerRef} className="relative -my-1 flex justify-center">
794
- <button
795
- type="button"
796
- onClick={() => {
797
- if (isDisabled) return
798
- // Single-block shortcut — skip the dropdown.
799
- if (blocks.length === 1) {
800
- const only = blocks[0]!
801
- const onlyAtCap = only.maxItems !== undefined && (typeCounts.get(only.name) ?? 0) >= only.maxItems
802
- if (onlyAtCap) return
803
- onPick(only.name)
804
- return
805
- }
806
- setOpen(o => !o)
807
- }}
808
- disabled={isDisabled}
809
- aria-label="Insert block here"
810
- aria-haspopup={blocks.length > 1 ? 'menu' : undefined}
811
- aria-expanded={blocks.length > 1 ? open : undefined}
812
- className="group/inserter flex h-4 w-full items-center justify-center opacity-0 hover:opacity-100 focus-visible:opacity-100 transition-opacity disabled:pointer-events-none"
813
- >
814
- <span className="flex h-px w-full items-center bg-border group-hover/inserter:bg-primary group-focus-visible/inserter:bg-primary transition-colors">
815
- <span className="mx-auto flex size-5 items-center justify-center rounded-full border border-primary bg-background text-primary">
816
- <PlusIcon className="size-3" />
817
- </span>
818
- </span>
819
- </button>
820
- {open && blocks.length > 1 && (
821
- <div
822
- role="menu"
823
- className="absolute left-1/2 top-full z-20 mt-1 min-w-[12rem] -translate-x-1/2 rounded-md border bg-popover p-1 shadow-md"
824
- >
825
- <div
826
- className={columns > 1 ? 'grid gap-1' : 'flex flex-col gap-0.5'}
827
- style={columns > 1 ? { gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` } : undefined}
828
- >
829
- {blocks.map(b => {
830
- const atTypeCap = b.maxItems !== undefined && (typeCounts.get(b.name) ?? 0) >= b.maxItems
831
- return (
832
- <BlockPickerItem
833
- key={b.name}
834
- block={b}
835
- disabled={atTypeCap}
836
- onPick={() => { onPick(b.name); setOpen(false) }}
837
- />
838
- )
839
- })}
840
- </div>
841
- </div>
842
- )}
843
- </div>
844
- )
845
- }
846
-
847
- // ─── Row ────────────────────────────────────────────────────
848
-
849
- function BuilderRow({
850
- row, block, index, isFirstVisible, isLastVisible, name, disabled,
851
- collapsible, isCollapsed, reorderable, buttonsOnly, cloneable, deletable,
852
- atMin, atMax, showNumbers, showIcons, buttons, isDragging,
853
- rowPath,
854
- onMoveUp, onMoveDown, onClone, onRemove, onToggleCollapse,
855
- onDragStart, onDragOver, onDrop, onDragEnd,
856
- }: {
857
- row: RowState
858
- block: BlockShape | undefined
859
- index: number
860
- isFirstVisible: boolean
861
- isLastVisible: boolean
862
- name: string
863
- disabled: boolean
864
- collapsible: boolean
865
- isCollapsed: boolean
866
- reorderable: boolean
867
- buttonsOnly: boolean
868
- cloneable: boolean
869
- deletable: boolean
870
- atMin: boolean
871
- atMax: boolean
872
- showNumbers: boolean
873
- showIcons: boolean
874
- buttons: RowButtonsMeta | undefined
875
- isDragging: boolean
876
- rowPath: string
877
- onMoveUp: () => void
878
- onMoveDown: () => void
879
- onClone: () => void
880
- onRemove: () => void
881
- onToggleCollapse: () => void
882
- onDragStart: (e: React.DragEvent<HTMLElement>) => void
883
- onDragOver: (e: React.DragEvent<HTMLElement>) => void
884
- onDrop: (e: React.DragEvent<HTMLElement>) => void
885
- onDragEnd: (e: React.DragEvent<HTMLElement>) => void
886
- }): React.ReactElement {
887
- // Inner inputs sit under `name.<i>.data.*` so the {type, data}
888
- // envelope round-trips through FormData. Hidden envelope inputs
889
- // (`__id` and `type`) get their own siblings at `name.<i>.__id` and
890
- // `name.<i>.type`.
891
- const dataPrefix = `${name}.${index}.data`
892
- const namespaced = useMemo(
893
- () => row.children.map(c => prefixFieldNames(c, dataPrefix)),
894
- [row.children, dataPrefix],
895
- )
896
- // Row coords for dotted-path text leaves under this row — composes
897
- // fragment-key `${arrayName}.${rowId}.${fieldName}` (Phase 1 of
898
- // collab-row-text-tiptap-backed.md). `parseRowFieldPath` strips the
899
- // Builder-specific `data` segment, so the coords use the array name +
900
- // the row's stable id without referencing the dialect.
901
- const rowCoords = useMemo(
902
- () => ({ arrayName: name, rowIndex: index, rowId: row.id }),
903
- [name, index, row.id],
904
- )
905
-
906
- const RowIcon = useIconFor(showIcons ? block?.icon : undefined)
907
- const blockLabel = block?.label ?? row.type ?? 'Block'
908
- const numberPrefix = showNumbers ? `${index + 1}. ` : ''
909
- const headerLabel = row.itemLabel ?? `${numberPrefix}${blockLabel}`
910
-
911
- if (row.hidden) {
912
- return (
913
- <RowCoordsContext.Provider value={rowCoords}>
914
- <div style={{ display: 'none' }} data-pilotiq-builder-row="hidden">
915
- <input type="hidden" name={`${name}.${index}.__id`} value={row.id} readOnly />
916
- <input type="hidden" name={`${name}.${index}.type`} value={row.type} readOnly />
917
- <SchemaRenderer elements={namespaced} />
918
- </div>
919
- </RowCoordsContext.Provider>
920
- )
921
- }
922
-
923
- if (row.unknownType || !block) {
924
- // Stale block type — keep envelope round-tripping but show a clear
925
- // placeholder. Values inside `data` aren't editable here (the
926
- // schema is gone), but they survive submit because the server
927
- // passes them through verbatim.
928
- return (
929
- <div className="rounded-md border border-dashed bg-muted/30 px-3 py-2 text-sm text-muted-foreground">
930
- <input type="hidden" name={`${name}.${index}.__id`} value={row.id} readOnly />
931
- <input type="hidden" name={`${name}.${index}.type`} value={row.type} readOnly />
932
- Unknown block type "{row.type}". Block was removed from the schema —
933
- save will preserve the row's data unchanged.
934
- </div>
935
- )
936
- }
937
-
938
- // Per-row capability gates — `itemCan*(rule)` server-resolved.
939
- // Composes with the global `deletable / cloneable / reorderable` flags:
940
- // a per-row gate can only narrow what the global flag allows.
941
- const canDelete = row.canDelete !== false
942
- const canClone = row.canClone !== false
943
- const canReorder = row.canReorder !== false
944
-
945
- // Drag source on the grip `<span>`, drop target on the row container.
946
- // See RepeaterInput's RepeaterRow for the rationale (lets the row body
947
- // host a Tiptap contenteditable without losing reorder).
948
- const rowRef = useRef<HTMLDivElement>(null)
949
- const dragEnabled = reorderable && !buttonsOnly && !disabled && canReorder
950
- const containerDropTargetProps = dragEnabled
951
- ? { onDragOver, onDrop, onDragEnd }
952
- : {}
953
- const gripDragHandleProps = dragEnabled
954
- ? {
955
- draggable: true as const,
956
- onDragStart: (e: React.DragEvent<HTMLElement>): void => {
957
- if (rowRef.current) e.dataTransfer.setDragImage(rowRef.current, 0, 0)
958
- onDragStart(e)
959
- },
960
- }
961
- : undefined
962
-
963
- const innerColumns = block.columns && block.columns > 1 ? block.columns : 1
964
-
965
- return (
966
- <RowCoordsContext.Provider value={rowCoords}>
967
- <div
968
- ref={rowRef}
969
- className={`rounded-md border bg-card transition-opacity ${isDragging ? 'opacity-50' : ''}`}
970
- data-pilotiq-builder-row=""
971
- {...containerDropTargetProps}
972
- >
973
- <div className="flex items-center gap-2 border-b px-3 py-2">
974
- {reorderable && !buttonsOnly && canReorder && (
975
- <ReorderGrip disabled={disabled} buttons={buttons} dragHandleProps={gripDragHandleProps} />
976
- )}
977
- {collapsible && (
978
- <CollapseChevron
979
- isCollapsed={isCollapsed}
980
- disabled={disabled}
981
- buttons={buttons}
982
- onToggle={onToggleCollapse}
983
- />
984
- )}
985
- {RowIcon && <RowIcon className="size-4 shrink-0 text-muted-foreground" />}
986
- <span className="flex-1 truncate text-sm font-medium">{headerLabel}</span>
987
- <input type="hidden" name={`${name}.${index}.__id`} value={row.id} readOnly />
988
- <input type="hidden" name={`${name}.${index}.type`} value={row.type} readOnly />
989
- {reorderable && canReorder && (
990
- <>
991
- <RowChromeIconButton
992
- defaults={DEFAULT_MOVE_UP}
993
- override={buttons?.moveUp}
994
- disabled={disabled || isFirstVisible}
995
- onClick={onMoveUp}
996
- />
997
- <RowChromeIconButton
998
- defaults={DEFAULT_MOVE_DOWN}
999
- override={buttons?.moveDown}
1000
- disabled={disabled || isLastVisible}
1001
- onClick={onMoveDown}
1002
- />
1003
- </>
1004
- )}
1005
- {row.extraActions && row.extraActions.length > 0 && (
1006
- <ExtraActionStrip
1007
- actions={row.extraActions}
1008
- rowPath={rowPath}
1009
- disabled={disabled}
1010
- />
1011
- )}
1012
- {cloneable && canClone && (
1013
- <RowChromeIconButton
1014
- defaults={DEFAULT_CLONE}
1015
- override={buttons?.clone}
1016
- disabled={disabled || atMax}
1017
- onClick={onClone}
1018
- />
1019
- )}
1020
- {deletable && canDelete && (
1021
- <RowChromeIconButton
1022
- defaults={DEFAULT_DELETE}
1023
- override={buttons?.delete}
1024
- disabled={disabled || atMin}
1025
- onClick={onRemove}
1026
- />
1027
- )}
1028
- </div>
1029
-
1030
- <div
1031
- className="p-3"
1032
- style={isCollapsed ? { display: 'none' } : undefined}
1033
- >
1034
- {innerColumns > 1
1035
- ? (
1036
- <div
1037
- className="grid gap-3"
1038
- style={{ gridTemplateColumns: `repeat(${innerColumns}, minmax(0, 1fr))` }}
1039
- >
1040
- <SchemaRenderer elements={namespaced} />
1041
- </div>
1042
- )
1043
- : <SchemaRenderer elements={namespaced} />}
1044
- </div>
1045
- </div>
1046
- </RowCoordsContext.Provider>
1047
- )
1048
- }
1049
-
1050
- function DropIndicator(): React.ReactElement {
1051
- return (
1052
- <div
1053
- aria-hidden="true"
1054
- className="pointer-events-none h-0.5 rounded-full bg-primary"
1055
- />
1056
- )
1057
- }
1058
-
1059
- /**
1060
- * Recursively prefix every Field meta's `name` with a row-scoped path.
1061
- * Mirrors `RepeaterInput.prefixFieldNames` but doesn't recurse into
1062
- * inner Repeater/Builder rows (nested array-row inside a Block isn't
1063
- * reactive in v1 — see plan).
1064
- */
1065
- function prefixFieldNames(el: ElementMeta, prefix: string): ElementMeta {
1066
- if (el.type === 'field' && typeof el['name'] === 'string') {
1067
- const innerName = el['name']
1068
- return { ...el, name: `${prefix}.${innerName}` }
1069
- }
1070
- if (Array.isArray(el.children)) {
1071
- return {
1072
- ...el,
1073
- children: (el.children as ElementMeta[]).map(c => prefixFieldNames(c, prefix)),
1074
- }
1075
- }
1076
- return el
1077
- }
1078
-