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