@pilotiq/pilotiq 0.23.1 → 0.24.2

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 (500) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/boost/guidelines.md +566 -0
  3. package/boost/skills/pilotiq-fields/SKILL.md +47 -0
  4. package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
  5. package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
  6. package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
  7. package/boost/skills/pilotiq-relations/SKILL.md +47 -0
  8. package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
  9. package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
  10. package/boost/skills/pilotiq-resource/SKILL.md +61 -0
  11. package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
  12. package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
  13. package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
  14. package/dist/actions/exportFactory.d.ts +10 -0
  15. package/dist/actions/exportFactory.d.ts.map +1 -1
  16. package/dist/actions/exportFactory.js +10 -0
  17. package/dist/actions/exportFactory.js.map +1 -1
  18. package/dist/react/CollabRoomContext.d.ts +5 -5
  19. package/dist/react/index.d.ts +0 -1
  20. package/dist/react/index.d.ts.map +1 -1
  21. package/dist/react/index.js +0 -1
  22. package/dist/react/index.js.map +1 -1
  23. package/dist/routes/helpers.d.ts.map +1 -1
  24. package/dist/routes/helpers.js +6 -2
  25. package/dist/routes/helpers.js.map +1 -1
  26. package/dist/routes/relations.d.ts.map +1 -1
  27. package/dist/routes/relations.js +12 -0
  28. package/dist/routes/relations.js.map +1 -1
  29. package/package.json +6 -1
  30. package/.turbo/turbo-build.log +0 -8
  31. package/CLAUDE.md +0 -265
  32. package/dist/react/useCollabSeed.d.ts +0 -23
  33. package/dist/react/useCollabSeed.d.ts.map +0 -1
  34. package/dist/react/useCollabSeed.js +0 -82
  35. package/dist/react/useCollabSeed.js.map +0 -1
  36. package/src/Cluster.test.ts +0 -283
  37. package/src/Cluster.ts +0 -83
  38. package/src/Column.test.ts +0 -199
  39. package/src/Column.ts +0 -710
  40. package/src/Global.test.ts +0 -367
  41. package/src/Global.ts +0 -169
  42. package/src/Page.test.ts +0 -114
  43. package/src/Page.ts +0 -208
  44. package/src/Pilotiq.perf.test.ts +0 -252
  45. package/src/Pilotiq.test.ts +0 -129
  46. package/src/Pilotiq.ts +0 -1158
  47. package/src/PilotiqRegistry.ts +0 -36
  48. package/src/PilotiqServiceProvider.ts +0 -121
  49. package/src/RelationManager.test.ts +0 -400
  50. package/src/RelationManager.ts +0 -527
  51. package/src/RenderHook.test.ts +0 -252
  52. package/src/RenderHook.ts +0 -242
  53. package/src/Resource.test.ts +0 -284
  54. package/src/Resource.ts +0 -526
  55. package/src/RightPanel.test.ts +0 -202
  56. package/src/RightPanel.ts +0 -132
  57. package/src/Tab.test.ts +0 -91
  58. package/src/Tab.ts +0 -156
  59. package/src/UserMenuItem.ts +0 -145
  60. package/src/actions/Action.test.ts +0 -2526
  61. package/src/actions/Action.ts +0 -1515
  62. package/src/actions/ActionGroup.test.ts +0 -112
  63. package/src/actions/ActionGroup.ts +0 -173
  64. package/src/actions/attachFactory.ts +0 -172
  65. package/src/actions/bulkFactories.ts +0 -168
  66. package/src/actions/crudFactories.ts +0 -220
  67. package/src/actions/exportFactory.ts +0 -215
  68. package/src/actions/factoryHelpers.ts +0 -177
  69. package/src/actions/importFactory.ts +0 -243
  70. package/src/actions/index.ts +0 -17
  71. package/src/actions/m2mFactories.ts +0 -193
  72. package/src/actions/relationFactories.ts +0 -372
  73. package/src/applyPageHooks.test.ts +0 -463
  74. package/src/applyPageHooks.ts +0 -330
  75. package/src/authorization.test.ts +0 -483
  76. package/src/breadcrumbs.test.ts +0 -238
  77. package/src/cells/coerce.test.ts +0 -85
  78. package/src/cells/coerce.ts +0 -84
  79. package/src/clusterPaths.ts +0 -35
  80. package/src/columns/BadgeColumn.test.ts +0 -54
  81. package/src/columns/BadgeColumn.ts +0 -32
  82. package/src/columns/BooleanColumn.test.ts +0 -41
  83. package/src/columns/BooleanColumn.ts +0 -18
  84. package/src/columns/ColorColumn.test.ts +0 -37
  85. package/src/columns/ColorColumn.ts +0 -38
  86. package/src/columns/IconColumn.test.ts +0 -54
  87. package/src/columns/IconColumn.ts +0 -37
  88. package/src/columns/ImageColumn.test.ts +0 -41
  89. package/src/columns/ImageColumn.ts +0 -28
  90. package/src/columns/SelectColumn.ts +0 -98
  91. package/src/columns/TextColumn.test.ts +0 -190
  92. package/src/columns/TextColumn.ts +0 -20
  93. package/src/columns/TextInputColumn.ts +0 -68
  94. package/src/columns/ToggleColumn.ts +0 -46
  95. package/src/columns/editableColumns.test.ts +0 -238
  96. package/src/columns/index.ts +0 -9
  97. package/src/defaultGlobalPages.ts +0 -95
  98. package/src/defaultPages.test.ts +0 -634
  99. package/src/defaultPages.ts +0 -617
  100. package/src/defaultViewPage.test.ts +0 -147
  101. package/src/elements/Form.test.ts +0 -223
  102. package/src/elements/Form.ts +0 -416
  103. package/src/elements/ListTabs.ts +0 -28
  104. package/src/elements/Table.test.ts +0 -422
  105. package/src/elements/Table.ts +0 -850
  106. package/src/elements/TableGroup.test.ts +0 -260
  107. package/src/elements/TableGroup.ts +0 -334
  108. package/src/elements/dispatchAction.test.ts +0 -463
  109. package/src/elements/dispatchAction.ts +0 -355
  110. package/src/elements/dispatchForm.test.ts +0 -477
  111. package/src/elements/dispatchForm.ts +0 -1993
  112. package/src/elements/dispatchTable.test.ts +0 -1514
  113. package/src/elements/dispatchTable.ts +0 -745
  114. package/src/elements/index.ts +0 -21
  115. package/src/entries/BadgeEntry.ts +0 -39
  116. package/src/entries/CodeEntry.test.ts +0 -40
  117. package/src/entries/CodeEntry.ts +0 -52
  118. package/src/entries/ColorEntry.ts +0 -63
  119. package/src/entries/ComponentEntry.test.ts +0 -173
  120. package/src/entries/ComponentEntry.ts +0 -95
  121. package/src/entries/Entry.ts +0 -304
  122. package/src/entries/IconEntry.ts +0 -49
  123. package/src/entries/ImageEntry.ts +0 -61
  124. package/src/entries/KeyValueEntry.ts +0 -47
  125. package/src/entries/RepeatableEntry.test.ts +0 -239
  126. package/src/entries/RepeatableEntry.ts +0 -173
  127. package/src/entries/TextEntry.test.ts +0 -394
  128. package/src/entries/TextEntry.ts +0 -60
  129. package/src/entries/index.ts +0 -12
  130. package/src/entries/leaves.test.ts +0 -306
  131. package/src/entries/registry.ts +0 -54
  132. package/src/fields/BuilderField.test.ts +0 -1188
  133. package/src/fields/BuilderField.ts +0 -605
  134. package/src/fields/BuilderRelationship.test.ts +0 -811
  135. package/src/fields/CheckboxField.test.ts +0 -44
  136. package/src/fields/CheckboxField.ts +0 -27
  137. package/src/fields/CheckboxListField.test.ts +0 -99
  138. package/src/fields/CheckboxListField.ts +0 -66
  139. package/src/fields/ColorPickerField.test.ts +0 -33
  140. package/src/fields/ColorPickerField.ts +0 -25
  141. package/src/fields/DateField.ts +0 -54
  142. package/src/fields/DateTimeField.test.ts +0 -55
  143. package/src/fields/EmailField.ts +0 -16
  144. package/src/fields/Field.test.ts +0 -654
  145. package/src/fields/Field.ts +0 -817
  146. package/src/fields/FileUploadField.test.ts +0 -143
  147. package/src/fields/FileUploadField.ts +0 -159
  148. package/src/fields/HiddenField.test.ts +0 -27
  149. package/src/fields/HiddenField.ts +0 -28
  150. package/src/fields/KeyValueField.test.ts +0 -105
  151. package/src/fields/KeyValueField.ts +0 -55
  152. package/src/fields/MarkdownField.test.ts +0 -167
  153. package/src/fields/MarkdownField.ts +0 -162
  154. package/src/fields/NumberField.ts +0 -33
  155. package/src/fields/RadioField.test.ts +0 -94
  156. package/src/fields/RadioField.ts +0 -67
  157. package/src/fields/RepeaterField.test.ts +0 -1806
  158. package/src/fields/RepeaterField.ts +0 -939
  159. package/src/fields/RepeaterRelationship.test.ts +0 -1923
  160. package/src/fields/RepeaterSimple.test.ts +0 -248
  161. package/src/fields/RowButton.test.ts +0 -219
  162. package/src/fields/RowButton.ts +0 -135
  163. package/src/fields/SelectField.test.ts +0 -192
  164. package/src/fields/SelectField.ts +0 -235
  165. package/src/fields/SliderField.test.ts +0 -50
  166. package/src/fields/SliderField.ts +0 -53
  167. package/src/fields/SlugField.ts +0 -24
  168. package/src/fields/TagsInputField.test.ts +0 -154
  169. package/src/fields/TagsInputField.ts +0 -133
  170. package/src/fields/TextField.test.ts +0 -213
  171. package/src/fields/TextField.ts +0 -177
  172. package/src/fields/TextareaField.test.ts +0 -58
  173. package/src/fields/TextareaField.ts +0 -59
  174. package/src/fields/ToggleButtonsField.test.ts +0 -106
  175. package/src/fields/ToggleButtonsField.ts +0 -59
  176. package/src/fields/ToggleField.ts +0 -16
  177. package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
  178. package/src/fields/optionsResolver.ts +0 -95
  179. package/src/fields/resolveField.ts +0 -28
  180. package/src/filters/BooleanFilter.ts +0 -35
  181. package/src/filters/DateRangeFilter.test.ts +0 -194
  182. package/src/filters/DateRangeFilter.ts +0 -148
  183. package/src/filters/Filter.test.ts +0 -268
  184. package/src/filters/Filter.ts +0 -184
  185. package/src/filters/FormFilter.test.ts +0 -238
  186. package/src/filters/FormFilter.ts +0 -215
  187. package/src/filters/MultiSelectFilter.test.ts +0 -119
  188. package/src/filters/MultiSelectFilter.ts +0 -78
  189. package/src/filters/QueryBuilderFilter.test.ts +0 -662
  190. package/src/filters/QueryBuilderFilter.ts +0 -398
  191. package/src/filters/SelectFilter.ts +0 -46
  192. package/src/filters/TernaryFilter.test.ts +0 -160
  193. package/src/filters/TernaryFilter.ts +0 -72
  194. package/src/filters/TrashedFilter.test.ts +0 -149
  195. package/src/filters/TrashedFilter.ts +0 -55
  196. package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
  197. package/src/filters/queryBuilder/Constraint.ts +0 -115
  198. package/src/filters/queryBuilder/DateConstraint.ts +0 -69
  199. package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
  200. package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
  201. package/src/filters/queryBuilder/TextConstraint.ts +0 -64
  202. package/src/filters/queryBuilder/index.ts +0 -12
  203. package/src/icons/index.ts +0 -2
  204. package/src/icons/lucide.ts +0 -204
  205. package/src/icons/registry.test.ts +0 -56
  206. package/src/icons/registry.ts +0 -41
  207. package/src/icons/types.ts +0 -47
  208. package/src/index.ts +0 -525
  209. package/src/io/csv.test.ts +0 -142
  210. package/src/io/csv.ts +0 -170
  211. package/src/nestedRelationManagerData.test.ts +0 -547
  212. package/src/notifications/Notification.test.ts +0 -210
  213. package/src/notifications/Notification.ts +0 -354
  214. package/src/notifications/broadcast.test.ts +0 -110
  215. package/src/notifications/broadcast.ts +0 -95
  216. package/src/notifications/database.test.ts +0 -383
  217. package/src/notifications/database.ts +0 -398
  218. package/src/notifications/databaseNotifications.test.ts +0 -187
  219. package/src/notifications/dispatchNotificationAction.test.ts +0 -341
  220. package/src/notifications/dispatchNotificationAction.ts +0 -142
  221. package/src/notifications/flash.test.ts +0 -89
  222. package/src/notifications/flash.ts +0 -71
  223. package/src/notifications/index.ts +0 -45
  224. package/src/notifications/registerBroadcastAuth.test.ts +0 -134
  225. package/src/notifications/registerBroadcastAuth.ts +0 -100
  226. package/src/notifications/resolveSavedNotification.test.ts +0 -82
  227. package/src/notifications/resolveSavedNotification.ts +0 -59
  228. package/src/notifications/types.ts +0 -93
  229. package/src/orm/m2mAccessor.ts +0 -66
  230. package/src/orm/modelDefaults.test.ts +0 -633
  231. package/src/orm/modelDefaults.ts +0 -666
  232. package/src/pageData/breadcrumbs.ts +0 -288
  233. package/src/pageData/forms.ts +0 -578
  234. package/src/pageData/helpers.ts +0 -857
  235. package/src/pageData/misc.ts +0 -347
  236. package/src/pageData/navigation.ts +0 -842
  237. package/src/pageData/relationPages.ts +0 -1248
  238. package/src/pageData/relationTabs.ts +0 -286
  239. package/src/pageData/resourcePages.ts +0 -609
  240. package/src/pageData.test.ts +0 -1545
  241. package/src/pageData.ts +0 -341
  242. package/src/plugins/index.ts +0 -8
  243. package/src/plugins/themeEditor.test.ts +0 -36
  244. package/src/plugins/themeEditor.ts +0 -45
  245. package/src/react/AppShell.tsx +0 -251
  246. package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
  247. package/src/react/CollabRoomContext.ts +0 -98
  248. package/src/react/CollabTextRendererRegistry.ts +0 -102
  249. package/src/react/CommandPalette.tsx +0 -375
  250. package/src/react/CurrentUserContext.tsx +0 -50
  251. package/src/react/CustomPageWrapperGate.tsx +0 -69
  252. package/src/react/CustomPageWrapperRegistry.ts +0 -45
  253. package/src/react/FieldFocusReporterRegistry.ts +0 -37
  254. package/src/react/FieldLabelSlotRegistry.ts +0 -30
  255. package/src/react/FieldPresenceRegistry.ts +0 -46
  256. package/src/react/FormCollabBindingRegistry.ts +0 -242
  257. package/src/react/FormStateContext.tsx +0 -591
  258. package/src/react/HeadHooks.tsx +0 -126
  259. package/src/react/MarkdownEditorRegistry.test.ts +0 -38
  260. package/src/react/MarkdownEditorRegistry.ts +0 -107
  261. package/src/react/NotificationActionStrip.tsx +0 -263
  262. package/src/react/NotificationBell.tsx +0 -426
  263. package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
  264. package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
  265. package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
  266. package/src/react/PendingSuggestionsContext.tsx +0 -172
  267. package/src/react/RecordWrapperGate.tsx +0 -58
  268. package/src/react/RecordWrapperRegistry.ts +0 -39
  269. package/src/react/RenderHookSlot.tsx +0 -32
  270. package/src/react/RightSidebar.tsx +0 -257
  271. package/src/react/RightSidebarContext.tsx +0 -234
  272. package/src/react/RightSidebarTrigger.tsx +0 -53
  273. package/src/react/RowCoordsContext.tsx +0 -23
  274. package/src/react/SchemaRenderer.tsx +0 -549
  275. package/src/react/SearchTrigger.tsx +0 -46
  276. package/src/react/ThemeProvider.tsx +0 -93
  277. package/src/react/ThemeSettingsPage.tsx +0 -579
  278. package/src/react/ThemeToggle.tsx +0 -20
  279. package/src/react/Toaster.tsx +0 -158
  280. package/src/react/UserMenu.tsx +0 -196
  281. package/src/react/WidgetDataContext.tsx +0 -157
  282. package/src/react/cells/EditableCell.tsx +0 -389
  283. package/src/react/component-slots.test.ts +0 -103
  284. package/src/react/component-slots.ts +0 -116
  285. package/src/react/fieldJsHandler.test.ts +0 -166
  286. package/src/react/fieldJsHandler.ts +0 -79
  287. package/src/react/fields/BuilderInput.tsx +0 -1078
  288. package/src/react/fields/CheckboxInput.tsx +0 -39
  289. package/src/react/fields/CheckboxListInput.tsx +0 -102
  290. package/src/react/fields/ColorInput.tsx +0 -71
  291. package/src/react/fields/DateFieldInput.tsx +0 -70
  292. package/src/react/fields/DateTimeInput.tsx +0 -62
  293. package/src/react/fields/FieldShell.tsx +0 -348
  294. package/src/react/fields/FileUploadInput.tsx +0 -639
  295. package/src/react/fields/HiddenInput.tsx +0 -17
  296. package/src/react/fields/KeyValueInput.tsx +0 -230
  297. package/src/react/fields/MarkdownInput.tsx +0 -560
  298. package/src/react/fields/RadioInput.tsx +0 -81
  299. package/src/react/fields/RepeaterInput.test.ts +0 -116
  300. package/src/react/fields/RepeaterInput.tsx +0 -1420
  301. package/src/react/fields/SelectFieldInput.tsx +0 -280
  302. package/src/react/fields/SliderInput.tsx +0 -81
  303. package/src/react/fields/TagsInput.tsx +0 -283
  304. package/src/react/fields/TextLikeInput.tsx +0 -256
  305. package/src/react/fields/ToggleButtonsInput.tsx +0 -60
  306. package/src/react/fields/ToggleFieldInput.tsx +0 -56
  307. package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
  308. package/src/react/fields/relationshipRenameDispatch.ts +0 -97
  309. package/src/react/fields/repeaterReconcile.test.ts +0 -114
  310. package/src/react/fields/repeaterReconcile.ts +0 -104
  311. package/src/react/fields/rowChromeButton.tsx +0 -336
  312. package/src/react/fields/rowState.ts +0 -106
  313. package/src/react/fields/syncRowGates.test.ts +0 -202
  314. package/src/react/fields/syncRowGates.ts +0 -66
  315. package/src/react/fields/textInputControls.tsx +0 -238
  316. package/src/react/fields/useRowReorderDnd.ts +0 -78
  317. package/src/react/formStateHelpers.test.ts +0 -508
  318. package/src/react/formStateHelpers.ts +0 -381
  319. package/src/react/hooks/use-mobile.ts +0 -19
  320. package/src/react/icon-context.tsx +0 -60
  321. package/src/react/index.ts +0 -195
  322. package/src/react/layouts/SidebarLayout.tsx +0 -250
  323. package/src/react/layouts/TopbarLayout.tsx +0 -258
  324. package/src/react/navigate.tsx +0 -37
  325. package/src/react/onProviderSynced.test.ts +0 -90
  326. package/src/react/parseRecordEditUrl.test.ts +0 -122
  327. package/src/react/parseRecordEditUrl.ts +0 -94
  328. package/src/react/persistedState.ts +0 -40
  329. package/src/react/registry.ts +0 -48
  330. package/src/react/right-panel-registry.tsx +0 -47
  331. package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
  332. package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
  333. package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
  334. package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
  335. package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
  336. package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
  337. package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
  338. package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
  339. package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
  340. package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
  341. package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
  342. package/src/react/schemaRenderer/action/buttons.tsx +0 -99
  343. package/src/react/schemaRenderer/action/helpers.ts +0 -140
  344. package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
  345. package/src/react/schemaRenderer/columnFormat.ts +0 -65
  346. package/src/react/schemaRenderer/constants.ts +0 -50
  347. package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
  348. package/src/react/schemaRenderer/form/renderField.tsx +0 -511
  349. package/src/react/schemaRenderer/helpers.tsx +0 -81
  350. package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
  351. package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
  352. package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
  353. package/src/react/schemaRenderer/table/filters.tsx +0 -1233
  354. package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
  355. package/src/react/schemaRenderer/table/links.tsx +0 -112
  356. package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
  357. package/src/react/schemaRenderer/table/url.tsx +0 -143
  358. package/src/react/theme-preview/apply.ts +0 -99
  359. package/src/react/theme-preview/build-html.ts +0 -436
  360. package/src/react/ui/button.tsx +0 -51
  361. package/src/react/ui/calendar.tsx +0 -67
  362. package/src/react/ui/checkbox.tsx +0 -29
  363. package/src/react/ui/dialog.tsx +0 -108
  364. package/src/react/ui/dropdown-menu.tsx +0 -97
  365. package/src/react/ui/input.tsx +0 -20
  366. package/src/react/ui/label.tsx +0 -21
  367. package/src/react/ui/popover.tsx +0 -50
  368. package/src/react/ui/select.tsx +0 -169
  369. package/src/react/ui/separator.tsx +0 -25
  370. package/src/react/ui/sheet.tsx +0 -136
  371. package/src/react/ui/sidebar.tsx +0 -723
  372. package/src/react/ui/skeleton.tsx +0 -13
  373. package/src/react/ui/slider.tsx +0 -34
  374. package/src/react/ui/switch.tsx +0 -28
  375. package/src/react/ui/table.tsx +0 -105
  376. package/src/react/ui/tabs.tsx +0 -63
  377. package/src/react/ui/textarea.tsx +0 -18
  378. package/src/react/ui/tooltip.tsx +0 -64
  379. package/src/react/useCollabSeed.ts +0 -86
  380. package/src/react/useResizableWidth.ts +0 -139
  381. package/src/react/utils.ts +0 -6
  382. package/src/react/widgetRegistry.test.ts +0 -43
  383. package/src/react/widgetRegistry.ts +0 -50
  384. package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
  385. package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
  386. package/src/react/widgets/ViewRenderer.tsx +0 -71
  387. package/src/relationManagerData.test.ts +0 -1595
  388. package/src/richtext/index.ts +0 -8
  389. package/src/richtext/registry.ts +0 -89
  390. package/src/routes/globals.ts +0 -148
  391. package/src/routes/guard.test.ts +0 -325
  392. package/src/routes/helpers.ts +0 -700
  393. package/src/routes/pages.ts +0 -175
  394. package/src/routes/panel.ts +0 -204
  395. package/src/routes/relations.ts +0 -1227
  396. package/src/routes/resources.ts +0 -781
  397. package/src/routes/theme.ts +0 -91
  398. package/src/routes-nested-relations.test.ts +0 -676
  399. package/src/routes-relations.test.ts +0 -972
  400. package/src/routes.test.ts +0 -2027
  401. package/src/routes.ts +0 -303
  402. package/src/schema/Alert.test.ts +0 -109
  403. package/src/schema/Alert.ts +0 -131
  404. package/src/schema/Block.ts +0 -169
  405. package/src/schema/Breadcrumbs.ts +0 -40
  406. package/src/schema/Card.ts +0 -35
  407. package/src/schema/Divider.ts +0 -20
  408. package/src/schema/Element.ts +0 -219
  409. package/src/schema/EmptyState.test.ts +0 -37
  410. package/src/schema/EmptyState.ts +0 -63
  411. package/src/schema/Fieldset.ts +0 -43
  412. package/src/schema/Grid.ts +0 -43
  413. package/src/schema/Group.ts +0 -30
  414. package/src/schema/Heading.ts +0 -39
  415. package/src/schema/Html.ts +0 -67
  416. package/src/schema/Icon.ts +0 -54
  417. package/src/schema/Image.ts +0 -57
  418. package/src/schema/LinkTag.ts +0 -41
  419. package/src/schema/Markdown.ts +0 -85
  420. package/src/schema/MetaTag.ts +0 -41
  421. package/src/schema/RelationTabs.ts +0 -71
  422. package/src/schema/ScriptTag.ts +0 -55
  423. package/src/schema/Section.ts +0 -160
  424. package/src/schema/ServerDataElement.test.ts +0 -140
  425. package/src/schema/ServerDataElement.ts +0 -156
  426. package/src/schema/SlotComponent.test.ts +0 -77
  427. package/src/schema/SlotComponent.ts +0 -71
  428. package/src/schema/Split.ts +0 -50
  429. package/src/schema/Stat.test.ts +0 -118
  430. package/src/schema/Stat.ts +0 -154
  431. package/src/schema/StatsOverview.test.ts +0 -141
  432. package/src/schema/StatsOverview.ts +0 -119
  433. package/src/schema/StyleTag.ts +0 -35
  434. package/src/schema/TableWidget.test.ts +0 -297
  435. package/src/schema/TableWidget.ts +0 -289
  436. package/src/schema/Tabs.ts +0 -79
  437. package/src/schema/Text.ts +0 -58
  438. package/src/schema/UnorderedList.ts +0 -49
  439. package/src/schema/View.test.ts +0 -111
  440. package/src/schema/View.ts +0 -127
  441. package/src/schema/Wizard.ts +0 -220
  442. package/src/schema/containers.test.ts +0 -564
  443. package/src/schema/headTags.test.ts +0 -134
  444. package/src/schema/index.ts +0 -40
  445. package/src/schema/primes.test.ts +0 -269
  446. package/src/schema/resolveSchema.test.ts +0 -379
  447. package/src/schema/resolveSchema.ts +0 -917
  448. package/src/schema/sanitize.ts +0 -58
  449. package/src/search.test.ts +0 -446
  450. package/src/search.ts +0 -178
  451. package/src/sessionFilters.test.ts +0 -375
  452. package/src/sessionFilters.ts +0 -143
  453. package/src/slot-components/index.ts +0 -10
  454. package/src/slot-components/registry.ts +0 -56
  455. package/src/styles/file-upload.css +0 -13
  456. package/src/summarizers/Summarizer.test.ts +0 -84
  457. package/src/summarizers/Summarizer.ts +0 -123
  458. package/src/summarizers/index.ts +0 -11
  459. package/src/theme/base-colors.ts +0 -68
  460. package/src/theme/chart-colors.ts +0 -50
  461. package/src/theme/colors.ts +0 -447
  462. package/src/theme/generate-css.test.ts +0 -139
  463. package/src/theme/generate-css.ts +0 -44
  464. package/src/theme/generate-scale.test.ts +0 -106
  465. package/src/theme/generate-scale.ts +0 -97
  466. package/src/theme/icon-map.ts +0 -42
  467. package/src/theme/index.ts +0 -34
  468. package/src/theme/migrate.test.ts +0 -178
  469. package/src/theme/migrate.ts +0 -81
  470. package/src/theme/presets.ts +0 -135
  471. package/src/theme/radius.ts +0 -18
  472. package/src/theme/resolve.test.ts +0 -238
  473. package/src/theme/resolve.ts +0 -96
  474. package/src/theme/spacing.ts +0 -18
  475. package/src/theme/storage.test.ts +0 -126
  476. package/src/theme/storage.ts +0 -106
  477. package/src/theme/theme-colors.ts +0 -88
  478. package/src/theme/types.ts +0 -125
  479. package/src/uploads/UploadAdapter.ts +0 -35
  480. package/src/uploads/index.ts +0 -2
  481. package/src/uploads/localUpload.test.ts +0 -70
  482. package/src/uploads/localUpload.ts +0 -84
  483. package/src/validation/Validator.ts +0 -49
  484. package/src/validation/index.ts +0 -28
  485. package/src/validation/rules.ts +0 -78
  486. package/src/validation/runValidators.ts +0 -435
  487. package/src/validation/uniqueValidator.test.ts +0 -196
  488. package/src/validation/uniqueValidator.ts +0 -133
  489. package/src/validation/validators.test.ts +0 -268
  490. package/src/vite.test.ts +0 -184
  491. package/src/vite.ts +0 -787
  492. package/src/widgets/index.ts +0 -10
  493. package/src/widgets/registry.ts +0 -45
  494. package/src/widgets.test.ts +0 -592
  495. package/tsconfig.build.json +0 -11
  496. package/tsconfig.json +0 -4
  497. package/tsconfig.test.json +0 -10
  498. package/views/react/Dashboard.tsx +0 -27
  499. package/views/react/Resources/Form.tsx +0 -102
  500. 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
-