@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.
- package/CHANGELOG.md +33 -0
- package/boost/guidelines.md +566 -0
- package/boost/skills/pilotiq-fields/SKILL.md +47 -0
- package/boost/skills/pilotiq-fields/rules/field-catalog.md +288 -0
- package/boost/skills/pilotiq-fields/rules/reactive-fields.md +199 -0
- package/boost/skills/pilotiq-fields/rules/validation.md +198 -0
- package/boost/skills/pilotiq-relations/SKILL.md +47 -0
- package/boost/skills/pilotiq-relations/rules/relation-managers.md +256 -0
- package/boost/skills/pilotiq-relations/rules/repeater-relationship.md +177 -0
- package/boost/skills/pilotiq-resource/SKILL.md +61 -0
- package/boost/skills/pilotiq-resource/rules/authorization.md +242 -0
- package/boost/skills/pilotiq-resource/rules/defining-resources.md +228 -0
- package/boost/skills/pilotiq-resource/rules/page-overrides.md +296 -0
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/src/Cluster.test.ts +0 -283
- package/src/Cluster.ts +0 -83
- package/src/Column.test.ts +0 -199
- package/src/Column.ts +0 -710
- package/src/Global.test.ts +0 -367
- package/src/Global.ts +0 -169
- package/src/Page.test.ts +0 -114
- package/src/Page.ts +0 -208
- package/src/Pilotiq.perf.test.ts +0 -252
- package/src/Pilotiq.test.ts +0 -129
- package/src/Pilotiq.ts +0 -1158
- package/src/PilotiqRegistry.ts +0 -36
- package/src/PilotiqServiceProvider.ts +0 -121
- package/src/RelationManager.test.ts +0 -400
- package/src/RelationManager.ts +0 -527
- package/src/RenderHook.test.ts +0 -252
- package/src/RenderHook.ts +0 -242
- package/src/Resource.test.ts +0 -284
- package/src/Resource.ts +0 -526
- package/src/RightPanel.test.ts +0 -202
- package/src/RightPanel.ts +0 -132
- package/src/Tab.test.ts +0 -91
- package/src/Tab.ts +0 -156
- package/src/UserMenuItem.ts +0 -145
- package/src/actions/Action.test.ts +0 -2526
- package/src/actions/Action.ts +0 -1515
- package/src/actions/ActionGroup.test.ts +0 -112
- package/src/actions/ActionGroup.ts +0 -173
- package/src/actions/attachFactory.ts +0 -172
- package/src/actions/bulkFactories.ts +0 -168
- package/src/actions/crudFactories.ts +0 -220
- package/src/actions/exportFactory.ts +0 -225
- package/src/actions/factoryHelpers.ts +0 -177
- package/src/actions/importFactory.ts +0 -243
- package/src/actions/index.ts +0 -17
- package/src/actions/m2mFactories.ts +0 -193
- package/src/actions/relationFactories.ts +0 -372
- package/src/applyPageHooks.test.ts +0 -463
- package/src/applyPageHooks.ts +0 -330
- package/src/authorization.test.ts +0 -483
- package/src/breadcrumbs.test.ts +0 -238
- package/src/cells/coerce.test.ts +0 -85
- package/src/cells/coerce.ts +0 -84
- package/src/clusterPaths.ts +0 -35
- package/src/columns/BadgeColumn.test.ts +0 -54
- package/src/columns/BadgeColumn.ts +0 -32
- package/src/columns/BooleanColumn.test.ts +0 -41
- package/src/columns/BooleanColumn.ts +0 -18
- package/src/columns/ColorColumn.test.ts +0 -37
- package/src/columns/ColorColumn.ts +0 -38
- package/src/columns/IconColumn.test.ts +0 -54
- package/src/columns/IconColumn.ts +0 -37
- package/src/columns/ImageColumn.test.ts +0 -41
- package/src/columns/ImageColumn.ts +0 -28
- package/src/columns/SelectColumn.ts +0 -98
- package/src/columns/TextColumn.test.ts +0 -190
- package/src/columns/TextColumn.ts +0 -20
- package/src/columns/TextInputColumn.ts +0 -68
- package/src/columns/ToggleColumn.ts +0 -46
- package/src/columns/editableColumns.test.ts +0 -238
- package/src/columns/index.ts +0 -9
- package/src/defaultGlobalPages.ts +0 -95
- package/src/defaultPages.test.ts +0 -634
- package/src/defaultPages.ts +0 -617
- package/src/defaultViewPage.test.ts +0 -147
- package/src/elements/Form.test.ts +0 -223
- package/src/elements/Form.ts +0 -416
- package/src/elements/ListTabs.ts +0 -28
- package/src/elements/Table.test.ts +0 -422
- package/src/elements/Table.ts +0 -850
- package/src/elements/TableGroup.test.ts +0 -260
- package/src/elements/TableGroup.ts +0 -334
- package/src/elements/dispatchAction.test.ts +0 -463
- package/src/elements/dispatchAction.ts +0 -355
- package/src/elements/dispatchForm.test.ts +0 -477
- package/src/elements/dispatchForm.ts +0 -1993
- package/src/elements/dispatchTable.test.ts +0 -1514
- package/src/elements/dispatchTable.ts +0 -745
- package/src/elements/index.ts +0 -21
- package/src/entries/BadgeEntry.ts +0 -39
- package/src/entries/CodeEntry.test.ts +0 -40
- package/src/entries/CodeEntry.ts +0 -52
- package/src/entries/ColorEntry.ts +0 -63
- package/src/entries/ComponentEntry.test.ts +0 -173
- package/src/entries/ComponentEntry.ts +0 -95
- package/src/entries/Entry.ts +0 -304
- package/src/entries/IconEntry.ts +0 -49
- package/src/entries/ImageEntry.ts +0 -61
- package/src/entries/KeyValueEntry.ts +0 -47
- package/src/entries/RepeatableEntry.test.ts +0 -239
- package/src/entries/RepeatableEntry.ts +0 -173
- package/src/entries/TextEntry.test.ts +0 -394
- package/src/entries/TextEntry.ts +0 -60
- package/src/entries/index.ts +0 -12
- package/src/entries/leaves.test.ts +0 -306
- package/src/entries/registry.ts +0 -54
- package/src/fields/BuilderField.test.ts +0 -1188
- package/src/fields/BuilderField.ts +0 -605
- package/src/fields/BuilderRelationship.test.ts +0 -811
- package/src/fields/CheckboxField.test.ts +0 -44
- package/src/fields/CheckboxField.ts +0 -27
- package/src/fields/CheckboxListField.test.ts +0 -99
- package/src/fields/CheckboxListField.ts +0 -66
- package/src/fields/ColorPickerField.test.ts +0 -33
- package/src/fields/ColorPickerField.ts +0 -25
- package/src/fields/DateField.ts +0 -54
- package/src/fields/DateTimeField.test.ts +0 -55
- package/src/fields/EmailField.ts +0 -16
- package/src/fields/Field.test.ts +0 -654
- package/src/fields/Field.ts +0 -817
- package/src/fields/FileUploadField.test.ts +0 -143
- package/src/fields/FileUploadField.ts +0 -159
- package/src/fields/HiddenField.test.ts +0 -27
- package/src/fields/HiddenField.ts +0 -28
- package/src/fields/KeyValueField.test.ts +0 -105
- package/src/fields/KeyValueField.ts +0 -55
- package/src/fields/MarkdownField.test.ts +0 -167
- package/src/fields/MarkdownField.ts +0 -162
- package/src/fields/NumberField.ts +0 -33
- package/src/fields/RadioField.test.ts +0 -94
- package/src/fields/RadioField.ts +0 -67
- package/src/fields/RepeaterField.test.ts +0 -1806
- package/src/fields/RepeaterField.ts +0 -939
- package/src/fields/RepeaterRelationship.test.ts +0 -1923
- package/src/fields/RepeaterSimple.test.ts +0 -248
- package/src/fields/RowButton.test.ts +0 -219
- package/src/fields/RowButton.ts +0 -135
- package/src/fields/SelectField.test.ts +0 -192
- package/src/fields/SelectField.ts +0 -235
- package/src/fields/SliderField.test.ts +0 -50
- package/src/fields/SliderField.ts +0 -53
- package/src/fields/SlugField.ts +0 -24
- package/src/fields/TagsInputField.test.ts +0 -154
- package/src/fields/TagsInputField.ts +0 -133
- package/src/fields/TextField.test.ts +0 -213
- package/src/fields/TextField.ts +0 -177
- package/src/fields/TextareaField.test.ts +0 -58
- package/src/fields/TextareaField.ts +0 -59
- package/src/fields/ToggleButtonsField.test.ts +0 -106
- package/src/fields/ToggleButtonsField.ts +0 -59
- package/src/fields/ToggleField.ts +0 -16
- package/src/fields/disableOptionsWhenSelectedInSiblingRepeaterItems.test.ts +0 -319
- package/src/fields/optionsResolver.ts +0 -95
- package/src/fields/resolveField.ts +0 -28
- package/src/filters/BooleanFilter.ts +0 -35
- package/src/filters/DateRangeFilter.test.ts +0 -194
- package/src/filters/DateRangeFilter.ts +0 -148
- package/src/filters/Filter.test.ts +0 -268
- package/src/filters/Filter.ts +0 -184
- package/src/filters/FormFilter.test.ts +0 -238
- package/src/filters/FormFilter.ts +0 -215
- package/src/filters/MultiSelectFilter.test.ts +0 -119
- package/src/filters/MultiSelectFilter.ts +0 -78
- package/src/filters/QueryBuilderFilter.test.ts +0 -662
- package/src/filters/QueryBuilderFilter.ts +0 -398
- package/src/filters/SelectFilter.ts +0 -46
- package/src/filters/TernaryFilter.test.ts +0 -160
- package/src/filters/TernaryFilter.ts +0 -72
- package/src/filters/TrashedFilter.test.ts +0 -149
- package/src/filters/TrashedFilter.ts +0 -55
- package/src/filters/queryBuilder/BooleanConstraint.ts +0 -31
- package/src/filters/queryBuilder/Constraint.ts +0 -115
- package/src/filters/queryBuilder/DateConstraint.ts +0 -69
- package/src/filters/queryBuilder/NumberConstraint.ts +0 -66
- package/src/filters/queryBuilder/SelectConstraint.ts +0 -72
- package/src/filters/queryBuilder/TextConstraint.ts +0 -64
- package/src/filters/queryBuilder/index.ts +0 -12
- package/src/icons/index.ts +0 -2
- package/src/icons/lucide.ts +0 -204
- package/src/icons/registry.test.ts +0 -56
- package/src/icons/registry.ts +0 -41
- package/src/icons/types.ts +0 -47
- package/src/index.ts +0 -525
- package/src/io/csv.test.ts +0 -142
- package/src/io/csv.ts +0 -170
- package/src/nestedRelationManagerData.test.ts +0 -547
- package/src/notifications/Notification.test.ts +0 -210
- package/src/notifications/Notification.ts +0 -354
- package/src/notifications/broadcast.test.ts +0 -110
- package/src/notifications/broadcast.ts +0 -95
- package/src/notifications/database.test.ts +0 -383
- package/src/notifications/database.ts +0 -398
- package/src/notifications/databaseNotifications.test.ts +0 -187
- package/src/notifications/dispatchNotificationAction.test.ts +0 -341
- package/src/notifications/dispatchNotificationAction.ts +0 -142
- package/src/notifications/flash.test.ts +0 -89
- package/src/notifications/flash.ts +0 -71
- package/src/notifications/index.ts +0 -45
- package/src/notifications/registerBroadcastAuth.test.ts +0 -134
- package/src/notifications/registerBroadcastAuth.ts +0 -100
- package/src/notifications/resolveSavedNotification.test.ts +0 -82
- package/src/notifications/resolveSavedNotification.ts +0 -59
- package/src/notifications/types.ts +0 -93
- package/src/orm/m2mAccessor.ts +0 -66
- package/src/orm/modelDefaults.test.ts +0 -633
- package/src/orm/modelDefaults.ts +0 -666
- package/src/pageData/breadcrumbs.ts +0 -288
- package/src/pageData/forms.ts +0 -578
- package/src/pageData/helpers.ts +0 -857
- package/src/pageData/misc.ts +0 -347
- package/src/pageData/navigation.ts +0 -842
- package/src/pageData/relationPages.ts +0 -1248
- package/src/pageData/relationTabs.ts +0 -286
- package/src/pageData/resourcePages.ts +0 -609
- package/src/pageData.test.ts +0 -1545
- package/src/pageData.ts +0 -341
- package/src/plugins/index.ts +0 -8
- package/src/plugins/themeEditor.test.ts +0 -36
- package/src/plugins/themeEditor.ts +0 -45
- package/src/react/AppShell.tsx +0 -251
- package/src/react/CollabExtensionFactoryRegistry.ts +0 -55
- package/src/react/CollabRoomContext.ts +0 -98
- package/src/react/CollabTextRendererRegistry.ts +0 -102
- package/src/react/CommandPalette.tsx +0 -375
- package/src/react/CurrentUserContext.tsx +0 -50
- package/src/react/CustomPageWrapperGate.tsx +0 -69
- package/src/react/CustomPageWrapperRegistry.ts +0 -45
- package/src/react/FieldFocusReporterRegistry.ts +0 -37
- package/src/react/FieldLabelSlotRegistry.ts +0 -30
- package/src/react/FieldPresenceRegistry.ts +0 -46
- package/src/react/FormCollabBindingRegistry.ts +0 -242
- package/src/react/FormStateContext.tsx +0 -591
- package/src/react/HeadHooks.tsx +0 -126
- package/src/react/MarkdownEditorRegistry.test.ts +0 -38
- package/src/react/MarkdownEditorRegistry.ts +0 -107
- package/src/react/NotificationActionStrip.tsx +0 -263
- package/src/react/NotificationBell.tsx +0 -426
- package/src/react/PendingSuggestionApplierRegistry.test.ts +0 -97
- package/src/react/PendingSuggestionApplierRegistry.ts +0 -98
- package/src/react/PendingSuggestionOverlayRegistry.ts +0 -54
- package/src/react/PendingSuggestionsContext.tsx +0 -172
- package/src/react/RecordWrapperGate.tsx +0 -58
- package/src/react/RecordWrapperRegistry.ts +0 -39
- package/src/react/RenderHookSlot.tsx +0 -32
- package/src/react/RightSidebar.tsx +0 -257
- package/src/react/RightSidebarContext.tsx +0 -234
- package/src/react/RightSidebarTrigger.tsx +0 -53
- package/src/react/RowCoordsContext.tsx +0 -23
- package/src/react/SchemaRenderer.tsx +0 -549
- package/src/react/SearchTrigger.tsx +0 -46
- package/src/react/ThemeProvider.tsx +0 -93
- package/src/react/ThemeSettingsPage.tsx +0 -579
- package/src/react/ThemeToggle.tsx +0 -20
- package/src/react/Toaster.tsx +0 -158
- package/src/react/UserMenu.tsx +0 -196
- package/src/react/WidgetDataContext.tsx +0 -157
- package/src/react/cells/EditableCell.tsx +0 -389
- package/src/react/component-slots.test.ts +0 -103
- package/src/react/component-slots.ts +0 -116
- package/src/react/fieldJsHandler.test.ts +0 -166
- package/src/react/fieldJsHandler.ts +0 -79
- package/src/react/fields/BuilderInput.tsx +0 -1078
- package/src/react/fields/CheckboxInput.tsx +0 -39
- package/src/react/fields/CheckboxListInput.tsx +0 -102
- package/src/react/fields/ColorInput.tsx +0 -71
- package/src/react/fields/DateFieldInput.tsx +0 -70
- package/src/react/fields/DateTimeInput.tsx +0 -62
- package/src/react/fields/FieldShell.tsx +0 -348
- package/src/react/fields/FileUploadInput.tsx +0 -639
- package/src/react/fields/HiddenInput.tsx +0 -17
- package/src/react/fields/KeyValueInput.tsx +0 -230
- package/src/react/fields/MarkdownInput.tsx +0 -560
- package/src/react/fields/RadioInput.tsx +0 -81
- package/src/react/fields/RepeaterInput.test.ts +0 -116
- package/src/react/fields/RepeaterInput.tsx +0 -1420
- package/src/react/fields/SelectFieldInput.tsx +0 -280
- package/src/react/fields/SliderInput.tsx +0 -81
- package/src/react/fields/TagsInput.tsx +0 -283
- package/src/react/fields/TextLikeInput.tsx +0 -256
- package/src/react/fields/ToggleButtonsInput.tsx +0 -60
- package/src/react/fields/ToggleFieldInput.tsx +0 -56
- package/src/react/fields/relationshipRenameDispatch.test.ts +0 -106
- package/src/react/fields/relationshipRenameDispatch.ts +0 -97
- package/src/react/fields/repeaterReconcile.test.ts +0 -114
- package/src/react/fields/repeaterReconcile.ts +0 -104
- package/src/react/fields/rowChromeButton.tsx +0 -336
- package/src/react/fields/rowState.ts +0 -106
- package/src/react/fields/syncRowGates.test.ts +0 -202
- package/src/react/fields/syncRowGates.ts +0 -66
- package/src/react/fields/textInputControls.tsx +0 -238
- package/src/react/fields/useRowReorderDnd.ts +0 -78
- package/src/react/formStateHelpers.test.ts +0 -508
- package/src/react/formStateHelpers.ts +0 -381
- package/src/react/hooks/use-mobile.ts +0 -19
- package/src/react/icon-context.tsx +0 -60
- package/src/react/index.ts +0 -194
- package/src/react/layouts/SidebarLayout.tsx +0 -250
- package/src/react/layouts/TopbarLayout.tsx +0 -258
- package/src/react/navigate.tsx +0 -37
- package/src/react/onProviderSynced.test.ts +0 -90
- package/src/react/parseRecordEditUrl.test.ts +0 -122
- package/src/react/parseRecordEditUrl.ts +0 -94
- package/src/react/persistedState.ts +0 -40
- package/src/react/registry.ts +0 -48
- package/src/react/right-panel-registry.tsx +0 -47
- package/src/react/schemaRenderer/AlertRenderer.tsx +0 -112
- package/src/react/schemaRenderer/EntryRenderer.tsx +0 -501
- package/src/react/schemaRenderer/SectionRenderer.tsx +0 -120
- package/src/react/schemaRenderer/SimpleElements.tsx +0 -306
- package/src/react/schemaRenderer/TabsRenderer.tsx +0 -62
- package/src/react/schemaRenderer/WizardRenderer.tsx +0 -338
- package/src/react/schemaRenderer/action/ActionGroupTrigger.tsx +0 -177
- package/src/react/schemaRenderer/action/ActionModalDialog.tsx +0 -273
- package/src/react/schemaRenderer/action/ConfirmActionDialog.tsx +0 -61
- package/src/react/schemaRenderer/action/HandlerActionButton.tsx +0 -43
- package/src/react/schemaRenderer/action/MethodActionButton.tsx +0 -64
- package/src/react/schemaRenderer/action/buttons.tsx +0 -99
- package/src/react/schemaRenderer/action/helpers.ts +0 -140
- package/src/react/schemaRenderer/action/renderAction.tsx +0 -245
- package/src/react/schemaRenderer/columnFormat.ts +0 -65
- package/src/react/schemaRenderer/constants.ts +0 -50
- package/src/react/schemaRenderer/form/FormRenderer.tsx +0 -274
- package/src/react/schemaRenderer/form/renderField.tsx +0 -511
- package/src/react/schemaRenderer/helpers.tsx +0 -81
- package/src/react/schemaRenderer/table/CardsLayoutBody.tsx +0 -308
- package/src/react/schemaRenderer/table/TableRenderer.tsx +0 -123
- package/src/react/schemaRenderer/table/TableRendererBody.tsx +0 -974
- package/src/react/schemaRenderer/table/filters.tsx +0 -1233
- package/src/react/schemaRenderer/table/formatCell.tsx +0 -264
- package/src/react/schemaRenderer/table/links.tsx +0 -112
- package/src/react/schemaRenderer/table/renderRowActions.tsx +0 -52
- package/src/react/schemaRenderer/table/url.tsx +0 -143
- package/src/react/theme-preview/apply.ts +0 -99
- package/src/react/theme-preview/build-html.ts +0 -436
- package/src/react/ui/button.tsx +0 -51
- package/src/react/ui/calendar.tsx +0 -67
- package/src/react/ui/checkbox.tsx +0 -29
- package/src/react/ui/dialog.tsx +0 -108
- package/src/react/ui/dropdown-menu.tsx +0 -97
- package/src/react/ui/input.tsx +0 -20
- package/src/react/ui/label.tsx +0 -21
- package/src/react/ui/popover.tsx +0 -50
- package/src/react/ui/select.tsx +0 -169
- package/src/react/ui/separator.tsx +0 -25
- package/src/react/ui/sheet.tsx +0 -136
- package/src/react/ui/sidebar.tsx +0 -723
- package/src/react/ui/skeleton.tsx +0 -13
- package/src/react/ui/slider.tsx +0 -34
- package/src/react/ui/switch.tsx +0 -28
- package/src/react/ui/table.tsx +0 -105
- package/src/react/ui/tabs.tsx +0 -63
- package/src/react/ui/textarea.tsx +0 -18
- package/src/react/ui/tooltip.tsx +0 -64
- package/src/react/useResizableWidth.ts +0 -139
- package/src/react/utils.ts +0 -6
- package/src/react/widgetRegistry.test.ts +0 -43
- package/src/react/widgetRegistry.ts +0 -50
- package/src/react/widgets/StatsOverviewRenderer.tsx +0 -232
- package/src/react/widgets/TableWidgetRenderer.tsx +0 -231
- package/src/react/widgets/ViewRenderer.tsx +0 -71
- package/src/relationManagerData.test.ts +0 -1595
- package/src/richtext/index.ts +0 -8
- package/src/richtext/registry.ts +0 -89
- package/src/routes/globals.ts +0 -148
- package/src/routes/guard.test.ts +0 -325
- package/src/routes/helpers.ts +0 -704
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1243
- package/src/routes/resources.ts +0 -781
- package/src/routes/theme.ts +0 -91
- package/src/routes-nested-relations.test.ts +0 -676
- package/src/routes-relations.test.ts +0 -972
- package/src/routes.test.ts +0 -2027
- package/src/routes.ts +0 -303
- package/src/schema/Alert.test.ts +0 -109
- package/src/schema/Alert.ts +0 -131
- package/src/schema/Block.ts +0 -169
- package/src/schema/Breadcrumbs.ts +0 -40
- package/src/schema/Card.ts +0 -35
- package/src/schema/Divider.ts +0 -20
- package/src/schema/Element.ts +0 -219
- package/src/schema/EmptyState.test.ts +0 -37
- package/src/schema/EmptyState.ts +0 -63
- package/src/schema/Fieldset.ts +0 -43
- package/src/schema/Grid.ts +0 -43
- package/src/schema/Group.ts +0 -30
- package/src/schema/Heading.ts +0 -39
- package/src/schema/Html.ts +0 -67
- package/src/schema/Icon.ts +0 -54
- package/src/schema/Image.ts +0 -57
- package/src/schema/LinkTag.ts +0 -41
- package/src/schema/Markdown.ts +0 -85
- package/src/schema/MetaTag.ts +0 -41
- package/src/schema/RelationTabs.ts +0 -71
- package/src/schema/ScriptTag.ts +0 -55
- package/src/schema/Section.ts +0 -160
- package/src/schema/ServerDataElement.test.ts +0 -140
- package/src/schema/ServerDataElement.ts +0 -156
- package/src/schema/SlotComponent.test.ts +0 -77
- package/src/schema/SlotComponent.ts +0 -71
- package/src/schema/Split.ts +0 -50
- package/src/schema/Stat.test.ts +0 -118
- package/src/schema/Stat.ts +0 -154
- package/src/schema/StatsOverview.test.ts +0 -141
- package/src/schema/StatsOverview.ts +0 -119
- package/src/schema/StyleTag.ts +0 -35
- package/src/schema/TableWidget.test.ts +0 -297
- package/src/schema/TableWidget.ts +0 -289
- package/src/schema/Tabs.ts +0 -79
- package/src/schema/Text.ts +0 -58
- package/src/schema/UnorderedList.ts +0 -49
- package/src/schema/View.test.ts +0 -111
- package/src/schema/View.ts +0 -127
- package/src/schema/Wizard.ts +0 -220
- package/src/schema/containers.test.ts +0 -564
- package/src/schema/headTags.test.ts +0 -134
- package/src/schema/index.ts +0 -40
- package/src/schema/primes.test.ts +0 -269
- package/src/schema/resolveSchema.test.ts +0 -379
- package/src/schema/resolveSchema.ts +0 -917
- package/src/schema/sanitize.ts +0 -58
- package/src/search.test.ts +0 -446
- package/src/search.ts +0 -178
- package/src/sessionFilters.test.ts +0 -375
- package/src/sessionFilters.ts +0 -143
- package/src/slot-components/index.ts +0 -10
- package/src/slot-components/registry.ts +0 -56
- package/src/styles/file-upload.css +0 -13
- package/src/summarizers/Summarizer.test.ts +0 -84
- package/src/summarizers/Summarizer.ts +0 -123
- package/src/summarizers/index.ts +0 -11
- package/src/theme/base-colors.ts +0 -68
- package/src/theme/chart-colors.ts +0 -50
- package/src/theme/colors.ts +0 -447
- package/src/theme/generate-css.test.ts +0 -139
- package/src/theme/generate-css.ts +0 -44
- package/src/theme/generate-scale.test.ts +0 -106
- package/src/theme/generate-scale.ts +0 -97
- package/src/theme/icon-map.ts +0 -42
- package/src/theme/index.ts +0 -34
- package/src/theme/migrate.test.ts +0 -178
- package/src/theme/migrate.ts +0 -81
- package/src/theme/presets.ts +0 -135
- package/src/theme/radius.ts +0 -18
- package/src/theme/resolve.test.ts +0 -238
- package/src/theme/resolve.ts +0 -96
- package/src/theme/spacing.ts +0 -18
- package/src/theme/storage.test.ts +0 -126
- package/src/theme/storage.ts +0 -106
- package/src/theme/theme-colors.ts +0 -88
- package/src/theme/types.ts +0 -125
- package/src/uploads/UploadAdapter.ts +0 -35
- package/src/uploads/index.ts +0 -2
- package/src/uploads/localUpload.test.ts +0 -70
- package/src/uploads/localUpload.ts +0 -84
- package/src/validation/Validator.ts +0 -49
- package/src/validation/index.ts +0 -28
- package/src/validation/rules.ts +0 -78
- package/src/validation/runValidators.ts +0 -435
- package/src/validation/uniqueValidator.test.ts +0 -196
- package/src/validation/uniqueValidator.ts +0 -133
- package/src/validation/validators.test.ts +0 -268
- package/src/vite.test.ts +0 -184
- package/src/vite.ts +0 -787
- package/src/widgets/index.ts +0 -10
- package/src/widgets/registry.ts +0 -45
- package/src/widgets.test.ts +0 -592
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -4
- package/tsconfig.test.json +0 -10
- package/views/react/Dashboard.tsx +0 -27
- package/views/react/Resources/Form.tsx +0 -102
- package/views/react/Resources/Index.tsx +0 -49
|
@@ -1,591 +0,0 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
createContext,
|
|
3
|
-
useCallback,
|
|
4
|
-
useContext,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
useRef,
|
|
8
|
-
useState,
|
|
9
|
-
} from 'react'
|
|
10
|
-
import type { ElementMeta } from '../schema/Element.js'
|
|
11
|
-
import {
|
|
12
|
-
collectFieldDefaults,
|
|
13
|
-
collectRowArrayFieldNames,
|
|
14
|
-
findFieldMeta,
|
|
15
|
-
parseFormDataToNested,
|
|
16
|
-
readNestedValue,
|
|
17
|
-
routeBindingWrite,
|
|
18
|
-
writeNestedValue,
|
|
19
|
-
} from './formStateHelpers.js'
|
|
20
|
-
import { runJsHandler } from './fieldJsHandler.js'
|
|
21
|
-
import { useToast } from './Toaster.js'
|
|
22
|
-
import { useCollabRoom } from './CollabRoomContext.js'
|
|
23
|
-
import {
|
|
24
|
-
getFormCollabBinding,
|
|
25
|
-
type FormCollabBinding,
|
|
26
|
-
type RowBindingApi,
|
|
27
|
-
} from './FormCollabBindingRegistry.js'
|
|
28
|
-
import { registerRelationshipRenameHandler } from './fields/relationshipRenameDispatch.js'
|
|
29
|
-
|
|
30
|
-
export type FieldStatus = 'idle' | 'pending'
|
|
31
|
-
|
|
32
|
-
export interface FormStateApi {
|
|
33
|
-
values: Record<string, unknown>
|
|
34
|
-
setValue: (name: string, value: unknown) => void
|
|
35
|
-
/** Fire the field's `live()` re-resolve hook. Plan #14 — `valueOverride`
|
|
36
|
-
* lets uncontrolled inner-Repeater inputs pass their just-typed value
|
|
37
|
-
* through without first writing it to the controlled `values` map.
|
|
38
|
-
* When the form has a `formRef`, the latest DOM state of all
|
|
39
|
-
* uncontrolled inputs is snapshotted via FormData before posting; the
|
|
40
|
-
* override is then layered on top so the leaf carries the most recent
|
|
41
|
-
* keystroke even when the snapshot races the input's commit. */
|
|
42
|
-
triggerLive: (name: string, valueOverride?: unknown) => void
|
|
43
|
-
errors: Record<string, string[]>
|
|
44
|
-
/** Plan #8 — replace the errors map. Used by Wizard's step-validate
|
|
45
|
-
* flow to surface per-field errors returned from the wizard endpoint. */
|
|
46
|
-
applyErrors: (errors: Record<string, string[]>) => void
|
|
47
|
-
formMeta: ElementMeta
|
|
48
|
-
inFlight: boolean
|
|
49
|
-
fieldStatus: (name: string) => FieldStatus
|
|
50
|
-
/** Phase F.5 — per-Repeater/Builder row-array bindings. `null` outside a
|
|
51
|
-
* collab room or when the binding doesn't implement F.5 row methods.
|
|
52
|
-
* Each entry's API methods are pre-bound to the array name so renderers
|
|
53
|
-
* call `.add(rowId, initial)` rather than `binding.addRow(name, …)`. */
|
|
54
|
-
rowBindings: ReadonlyMap<string, RowBindingApi> | null
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const FormStateContext = createContext<FormStateApi | null>(null)
|
|
58
|
-
|
|
59
|
-
/** Hook for direct access to the form context. Returns `null` outside a
|
|
60
|
-
* `FormStateProvider` (e.g. an action modal, or a form without any live
|
|
61
|
-
* fields where the legacy uncontrolled path is in use). */
|
|
62
|
-
export function useFormState(): FormStateApi | null {
|
|
63
|
-
return useContext(FormStateContext)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Plan #14 — minimal context that lets nested renderers (e.g. RepeaterInput)
|
|
68
|
-
* see the parent `<form>`'s `formId` without reaching for `useFormState`
|
|
69
|
-
* (which is null on uncontrolled forms) or sniffing the DOM. `FormRenderer`
|
|
70
|
-
* wraps its children in this provider; readers hit the context with
|
|
71
|
-
* `useContext(FormIdContext)` and fall back to a sentinel when missing.
|
|
72
|
-
*/
|
|
73
|
-
export const FormIdContext = createContext<string>('')
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Public accessor for the surrounding form's id, normalized to
|
|
77
|
-
* `undefined` when no `FormRenderer` is mounted up-tree (the sentinel
|
|
78
|
-
* empty string maps to `undefined`). Adapter packages — `@pilotiq/tiptap`,
|
|
79
|
-
* `@pilotiq/codemirror` — consume this via the package's `react` re-export
|
|
80
|
-
* to scope per-field registries (pending-suggestion appliers, focus
|
|
81
|
-
* reporters) by form so multi-form pages route correctly to the matching
|
|
82
|
-
* editor instance.
|
|
83
|
-
*/
|
|
84
|
-
export function useFormId(): string | undefined {
|
|
85
|
-
return useContext(FormIdContext) || undefined
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export interface UseFieldStateResult {
|
|
89
|
-
/** True when the field is inside a controlled form (live fields enabled).
|
|
90
|
-
* Renderers should fall back to their `defaultValue` path when false.
|
|
91
|
-
*
|
|
92
|
-
* Plan #14 — dotted-name fields (inner Repeater rows) always return
|
|
93
|
-
* `controlled: false`. Their inputs stay uncontrolled so reorder/clone
|
|
94
|
-
* preserves typed values; the live trigger still works (snapshotting
|
|
95
|
-
* via the form's FormData at trigger time). */
|
|
96
|
-
controlled: boolean
|
|
97
|
-
value: unknown
|
|
98
|
-
setValue: (v: unknown) => void
|
|
99
|
-
/** Notify the framework that this field's value has changed in a way that
|
|
100
|
-
* should trigger its `live()` hook (if configured). No-op for non-live
|
|
101
|
-
* fields and outside controlled forms. `valueOverride` lets uncontrolled
|
|
102
|
-
* inputs pass their just-typed value (for dotted-path inner Repeater
|
|
103
|
-
* fields). */
|
|
104
|
-
triggerLive: (valueOverride?: unknown) => void
|
|
105
|
-
/** True while a live re-resolve POST is in flight for this field. */
|
|
106
|
-
pending: boolean
|
|
107
|
-
errors: string[]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Phase F.5 — return the row-array CRDT API for a Repeater/Builder field.
|
|
112
|
-
* Returns `null` when:
|
|
113
|
-
*
|
|
114
|
-
* - No `FormStateProvider` is mounted (e.g. uncontrolled form path).
|
|
115
|
-
* - No `<RecordCollabRoom>` is up-tree (no binding registered).
|
|
116
|
-
* - The active binding doesn't implement F.5's row methods.
|
|
117
|
-
* - The named field opted out via `.collab(false)` (skipped at meta walk).
|
|
118
|
-
* - The named field isn't a Repeater/Builder.
|
|
119
|
-
*
|
|
120
|
-
* RepeaterInput + BuilderInput call this once per render and proceed
|
|
121
|
-
* with the v1 local-only behaviour when null. The returned API methods
|
|
122
|
-
* are pre-bound to the array name so consumers don't repeat it.
|
|
123
|
-
*/
|
|
124
|
-
export function useRowBinding(arrayName: string): RowBindingApi | null {
|
|
125
|
-
const ctx = useContext(FormStateContext)
|
|
126
|
-
if (!ctx?.rowBindings) return null
|
|
127
|
-
return ctx.rowBindings.get(arrayName) ?? null
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** Per-field accessor. Inside a `FormStateProvider` it returns the controlled
|
|
131
|
-
* value + setter + live trigger; outside, it returns sentinels and callers
|
|
132
|
-
* should fall back to `defaultValue` (uncontrolled inputs). */
|
|
133
|
-
export function useFieldState(name: string): UseFieldStateResult {
|
|
134
|
-
const ctx = useContext(FormStateContext)
|
|
135
|
-
if (!ctx) {
|
|
136
|
-
return {
|
|
137
|
-
controlled: false,
|
|
138
|
-
value: undefined,
|
|
139
|
-
setValue: () => {},
|
|
140
|
-
triggerLive: () => {},
|
|
141
|
-
pending: false,
|
|
142
|
-
errors: [],
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
// Dotted-path fields (inner Repeater rows) always render uncontrolled
|
|
146
|
-
// — see UseFieldStateResult.controlled doc for why.
|
|
147
|
-
const dotted = name.includes('.')
|
|
148
|
-
return {
|
|
149
|
-
controlled: !dotted,
|
|
150
|
-
value: dotted ? undefined : ctx.values[name],
|
|
151
|
-
setValue: dotted ? () => {} : (v) => ctx.setValue(name, v),
|
|
152
|
-
triggerLive: (valueOverride?: unknown) => ctx.triggerLive(name, valueOverride),
|
|
153
|
-
pending: ctx.fieldStatus(name) === 'pending',
|
|
154
|
-
errors: ctx.errors[name] ?? [],
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** Response shape from `POST {base}/.../_form/:formId/state`. */
|
|
159
|
-
interface FormStateResponse {
|
|
160
|
-
ok: boolean
|
|
161
|
-
form?: ElementMeta
|
|
162
|
-
dirty?: string[]
|
|
163
|
-
errors?: Record<string, string[]>
|
|
164
|
-
error?: string
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export interface FormStateProviderProps {
|
|
168
|
-
/** Initial form meta from the server. The provider tracks subsequent
|
|
169
|
-
* replacements after live POSTs internally. */
|
|
170
|
-
initialMeta: ElementMeta
|
|
171
|
-
initialErrors: Record<string, string[]>
|
|
172
|
-
children: React.ReactNode
|
|
173
|
-
/** Optional override fetch — used in tests. Defaults to the global `fetch`. */
|
|
174
|
-
fetchImpl?: typeof fetch
|
|
175
|
-
/** Optional callback when the form meta is replaced after a live POST.
|
|
176
|
-
* Tests use this; production code reads from `useFormState().formMeta`. */
|
|
177
|
-
onMetaUpdate?: (meta: ElementMeta) => void
|
|
178
|
-
/** Plan #14 — ref to the wrapping `<form>` element. When set, live
|
|
179
|
-
* triggers snapshot the form's full DOM state via `FormData` before
|
|
180
|
-
* POSTing, which captures uncontrolled inner-Repeater inputs. The
|
|
181
|
-
* controlled `values` map is overlaid on top, then any explicit
|
|
182
|
-
* `valueOverride` from `triggerLive` wins last. */
|
|
183
|
-
formRef?: React.RefObject<HTMLFormElement | null>
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Provider component for the controlled form path. Holds the values map,
|
|
187
|
-
* the current form meta (replaced wholesale on live POST), and the
|
|
188
|
-
* per-field live trigger. Mounted by `FormRenderer` when the form has a
|
|
189
|
-
* `stateUrl` set (i.e. at least one descendant field is `live()`). */
|
|
190
|
-
export function FormStateProvider({
|
|
191
|
-
initialMeta,
|
|
192
|
-
initialErrors,
|
|
193
|
-
children,
|
|
194
|
-
fetchImpl,
|
|
195
|
-
onMetaUpdate,
|
|
196
|
-
formRef,
|
|
197
|
-
}: FormStateProviderProps): React.ReactElement {
|
|
198
|
-
const [formMeta, setFormMeta] = useState<ElementMeta>(initialMeta)
|
|
199
|
-
const [values, setValuesState] = useState<Record<string, unknown>>(
|
|
200
|
-
() => collectFieldDefaults(initialMeta),
|
|
201
|
-
)
|
|
202
|
-
const [errors, setErrors] = useState<Record<string, string[]>>(initialErrors)
|
|
203
|
-
const [pendingNames, setPendingNames] = useState<Set<string>>(() => new Set())
|
|
204
|
-
const [inFlight, setInFlight] = useState(false)
|
|
205
|
-
// Phase F.5 — per-Repeater/Builder row-array stash. Populated on
|
|
206
|
-
// collab mount when the binding implements F.5 row methods, cleared
|
|
207
|
-
// on unmount. Stored in state (not a ref) so consumers of
|
|
208
|
-
// `useRowBinding` re-render once the bindings land.
|
|
209
|
-
const [rowBindings, setRowBindings] = useState<ReadonlyMap<string, RowBindingApi> | null>(null)
|
|
210
|
-
|
|
211
|
-
const { notify } = useToast()
|
|
212
|
-
|
|
213
|
-
// Track an incrementing in-flight id so out-of-order responses are dropped.
|
|
214
|
-
// useRef (not useState) so React StrictMode dev double-invokes don't
|
|
215
|
-
// produce stale closures over `inFlightId`.
|
|
216
|
-
// See feedback_strict_mode_double_flash.md for the same pattern reasoning.
|
|
217
|
-
const requestSeqRef = useRef(0)
|
|
218
|
-
const latestSeenRef = useRef(0)
|
|
219
|
-
|
|
220
|
-
// Per-field debounce timers. Mutating refs not state — never trigger a
|
|
221
|
-
// re-render; just hold the timeout handle.
|
|
222
|
-
const debounceTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map())
|
|
223
|
-
useEffect(() => () => {
|
|
224
|
-
for (const t of debounceTimersRef.current.values()) clearTimeout(t)
|
|
225
|
-
debounceTimersRef.current.clear()
|
|
226
|
-
}, [])
|
|
227
|
-
|
|
228
|
-
// Always-current values ref so debounced/blur callbacks read the latest
|
|
229
|
-
// map without needing to be re-created on every keystroke.
|
|
230
|
-
const valuesRef = useRef(values)
|
|
231
|
-
useEffect(() => { valuesRef.current = values }, [values])
|
|
232
|
-
|
|
233
|
-
// Resolve helper: read the current (synchronous) form meta. Used to look
|
|
234
|
-
// up a field's `live` config when its trigger fires.
|
|
235
|
-
const formMetaRef = useRef(formMeta)
|
|
236
|
-
useEffect(() => { formMetaRef.current = formMeta }, [formMeta])
|
|
237
|
-
|
|
238
|
-
const stateUrl = (formMeta as { stateUrl?: string })['stateUrl']
|
|
239
|
-
const formId = (formMeta as { formId?: string })['formId'] ?? ''
|
|
240
|
-
|
|
241
|
-
// Phase F2 — collab binding. When `<RecordCollabRoom>` is mounted up-tree
|
|
242
|
-
// AND `@pilotiq-pro/collab` registered a `FormCollabBinding` factory, we
|
|
243
|
-
// construct a binding for this form, lift any already-synced state on
|
|
244
|
-
// top of the SSR-rendered defaults, and proxy every local write through
|
|
245
|
-
// it. Remote writes flow back via `subscribe`. Outside a room (or with
|
|
246
|
-
// no factory registered), `bindingRef` stays null and the plain
|
|
247
|
-
// local-state path runs unchanged.
|
|
248
|
-
const collabRoom = useCollabRoom()
|
|
249
|
-
const bindingFactory = getFormCollabBinding()
|
|
250
|
-
const bindingRef = useRef<FormCollabBinding | null>(null)
|
|
251
|
-
|
|
252
|
-
useEffect(() => {
|
|
253
|
-
if (!collabRoom || !bindingFactory || !formId) return
|
|
254
|
-
|
|
255
|
-
const binding = bindingFactory({
|
|
256
|
-
room: collabRoom,
|
|
257
|
-
formId,
|
|
258
|
-
initial: valuesRef.current,
|
|
259
|
-
formMeta: formMetaRef.current,
|
|
260
|
-
})
|
|
261
|
-
bindingRef.current = binding
|
|
262
|
-
|
|
263
|
-
// Lift any state already in the room (subsequent joiners — first
|
|
264
|
-
// mover sees an empty snapshot here and the local SSR defaults
|
|
265
|
-
// stay authoritative). Shallow merge so fields the binding doesn't
|
|
266
|
-
// know about (defaults the seed skipped, dotted-path Repeater rows
|
|
267
|
-
// we don't sync in F2) survive.
|
|
268
|
-
const synced = binding.get()
|
|
269
|
-
if (Object.keys(synced).length > 0) {
|
|
270
|
-
setValuesState((prev) => ({ ...prev, ...synced }))
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Phase F.5 — build a `RowBindingApi` per top-level Repeater/Builder
|
|
274
|
-
// field when the binding implements all three lifecycle methods. The
|
|
275
|
-
// walk reads from formMeta (structural — fields exist regardless of
|
|
276
|
-
// whether the form has any rows yet); the API is then pre-bound to
|
|
277
|
-
// the array name so `RepeaterInput` calls `rb.add(rowId, …)` rather
|
|
278
|
-
// than `binding.addRow(name, rowId, …)`. Partial F.5 impls (e.g. a
|
|
279
|
-
// binding that has addRow but not reorderRows) skip the stash — the
|
|
280
|
-
// contract says all three or nothing.
|
|
281
|
-
if (binding.addRow && binding.removeRow && binding.reorderRows) {
|
|
282
|
-
const { addRow, removeRow, reorderRows, subscribeRows, getRowOrder } = binding
|
|
283
|
-
const arrayNames = collectRowArrayFieldNames(formMetaRef.current)
|
|
284
|
-
if (arrayNames.length > 0) {
|
|
285
|
-
const rowStash = new Map<string, RowBindingApi>()
|
|
286
|
-
for (const arrayName of arrayNames) {
|
|
287
|
-
rowStash.set(arrayName, {
|
|
288
|
-
add: (rowId, initial = {}) => addRow.call(binding, arrayName, rowId, initial),
|
|
289
|
-
remove: (rowId) => removeRow.call(binding, arrayName, rowId),
|
|
290
|
-
reorder: (newOrder) => reorderRows.call(binding, arrayName, newOrder),
|
|
291
|
-
// Partial F.5 impl: a binding may ship add/remove/reorder
|
|
292
|
-
// without `subscribeRows` (e.g. tests). Substitute a no-op
|
|
293
|
-
// subscription so renderer code stays uniform — the cleanup
|
|
294
|
-
// fn is still called on unmount but no events ever arrive.
|
|
295
|
-
subscribe: subscribeRows
|
|
296
|
-
? (fn) => subscribeRows.call(binding, arrayName, fn)
|
|
297
|
-
: () => () => {},
|
|
298
|
-
// `getRowOrder` is optional — bindings that ship row CRUD
|
|
299
|
-
// without a snapshot read (test stubs, older plugins) get a
|
|
300
|
-
// `[]` substitute. The renderer's reconciler treats empty as
|
|
301
|
-
// "no orphans known" and no-ops, which is the safest fallback.
|
|
302
|
-
current: getRowOrder
|
|
303
|
-
? () => getRowOrder.call(binding, arrayName)
|
|
304
|
-
: () => [],
|
|
305
|
-
})
|
|
306
|
-
}
|
|
307
|
-
setRowBindings(rowStash)
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Subscribe to remote changes. Local writes ALSO trigger this
|
|
312
|
-
// (Yjs observers fire on local transactions too) — the per-key
|
|
313
|
-
// Object.is short-circuit below collapses them into no-op renders.
|
|
314
|
-
const unsubscribe = binding.subscribe((snapshot) => {
|
|
315
|
-
setValuesState((prev) => {
|
|
316
|
-
let changed = false
|
|
317
|
-
const next: Record<string, unknown> = { ...prev }
|
|
318
|
-
for (const [k, v] of Object.entries(snapshot)) {
|
|
319
|
-
if (!Object.is(prev[k], v)) {
|
|
320
|
-
next[k] = v
|
|
321
|
-
changed = true
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return changed ? next : prev
|
|
325
|
-
})
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
// PK-switch Phase B — register a per-formId rename handler so
|
|
329
|
-
// `FormRenderer`'s submit-success path can re-key newly persisted
|
|
330
|
-
// relationship rows on the CRDT without us having to expose the
|
|
331
|
-
// binding through React context (FormRenderer lives outside this
|
|
332
|
-
// provider). No-op when the active binding skipped the optional
|
|
333
|
-
// `renameRow` method; the documented fallback is the submitter-only
|
|
334
|
-
// Phase A reconciler (other peers reload to converge). See
|
|
335
|
-
// `pilotiq-pro/docs/plans/repeater-relationship-pk-switch.md`.
|
|
336
|
-
const renameRow = binding.renameRow
|
|
337
|
-
const unregisterRename = renameRow
|
|
338
|
-
? registerRelationshipRenameHandler(formId, (renames) => {
|
|
339
|
-
for (const r of renames) {
|
|
340
|
-
renameRow.call(binding, r.field, r.old, r.new)
|
|
341
|
-
}
|
|
342
|
-
})
|
|
343
|
-
: () => {}
|
|
344
|
-
|
|
345
|
-
return () => {
|
|
346
|
-
unsubscribe()
|
|
347
|
-
unregisterRename()
|
|
348
|
-
binding.destroy()
|
|
349
|
-
bindingRef.current = null
|
|
350
|
-
setRowBindings(null)
|
|
351
|
-
}
|
|
352
|
-
// `valuesRef.current` is intentionally read once at mount — initial
|
|
353
|
-
// values seed the binding; subsequent edits flow through `setValue`
|
|
354
|
-
// and remote changes flow through `subscribe`.
|
|
355
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
356
|
-
}, [collabRoom, bindingFactory, formId])
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Tier-2 follow-up to Plan #5 — fire `Field.afterStateUpdatedJs(body)`
|
|
360
|
-
* if the named field declares one. Independent of `live()`: runs
|
|
361
|
-
* synchronously on every change, no debounce, no roundtrip.
|
|
362
|
-
*
|
|
363
|
-
* `$get / $set` proxy this provider's values map. Dotted-path names
|
|
364
|
-
* (Repeater / Builder rows) read + write nested. `$state` is the
|
|
365
|
-
* just-set value; `$get(thisField)` returns the same value (we
|
|
366
|
-
* overlay it onto the snapshot so the handler sees a consistent
|
|
367
|
-
* post-write view of the form). `$set` calls `setValuesState`
|
|
368
|
-
* directly so React rerenders affected controlled inputs without
|
|
369
|
-
* re-firing `live` / re-firing JS for the sibling (mirrors
|
|
370
|
-
* server-side `$set` semantics — a write is not a user change).
|
|
371
|
-
*/
|
|
372
|
-
const runFieldJs = useCallback((name: string, value: unknown): void => {
|
|
373
|
-
const fieldMeta = findFieldMeta(formMetaRef.current, name)
|
|
374
|
-
const body = (fieldMeta as { afterStateUpdatedJs?: string } | undefined)?.afterStateUpdatedJs
|
|
375
|
-
if (!body) return
|
|
376
|
-
|
|
377
|
-
// Snapshot the values map with the just-changed field overlaid so
|
|
378
|
-
// `$get(name)` is consistent with `$state`. Cheap shallow clone —
|
|
379
|
-
// only allocated when a JS handler is actually present.
|
|
380
|
-
const snapshot: Record<string, unknown> = { ...valuesRef.current }
|
|
381
|
-
if (name.includes('.')) writeNestedValue(snapshot, name, value)
|
|
382
|
-
else snapshot[name] = value
|
|
383
|
-
|
|
384
|
-
const $get = (n: string): unknown => {
|
|
385
|
-
if (n.includes('.')) return readNestedValue(snapshot, n)
|
|
386
|
-
return snapshot[n]
|
|
387
|
-
}
|
|
388
|
-
const $set = (n: string, v: unknown): void => {
|
|
389
|
-
if (n.includes('.')) {
|
|
390
|
-
setValuesState((prev) => {
|
|
391
|
-
const next = { ...prev }
|
|
392
|
-
writeNestedValue(next, n, v)
|
|
393
|
-
return next
|
|
394
|
-
})
|
|
395
|
-
return
|
|
396
|
-
}
|
|
397
|
-
setValuesState((prev) => {
|
|
398
|
-
if (Object.is(prev[n], v)) return prev
|
|
399
|
-
return { ...prev, [n]: v }
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
runJsHandler({ body, fieldName: name, state: value, $get, $set })
|
|
404
|
-
}, [])
|
|
405
|
-
|
|
406
|
-
const setValue = useCallback((name: string, value: unknown): void => {
|
|
407
|
-
setValuesState((prev) => {
|
|
408
|
-
if (Object.is(prev[name], value)) return prev
|
|
409
|
-
return { ...prev, [name]: value }
|
|
410
|
-
})
|
|
411
|
-
// Phase F2 / F.5 — proxy the write through the collab binding when
|
|
412
|
-
// active AND the field hasn't opted out via `.collab(false)`. Top-level
|
|
413
|
-
// fields ride `binding.set`. Row leaves (dotted paths matching
|
|
414
|
-
// `parseRowFieldPath`) route through `binding.setRow` when the
|
|
415
|
-
// binding implements F.5 — otherwise stay local-only (same posture
|
|
416
|
-
// as pre-F.5).
|
|
417
|
-
routeBindingWrite(bindingRef.current, formMetaRef.current, valuesRef.current, name, value)
|
|
418
|
-
// Fire the client-side JS hook synchronously after the state write.
|
|
419
|
-
// Dotted-name fields don't go through here (their setter is a no-op
|
|
420
|
-
// in `useFieldState`); they fire JS via `triggerLive` instead so we
|
|
421
|
-
// never double-fire for the same change.
|
|
422
|
-
runFieldJs(name, value)
|
|
423
|
-
}, [runFieldJs])
|
|
424
|
-
|
|
425
|
-
const performLivePost = useCallback(async (name: string, valueOverride?: unknown): Promise<void> => {
|
|
426
|
-
if (!stateUrl) return
|
|
427
|
-
const seq = ++requestSeqRef.current
|
|
428
|
-
setPendingNames((prev) => {
|
|
429
|
-
if (prev.has(name)) return prev
|
|
430
|
-
const next = new Set(prev)
|
|
431
|
-
next.add(name)
|
|
432
|
-
return next
|
|
433
|
-
})
|
|
434
|
-
setInFlight(true)
|
|
435
|
-
|
|
436
|
-
const doFetch = fetchImpl ?? fetch
|
|
437
|
-
try {
|
|
438
|
-
// Plan #14 — build the values payload:
|
|
439
|
-
// 1. Start from controlled values (top-level fields the user has
|
|
440
|
-
// typed into via FormStateProvider's setValue path).
|
|
441
|
-
// 2. If a form ref is set, overlay FormData-derived nested values
|
|
442
|
-
// so uncontrolled inputs (including inner Repeater fields)
|
|
443
|
-
// contribute their current DOM state.
|
|
444
|
-
// 3. If a valueOverride is provided, write it through to the
|
|
445
|
-
// target field's path — this wins last, so the leaf carries
|
|
446
|
-
// the just-typed value even if the snapshot races a debounce.
|
|
447
|
-
let payloadValues: Record<string, unknown> = { ...valuesRef.current }
|
|
448
|
-
const formEl = formRef?.current
|
|
449
|
-
if (formEl) {
|
|
450
|
-
const snapshot = parseFormDataToNested(new FormData(formEl))
|
|
451
|
-
payloadValues = { ...payloadValues, ...snapshot }
|
|
452
|
-
}
|
|
453
|
-
if (valueOverride !== undefined) {
|
|
454
|
-
writeNestedValue(payloadValues, name, valueOverride)
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
const res = await doFetch(stateUrl, {
|
|
458
|
-
method: 'POST',
|
|
459
|
-
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
460
|
-
body: JSON.stringify({ changed: name, values: payloadValues }),
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
// Drop stale responses — a newer request has already been issued.
|
|
464
|
-
if (seq < latestSeenRef.current) return
|
|
465
|
-
latestSeenRef.current = seq
|
|
466
|
-
|
|
467
|
-
const data = await res.json().catch(() => ({})) as FormStateResponse
|
|
468
|
-
|
|
469
|
-
if (!res.ok) {
|
|
470
|
-
if (res.status === 422 && data.errors) {
|
|
471
|
-
setErrors(data.errors)
|
|
472
|
-
} else {
|
|
473
|
-
notify({
|
|
474
|
-
type: 'error',
|
|
475
|
-
title: 'Form update failed',
|
|
476
|
-
...(data.error ? { body: data.error } : {}),
|
|
477
|
-
})
|
|
478
|
-
}
|
|
479
|
-
return
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (data.form) {
|
|
483
|
-
setFormMeta(data.form)
|
|
484
|
-
onMetaUpdate?.(data.form)
|
|
485
|
-
// Server may have $set'd sibling values — overlay them onto the
|
|
486
|
-
// current values map. Keep client-typed values intact for fields
|
|
487
|
-
// the server didn't touch.
|
|
488
|
-
const serverValues = (data.form as { values?: Record<string, unknown> }).values
|
|
489
|
-
if (serverValues) {
|
|
490
|
-
setValuesState((prev) => ({ ...prev, ...serverValues }))
|
|
491
|
-
// Phase F2 (Q2) / F.5 — derived fields propagate to peers via the
|
|
492
|
-
// collab binding so every client sees the auto-`slug` / etc. without
|
|
493
|
-
// each peer roundtripping the server. Row leaves route through
|
|
494
|
-
// `setRow` when the binding implements F.5; top-level fields ride
|
|
495
|
-
// `set`. The rowId lookup needs the freshest values — merge
|
|
496
|
-
// `valuesRef.current` with the server overlay so a row-id stamped
|
|
497
|
-
// by this very server-resolve response is visible to `rowIdAtIndex`.
|
|
498
|
-
const binding = bindingRef.current
|
|
499
|
-
if (binding) {
|
|
500
|
-
const lookupValues = { ...valuesRef.current, ...serverValues }
|
|
501
|
-
for (const [k, v] of Object.entries(serverValues)) {
|
|
502
|
-
// routeBindingWrite handles `.collab(false)` opt-out internally.
|
|
503
|
-
routeBindingWrite(binding, data.form, lookupValues, k, v)
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
setErrors({})
|
|
508
|
-
}
|
|
509
|
-
} catch (err) {
|
|
510
|
-
// Network / parse error — surface a toast but don't roll back values.
|
|
511
|
-
// Next keystroke will retry naturally.
|
|
512
|
-
notify({
|
|
513
|
-
type: 'error',
|
|
514
|
-
title: 'Form update failed',
|
|
515
|
-
body: err instanceof Error ? err.message : String(err),
|
|
516
|
-
})
|
|
517
|
-
} finally {
|
|
518
|
-
setPendingNames((prev) => {
|
|
519
|
-
if (!prev.has(name)) return prev
|
|
520
|
-
const next = new Set(prev)
|
|
521
|
-
next.delete(name)
|
|
522
|
-
return next
|
|
523
|
-
})
|
|
524
|
-
// Only clear inFlight when no other field is pending.
|
|
525
|
-
setPendingNames((prev) => {
|
|
526
|
-
setInFlight(prev.size > 0)
|
|
527
|
-
return prev
|
|
528
|
-
})
|
|
529
|
-
}
|
|
530
|
-
}, [stateUrl, fetchImpl, notify, onMetaUpdate, formRef])
|
|
531
|
-
|
|
532
|
-
const triggerLive = useCallback((name: string, valueOverride?: unknown): void => {
|
|
533
|
-
// Dotted-name fields (Repeater / Builder rows) bypass the controlled
|
|
534
|
-
// `setValue` path entirely — fire their JS hook here so a row-scoped
|
|
535
|
-
// afterStateUpdatedJs runs even without `live()`. Top-level fields
|
|
536
|
-
// already had JS dispatched via `setValue`; skip to avoid double-fire.
|
|
537
|
-
if (name.includes('.') && valueOverride !== undefined) {
|
|
538
|
-
runFieldJs(name, valueOverride)
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (!stateUrl) return
|
|
542
|
-
const fieldMeta = findFieldMeta(formMetaRef.current, name)
|
|
543
|
-
const liveCfg = fieldMeta?.['live']
|
|
544
|
-
if (!liveCfg) return
|
|
545
|
-
|
|
546
|
-
const opts = typeof liveCfg === 'object' ? liveCfg as { onBlur?: boolean; debounce?: number } : {}
|
|
547
|
-
const debounce = typeof opts.debounce === 'number' && opts.debounce > 0 ? opts.debounce : 0
|
|
548
|
-
|
|
549
|
-
// Clear any pending debounce for this name; the new event resets the timer.
|
|
550
|
-
const timers = debounceTimersRef.current
|
|
551
|
-
const prevTimer = timers.get(name)
|
|
552
|
-
if (prevTimer) clearTimeout(prevTimer)
|
|
553
|
-
|
|
554
|
-
if (debounce > 0) {
|
|
555
|
-
const t = setTimeout(() => {
|
|
556
|
-
timers.delete(name)
|
|
557
|
-
void performLivePost(name, valueOverride)
|
|
558
|
-
}, debounce)
|
|
559
|
-
timers.set(name, t)
|
|
560
|
-
return
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
void performLivePost(name, valueOverride)
|
|
564
|
-
}, [stateUrl, performLivePost])
|
|
565
|
-
|
|
566
|
-
const fieldStatus = useCallback((name: string): FieldStatus => {
|
|
567
|
-
return pendingNames.has(name) ? 'pending' : 'idle'
|
|
568
|
-
}, [pendingNames])
|
|
569
|
-
|
|
570
|
-
const applyErrors = useCallback((next: Record<string, string[]>): void => {
|
|
571
|
-
setErrors(next)
|
|
572
|
-
}, [])
|
|
573
|
-
|
|
574
|
-
const api = useMemo<FormStateApi>(() => ({
|
|
575
|
-
values,
|
|
576
|
-
setValue,
|
|
577
|
-
triggerLive,
|
|
578
|
-
errors,
|
|
579
|
-
applyErrors,
|
|
580
|
-
formMeta,
|
|
581
|
-
inFlight,
|
|
582
|
-
fieldStatus,
|
|
583
|
-
rowBindings,
|
|
584
|
-
}), [values, setValue, triggerLive, errors, applyErrors, formMeta, inFlight, fieldStatus, rowBindings])
|
|
585
|
-
|
|
586
|
-
return (
|
|
587
|
-
<FormStateContext.Provider value={api}>
|
|
588
|
-
{children}
|
|
589
|
-
</FormStateContext.Provider>
|
|
590
|
-
)
|
|
591
|
-
}
|