@pilotiq/pilotiq 0.23.1 → 0.24.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +91 -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/dist/actions/exportFactory.d.ts +10 -0
- package/dist/actions/exportFactory.d.ts.map +1 -1
- package/dist/actions/exportFactory.js +10 -0
- package/dist/actions/exportFactory.js.map +1 -1
- package/dist/react/CollabRoomContext.d.ts +5 -5
- package/dist/react/index.d.ts +0 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +0 -1
- package/dist/react/index.js.map +1 -1
- package/dist/routes/helpers.d.ts.map +1 -1
- package/dist/routes/helpers.js +6 -2
- package/dist/routes/helpers.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +12 -0
- package/dist/routes/relations.js.map +1 -1
- package/package.json +6 -1
- package/.turbo/turbo-build.log +0 -8
- package/CLAUDE.md +0 -265
- package/dist/react/useCollabSeed.d.ts +0 -23
- package/dist/react/useCollabSeed.d.ts.map +0 -1
- package/dist/react/useCollabSeed.js +0 -82
- package/dist/react/useCollabSeed.js.map +0 -1
- 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 -215
- 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 -195
- 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/useCollabSeed.ts +0 -86
- 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 -700
- package/src/routes/pages.ts +0 -175
- package/src/routes/panel.ts +0 -204
- package/src/routes/relations.ts +0 -1227
- 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,256 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import type { ElementMeta } from '../../schema/Element.js'
|
|
3
|
-
import { useFieldState } from '../FormStateContext.js'
|
|
4
|
-
import { useCollabRoom } from '../CollabRoomContext.js'
|
|
5
|
-
import { getCollabTextRenderer, type CollabTextRenderer } from '../CollabTextRendererRegistry.js'
|
|
6
|
-
import { useRowCoords } from '../RowCoordsContext.js'
|
|
7
|
-
import { parseRowFieldPath } from '../formStateHelpers.js'
|
|
8
|
-
import { Input } from '../ui/input.js'
|
|
9
|
-
import { Textarea } from '../ui/textarea.js'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Bridge between controlled (FormStateProvider) and uncontrolled
|
|
13
|
-
* (defaultValue) modes for text-style inputs. When inside a form with
|
|
14
|
-
* `live()` fields, the input is bound to the context's values map and
|
|
15
|
-
* fires the live trigger on change/blur according to the field's `live`
|
|
16
|
-
* config. Outside a controlled form, falls back to plain `defaultValue`.
|
|
17
|
-
*
|
|
18
|
-
* **Collab branch — Tiptap-backed `Y.XmlFragment`.** When a
|
|
19
|
-
* `<RecordCollabRoom>` is mounted up-tree AND `@pilotiq/tiptap`'s
|
|
20
|
-
* `registerTiptap()` registered a collab text renderer, the input
|
|
21
|
-
* mounts the Tiptap-backed editor against a `Y.XmlFragment` keyed by
|
|
22
|
-
* either the bare field name (top-level) or
|
|
23
|
-
* `${arrayName}.${rowId}.${fieldName}` (Repeater / Builder row leaves
|
|
24
|
-
* via `useRowCoords()`). Selections anchor to `Y.RelativePosition` via
|
|
25
|
-
* y-prosemirror, so cursors survive both mid-word remote edits and
|
|
26
|
-
* concurrent inserts. Masked fields fall through to the legacy
|
|
27
|
-
* whole-string LWW path (mask + character-level CRDT is incompatible
|
|
28
|
-
* — peers would see raw keystrokes desynced from the rendered mask).
|
|
29
|
-
*/
|
|
30
|
-
export function TextLikeInput({
|
|
31
|
-
el, name, common, type, extraProps, multiline, applyMask,
|
|
32
|
-
}: {
|
|
33
|
-
el: ElementMeta
|
|
34
|
-
name: string
|
|
35
|
-
common: Record<string, unknown>
|
|
36
|
-
type: string
|
|
37
|
-
extraProps: Record<string, unknown>
|
|
38
|
-
multiline: boolean
|
|
39
|
-
/** Optional keystroke formatter — `TextField.mask(pattern)`. When
|
|
40
|
-
* set, every change runs the value through this fn before it lands
|
|
41
|
-
* in state / the DOM. Defaults to identity. */
|
|
42
|
-
applyMask?: (value: string) => string
|
|
43
|
-
}): React.ReactElement {
|
|
44
|
-
const fs = useFieldState(name)
|
|
45
|
-
const room = useCollabRoom()
|
|
46
|
-
const collabRenderer = getCollabTextRenderer()
|
|
47
|
-
const rowCoords = useRowCoords()
|
|
48
|
-
const liveCfg = el['live']
|
|
49
|
-
const liveOpts = (typeof liveCfg === 'object' && liveCfg !== null
|
|
50
|
-
? liveCfg as { onBlur?: boolean; debounce?: number }
|
|
51
|
-
: {})
|
|
52
|
-
const onBlurMode = liveOpts.onBlur === true
|
|
53
|
-
const mask = applyMask ?? identity
|
|
54
|
-
|
|
55
|
-
// Masking is mutually exclusive with character-level CRDT (peers would
|
|
56
|
-
// see raw keystrokes diverged from the local mask render); masked
|
|
57
|
-
// fields fall through to LWW. We read the mask from the field meta
|
|
58
|
-
// directly — `applyMask` is a `useCallback`-wrapped fn that's *always*
|
|
59
|
-
// defined (identity when no mask), so its truthiness can't gate the
|
|
60
|
-
// branch.
|
|
61
|
-
const hasMask = typeof el['mask'] === 'string'
|
|
62
|
-
|
|
63
|
-
// Collab branch — Tiptap-backed plain-text editor. Top-level fields
|
|
64
|
-
// use the bare `name` as the fragment-key; Repeater / Builder row
|
|
65
|
-
// leaves compose `${arrayName}.${rowId}.${fieldName}` from
|
|
66
|
-
// `useRowCoords()` so the Y.XmlFragment survives row reorders (keyed
|
|
67
|
-
// by the stable rowId, not the array index). The hidden FormData
|
|
68
|
-
// input keeps the original dotted path so submission lands on the
|
|
69
|
-
// server at the right slot.
|
|
70
|
-
//
|
|
71
|
-
// Dotted paths that don't match a row shape (no rowCoords OR
|
|
72
|
-
// `parseRowFieldPath` returns null — nested row arrays, malformed
|
|
73
|
-
// names) skip the collab path and fall through to the controlled /
|
|
74
|
-
// uncontrolled branches below.
|
|
75
|
-
const fieldCollab = el['collab'] as boolean | undefined
|
|
76
|
-
// Auto-upgrade to the Tiptap-backed editor whenever the field has AI
|
|
77
|
-
// agents attached, even outside collab — the inline-diff chip widget
|
|
78
|
-
// (red strikethrough on the current value + green chip with the suggested
|
|
79
|
-
// text + ✓/✕) needs a real ProseMirror surface to render. The renderer
|
|
80
|
-
// handles `useCollabRoom() === null` cleanly (mounts the editor without
|
|
81
|
-
// the Yjs Collaboration extension), so this widening doesn't force a
|
|
82
|
-
// collab room.
|
|
83
|
-
//
|
|
84
|
-
// `field.ai([…])` from `@pilotiq-pro/ai` lands on `FieldMeta.aiActions`
|
|
85
|
-
// as a resolved `PilotiqAgentMeta[]`. Read the array; non-empty means
|
|
86
|
-
// "this field has AI surfaces wired".
|
|
87
|
-
const aiActions = el['aiActions']
|
|
88
|
-
const hasAi = Array.isArray(aiActions) && aiActions.length > 0
|
|
89
|
-
const fragmentKey: string | null = (() => {
|
|
90
|
-
if (!name.includes('.')) return name
|
|
91
|
-
if (!rowCoords) return null
|
|
92
|
-
const parsed = parseRowFieldPath(name)
|
|
93
|
-
if (!parsed) return null
|
|
94
|
-
if (parsed.arrayName !== rowCoords.arrayName) return null
|
|
95
|
-
if (parsed.index !== rowCoords.rowIndex) return null
|
|
96
|
-
return `${rowCoords.arrayName}.${rowCoords.rowId}.${parsed.fieldName}`
|
|
97
|
-
})()
|
|
98
|
-
if (
|
|
99
|
-
(room || hasAi) &&
|
|
100
|
-
collabRenderer &&
|
|
101
|
-
fieldCollab !== false &&
|
|
102
|
-
!hasMask &&
|
|
103
|
-
fragmentKey !== null
|
|
104
|
-
) {
|
|
105
|
-
return (
|
|
106
|
-
<CollabTextField
|
|
107
|
-
Renderer={collabRenderer}
|
|
108
|
-
fragmentKey={fragmentKey}
|
|
109
|
-
hiddenInputName={name}
|
|
110
|
-
multiline={multiline}
|
|
111
|
-
defaultValue={stringValue(common['defaultValue'])}
|
|
112
|
-
{...(common['placeholder'] !== undefined ? { placeholder: String(common['placeholder']) } : {})}
|
|
113
|
-
disabled={Boolean(common['disabled'])}
|
|
114
|
-
triggerLive={fs.triggerLive}
|
|
115
|
-
setValue={fs.setValue}
|
|
116
|
-
controlled={fs.controlled}
|
|
117
|
-
onBlurMode={onBlurMode}
|
|
118
|
-
/>
|
|
119
|
-
)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (fs.controlled) {
|
|
123
|
-
const ctxValue = fs.value !== undefined && fs.value !== null ? String(fs.value) : ''
|
|
124
|
-
const onChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
|
125
|
-
const formatted = mask(e.target.value)
|
|
126
|
-
fs.setValue(formatted)
|
|
127
|
-
if (!onBlurMode) fs.triggerLive()
|
|
128
|
-
}
|
|
129
|
-
const onBlur = (): void => {
|
|
130
|
-
if (onBlurMode) fs.triggerLive()
|
|
131
|
-
}
|
|
132
|
-
const props = {
|
|
133
|
-
...common,
|
|
134
|
-
...extraProps,
|
|
135
|
-
defaultValue: undefined,
|
|
136
|
-
value: ctxValue,
|
|
137
|
-
onChange,
|
|
138
|
-
onBlur,
|
|
139
|
-
}
|
|
140
|
-
if (multiline) return <Textarea {...(props as React.ComponentProps<typeof Textarea>)} />
|
|
141
|
-
return <Input {...(props as React.ComponentProps<typeof Input>)} type={type} />
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Uncontrolled path with mask: wire onInput so the user sees the
|
|
145
|
-
// formatted value as they type. Without `applyMask`, fall through to
|
|
146
|
-
// the legacy bare-defaultValue render so the DOM stays unchanged.
|
|
147
|
-
if (applyMask) {
|
|
148
|
-
const onInput = (e: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
|
|
149
|
-
const target = e.currentTarget
|
|
150
|
-
target.value = mask(target.value)
|
|
151
|
-
}
|
|
152
|
-
if (multiline) return <Textarea {...(common as React.ComponentProps<typeof Textarea>)} {...extraProps} onInput={onInput} />
|
|
153
|
-
return <Input {...(common as React.ComponentProps<typeof Input>)} type={type} {...extraProps} onInput={onInput} />
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (multiline) return <Textarea {...(common as React.ComponentProps<typeof Textarea>)} {...extraProps} />
|
|
157
|
-
return <Input {...(common as React.ComponentProps<typeof Input>)} type={type} {...extraProps} />
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Wrapper around the registered Tiptap-backed collab editor.
|
|
162
|
-
* Owns the local text mirror so the hidden `<input>` always carries the
|
|
163
|
-
* editor's current value for FormData submission. When `FormStateProvider`
|
|
164
|
-
* is mounted up-tree, also mirrors every update into the values map via
|
|
165
|
-
* `fs.setValue` so `$get/$set` computations and any Y.Map LWW path (kept
|
|
166
|
-
* for non-text consumers) stay in sync.
|
|
167
|
-
*
|
|
168
|
-
* No IME / cursor-preservation gymnastics in here — the underlying Tiptap
|
|
169
|
-
* editor handles composition natively and y-prosemirror anchors selections
|
|
170
|
-
* to `Yjs.RelativePosition`, so the cursor survives concurrent + mid-word
|
|
171
|
-
* remote edits without any client-side bookkeeping.
|
|
172
|
-
*
|
|
173
|
-
* `fragmentKey` and `hiddenInputName` diverge for row-text leaves (Phase
|
|
174
|
-
* 1 of collab-row-text-tiptap-backed.md): the renderer's Y.XmlFragment is
|
|
175
|
-
* keyed by `${arrayName}.${rowId}.${fieldName}` so it survives row
|
|
176
|
-
* reorders, while the hidden FormData input keeps the dotted path
|
|
177
|
-
* (`items.0.title`) so submission lands at the right server-side slot.
|
|
178
|
-
* For top-level fields the two are identical.
|
|
179
|
-
*/
|
|
180
|
-
function CollabTextField({
|
|
181
|
-
Renderer, fragmentKey, hiddenInputName, multiline, defaultValue, placeholder, disabled,
|
|
182
|
-
triggerLive, setValue, controlled, onBlurMode,
|
|
183
|
-
}: {
|
|
184
|
-
Renderer: CollabTextRenderer
|
|
185
|
-
fragmentKey: string
|
|
186
|
-
hiddenInputName: string
|
|
187
|
-
multiline: boolean
|
|
188
|
-
defaultValue: string
|
|
189
|
-
placeholder?: string
|
|
190
|
-
disabled: boolean
|
|
191
|
-
triggerLive: (valueOverride?: unknown) => void
|
|
192
|
-
setValue: (v: unknown) => void
|
|
193
|
-
controlled: boolean
|
|
194
|
-
onBlurMode: boolean
|
|
195
|
-
}): React.ReactElement {
|
|
196
|
-
const [text, setText] = useState<string>(defaultValue)
|
|
197
|
-
const textRef = useRef(text)
|
|
198
|
-
useEffect(() => { textRef.current = text }, [text])
|
|
199
|
-
|
|
200
|
-
const handleChange = useCallback((next: string): void => {
|
|
201
|
-
setText(next)
|
|
202
|
-
if (controlled) setValue(next)
|
|
203
|
-
if (!onBlurMode) triggerLive(next)
|
|
204
|
-
}, [controlled, onBlurMode, setValue, triggerLive])
|
|
205
|
-
|
|
206
|
-
const handleBlur = useCallback((): void => {
|
|
207
|
-
if (onBlurMode) triggerLive(textRef.current)
|
|
208
|
-
}, [onBlurMode, triggerLive])
|
|
209
|
-
|
|
210
|
-
// Match the visual chrome of `<Input>` / `<Textarea>` so the editor reads
|
|
211
|
-
// as a drop-in replacement. The adapter forwards this class to its
|
|
212
|
-
// contenteditable wrapper; `whitespace-nowrap` on the single-line variant
|
|
213
|
-
// keeps the editor from wrapping into a second line if a stray paragraph
|
|
214
|
-
// split somehow makes it through.
|
|
215
|
-
//
|
|
216
|
-
// `overflow-x-clip` (not `auto`) on the single-line variant matters for
|
|
217
|
-
// `CollaborationCaret` presence labels: per the CSS overflow spec, setting
|
|
218
|
-
// either axis to a non-visible / non-clip value (`auto` / `scroll` /
|
|
219
|
-
// `hidden`) forces the other axis to compute as `auto` too — so
|
|
220
|
-
// `overflow-x-auto` would clip the caret's user-name label, which renders
|
|
221
|
-
// `-1.4em` above the line. `clip` is the one non-visible value that does
|
|
222
|
-
// NOT force the other axis, so `overflow-y` stays `visible` and the label
|
|
223
|
-
// escapes the chrome upward as designed. Trade-off: long text gets clipped
|
|
224
|
-
// on the right rather than horizontally scrollable (native `<input>`
|
|
225
|
-
// semantics) — acceptable for plain-text fields, where typing past the
|
|
226
|
-
// visible width is rare and the caret presence label is the higher-value
|
|
227
|
-
// affordance.
|
|
228
|
-
const className = multiline
|
|
229
|
-
? 'flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm whitespace-pre-wrap break-words'
|
|
230
|
-
: 'flex h-9 w-full items-center rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm whitespace-nowrap overflow-x-clip'
|
|
231
|
-
|
|
232
|
-
return (
|
|
233
|
-
<>
|
|
234
|
-
<input type="hidden" name={hiddenInputName} value={text} />
|
|
235
|
-
<Renderer
|
|
236
|
-
name={hiddenInputName}
|
|
237
|
-
{...(fragmentKey !== hiddenInputName ? { fragmentKey } : {})}
|
|
238
|
-
multiline={multiline}
|
|
239
|
-
defaultValue={defaultValue}
|
|
240
|
-
{...(placeholder !== undefined ? { placeholder } : {})}
|
|
241
|
-
disabled={disabled}
|
|
242
|
-
onChange={handleChange}
|
|
243
|
-
onBlur={handleBlur}
|
|
244
|
-
className={className}
|
|
245
|
-
/>
|
|
246
|
-
</>
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function identity(v: string): string { return v }
|
|
251
|
-
|
|
252
|
-
function stringValue(v: unknown): string {
|
|
253
|
-
if (v === undefined || v === null) return ''
|
|
254
|
-
if (typeof v === 'string') return v
|
|
255
|
-
return String(v)
|
|
256
|
-
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { useFieldState } from '../FormStateContext.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Single-choice field rendered as a horizontal row of pill-shaped
|
|
6
|
-
* buttons styled like a segmented control. Submits via a hidden input
|
|
7
|
-
* carrying the active value (form submission contract identical to
|
|
8
|
-
* `RadioInput`).
|
|
9
|
-
*
|
|
10
|
-
* Controlled when inside a `FormStateProvider`, uncontrolled otherwise.
|
|
11
|
-
*/
|
|
12
|
-
export function ToggleButtonsInput({
|
|
13
|
-
name, defaultValue, disabled, options,
|
|
14
|
-
}: {
|
|
15
|
-
name: string
|
|
16
|
-
defaultValue: string | undefined
|
|
17
|
-
disabled: boolean
|
|
18
|
-
options: Array<{ value: string; label: string; disabled?: boolean }>
|
|
19
|
-
}): React.ReactElement {
|
|
20
|
-
const fs = useFieldState(name)
|
|
21
|
-
const [localValue, setLocalValue] = useState<string>(defaultValue ?? '')
|
|
22
|
-
const value = fs.controlled
|
|
23
|
-
? (fs.value !== undefined && fs.value !== null ? String(fs.value) : '')
|
|
24
|
-
: localValue
|
|
25
|
-
const onPick = (next: string): void => {
|
|
26
|
-
if (disabled) return
|
|
27
|
-
if (fs.controlled) { fs.setValue(next); fs.triggerLive(next) }
|
|
28
|
-
else { setLocalValue(next); fs.triggerLive(next) }
|
|
29
|
-
}
|
|
30
|
-
return (
|
|
31
|
-
<div role="radiogroup" className="inline-flex flex-wrap rounded-md border border-input bg-background overflow-hidden">
|
|
32
|
-
<input type="hidden" name={name} value={value} />
|
|
33
|
-
{options.map((o, i) => {
|
|
34
|
-
const active = value === o.value
|
|
35
|
-
const optDisabled = disabled || Boolean(o.disabled)
|
|
36
|
-
return (
|
|
37
|
-
<button
|
|
38
|
-
key={o.value}
|
|
39
|
-
type="button"
|
|
40
|
-
role="radio"
|
|
41
|
-
aria-checked={active}
|
|
42
|
-
disabled={optDisabled}
|
|
43
|
-
data-state={active ? 'on' : 'off'}
|
|
44
|
-
onClick={() => { if (!optDisabled) onPick(o.value) }}
|
|
45
|
-
className={
|
|
46
|
-
'px-3 py-1.5 text-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ring ' +
|
|
47
|
-
(i > 0 ? 'border-l border-input ' : '') +
|
|
48
|
-
(active
|
|
49
|
-
? 'bg-primary text-primary-foreground hover:bg-primary/90 '
|
|
50
|
-
: 'bg-transparent hover:bg-accent hover:text-accent-foreground ') +
|
|
51
|
-
(optDisabled ? 'opacity-50 cursor-not-allowed ' : 'cursor-pointer ')
|
|
52
|
-
}
|
|
53
|
-
>
|
|
54
|
-
{o.label}
|
|
55
|
-
</button>
|
|
56
|
-
)
|
|
57
|
-
})}
|
|
58
|
-
</div>
|
|
59
|
-
)
|
|
60
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { useFieldState, FormIdContext } from '../FormStateContext.js'
|
|
3
|
-
import { registerPendingSuggestionApplier, type PendingSuggestionApplier } from '../PendingSuggestionApplierRegistry.js'
|
|
4
|
-
import { Switch } from '../ui/switch.js'
|
|
5
|
-
|
|
6
|
-
export function ToggleFieldInput({
|
|
7
|
-
name, defaultChecked, disabled,
|
|
8
|
-
}: { name: string; defaultChecked: boolean; disabled: boolean }): React.ReactElement {
|
|
9
|
-
const fs = useFieldState(name)
|
|
10
|
-
const [localChecked, setLocalChecked] = useState(defaultChecked)
|
|
11
|
-
const checked = fs.controlled
|
|
12
|
-
? (fs.value === true || fs.value === 'true' || fs.value === 1 || fs.value === '1')
|
|
13
|
-
: localChecked
|
|
14
|
-
// Base UI Switch's onCheckedChange callback does NOT dispatch a native
|
|
15
|
-
// bubbling change event, so RepeaterInput's container-level delegate
|
|
16
|
-
// can't pick up inner-row live() triggers. Call triggerLive explicitly
|
|
17
|
-
// in BOTH paths — the function is a no-op outside FormStateProvider
|
|
18
|
-
// and when the field has no `live` config, so it's safe to fire
|
|
19
|
-
// unconditionally. The value is passed as a `valueOverride` so
|
|
20
|
-
// dotted-path inner-Repeater fields pass through correctly.
|
|
21
|
-
const onChange = (next: boolean): void => {
|
|
22
|
-
if (fs.controlled) { fs.setValue(next); fs.triggerLive(next) }
|
|
23
|
-
else { setLocalChecked(next); fs.triggerLive(next) }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Cross-tree applier — Switch state lives in React, not in the hidden
|
|
27
|
-
// mirror input below. FieldShell's generic DOM-write applier would
|
|
28
|
-
// dispatch a change on the hidden input, but the visible Switch has
|
|
29
|
-
// no listener for it, so the toggle wouldn't flip. FieldShell skips
|
|
30
|
-
// its generic registration for fieldType === 'toggle'.
|
|
31
|
-
const fsRef = useRef(fs)
|
|
32
|
-
useEffect(() => { fsRef.current = fs }, [fs])
|
|
33
|
-
const formId = useContext(FormIdContext) || undefined
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (name.includes('.')) return
|
|
36
|
-
const applier: PendingSuggestionApplier = (suggestion) => {
|
|
37
|
-
const v = suggestion.suggestedValue
|
|
38
|
-
const next = v === true || v === 'true' || v === 1 || v === '1'
|
|
39
|
-
const cur = fsRef.current
|
|
40
|
-
if (cur.controlled) { cur.setValue(next); cur.triggerLive(next) }
|
|
41
|
-
else { setLocalChecked(next); cur.triggerLive(next) }
|
|
42
|
-
}
|
|
43
|
-
return registerPendingSuggestionApplier(formId, name, applier)
|
|
44
|
-
}, [name, formId])
|
|
45
|
-
return (
|
|
46
|
-
<div className="flex items-center gap-2">
|
|
47
|
-
<input type="hidden" name={name} value={checked ? 'true' : 'false'} />
|
|
48
|
-
<Switch
|
|
49
|
-
id={name}
|
|
50
|
-
checked={checked}
|
|
51
|
-
onCheckedChange={onChange}
|
|
52
|
-
disabled={disabled}
|
|
53
|
-
/>
|
|
54
|
-
</div>
|
|
55
|
-
)
|
|
56
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
applyRelationshipRenames,
|
|
6
|
-
registerRelationshipRenameHandler,
|
|
7
|
-
_resetRelationshipRenameRegistryForTests,
|
|
8
|
-
type RelationshipRenameEntry,
|
|
9
|
-
type RelationshipRenameHandler,
|
|
10
|
-
} from './relationshipRenameDispatch.js'
|
|
11
|
-
|
|
12
|
-
describe('relationshipRenameDispatch', () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
_resetRelationshipRenameRegistryForTests()
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it('routes a rename through the registered handler for its formId', () => {
|
|
18
|
-
const seen: ReadonlyArray<RelationshipRenameEntry>[] = []
|
|
19
|
-
registerRelationshipRenameHandler('form-1', (renames) => { seen.push(renames) })
|
|
20
|
-
|
|
21
|
-
applyRelationshipRenames('form-1', [
|
|
22
|
-
{ field: 'comments', old: 'uuid-foo', new: '42' },
|
|
23
|
-
])
|
|
24
|
-
|
|
25
|
-
assert.equal(seen.length, 1)
|
|
26
|
-
assert.deepEqual(seen[0], [{ field: 'comments', old: 'uuid-foo', new: '42' }])
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('isolates handlers across formIds — multi-form pages do not cross-fire', () => {
|
|
30
|
-
const a: RelationshipRenameEntry[][] = []
|
|
31
|
-
const b: RelationshipRenameEntry[][] = []
|
|
32
|
-
registerRelationshipRenameHandler('form-a', (r) => { a.push([...r]) })
|
|
33
|
-
registerRelationshipRenameHandler('form-b', (r) => { b.push([...r]) })
|
|
34
|
-
|
|
35
|
-
applyRelationshipRenames('form-a', [{ field: 'x', old: 'u', new: '1' }])
|
|
36
|
-
|
|
37
|
-
assert.equal(a.length, 1)
|
|
38
|
-
assert.equal(b.length, 0)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('cleanup unregisters the handler', () => {
|
|
42
|
-
let calls = 0
|
|
43
|
-
const off = registerRelationshipRenameHandler('form-1', () => { calls += 1 })
|
|
44
|
-
off()
|
|
45
|
-
applyRelationshipRenames('form-1', [{ field: 'x', old: 'u', new: '1' }])
|
|
46
|
-
assert.equal(calls, 0)
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
it("cleanup does NOT wipe a handler that another caller replaced (StrictMode-safe)", () => {
|
|
50
|
-
// StrictMode dev double-mount: provider A mounts, registers fn1; React
|
|
51
|
-
// schedules cleanup; provider A's effect re-runs and registers fn2; THEN
|
|
52
|
-
// the cleanup of the first effect fires. fn2 must survive.
|
|
53
|
-
let calls1 = 0
|
|
54
|
-
let calls2 = 0
|
|
55
|
-
const fn1: RelationshipRenameHandler = () => { calls1 += 1 }
|
|
56
|
-
const fn2: RelationshipRenameHandler = () => { calls2 += 1 }
|
|
57
|
-
|
|
58
|
-
const off1 = registerRelationshipRenameHandler('form-1', fn1)
|
|
59
|
-
registerRelationshipRenameHandler('form-1', fn2)
|
|
60
|
-
off1()
|
|
61
|
-
|
|
62
|
-
applyRelationshipRenames('form-1', [{ field: 'x', old: 'u', new: '1' }])
|
|
63
|
-
assert.equal(calls1, 0)
|
|
64
|
-
assert.equal(calls2, 1, 'second registration survived the first cleanup')
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('apply with no handler registered is a silent no-op', () => {
|
|
68
|
-
// The success path always fires apply; consumers without a collab
|
|
69
|
-
// plugin shouldn't see any error.
|
|
70
|
-
assert.doesNotThrow(() => {
|
|
71
|
-
applyRelationshipRenames('form-unknown', [{ field: 'x', old: 'u', new: '1' }])
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('apply with empty or undefined rename list short-circuits', () => {
|
|
76
|
-
let calls = 0
|
|
77
|
-
registerRelationshipRenameHandler('form-1', () => { calls += 1 })
|
|
78
|
-
|
|
79
|
-
applyRelationshipRenames('form-1', [])
|
|
80
|
-
applyRelationshipRenames('form-1', undefined)
|
|
81
|
-
|
|
82
|
-
assert.equal(calls, 0)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('apply with empty formId is a no-op', () => {
|
|
86
|
-
let calls = 0
|
|
87
|
-
registerRelationshipRenameHandler('', () => { calls += 1 })
|
|
88
|
-
applyRelationshipRenames('', [{ field: 'x', old: 'u', new: '1' }])
|
|
89
|
-
assert.equal(calls, 0)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
it('register returns a stub cleanup when formId is empty (no crash)', () => {
|
|
93
|
-
const off = registerRelationshipRenameHandler('', () => {})
|
|
94
|
-
assert.doesNotThrow(off)
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
it('handler errors propagate so FormRenderer can surface a save-failed toast', () => {
|
|
98
|
-
registerRelationshipRenameHandler('form-1', () => {
|
|
99
|
-
throw new Error('binding wedged')
|
|
100
|
-
})
|
|
101
|
-
assert.throws(
|
|
102
|
-
() => applyRelationshipRenames('form-1', [{ field: 'x', old: 'u', new: '1' }]),
|
|
103
|
-
/binding wedged/,
|
|
104
|
-
)
|
|
105
|
-
})
|
|
106
|
-
})
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PK-switch Phase B — client-side dispatcher.
|
|
3
|
-
*
|
|
4
|
-
* The pilotiq server returns `relationshipRenames: { field, old, new }[]`
|
|
5
|
-
* in the JSON form-submit response whenever a `Repeater.relationship` /
|
|
6
|
-
* `Builder.relationship` create persisted under a DB-assigned PK that
|
|
7
|
-
* differs from the submitter's pre-assigned `__id` (see
|
|
8
|
-
* `pilotiq-pro/docs/plans/repeater-relationship-pk-switch.md`,
|
|
9
|
-
* `pilotiq/src/elements/dispatchForm.ts` for the wire shape).
|
|
10
|
-
*
|
|
11
|
-
* The renames need to land on the form's collab binding so other peers
|
|
12
|
-
* see the CRDT row re-keyed from UUID → PK without reloading. But the
|
|
13
|
-
* binding lives inside `FormStateProvider` (it owns `bindingRef`), while
|
|
14
|
-
* the JSON success path lives in `FormRenderer`'s `onSubmit` — a sibling
|
|
15
|
-
* component, not a context consumer. We bridge them with a per-`formId`
|
|
16
|
-
* module-level registry: `FormStateProvider` registers a handler when
|
|
17
|
-
* its binding mounts; `FormRenderer` dispatches against it after a
|
|
18
|
-
* successful submit.
|
|
19
|
-
*
|
|
20
|
-
* Pattern parallels `repeaterReconcile.ts`'s sessionStorage flag — same
|
|
21
|
-
* formId-keyed seam, different storage. SessionStorage was right for
|
|
22
|
-
* Phase A (the flag has to survive a navigation between submit and
|
|
23
|
-
* next mount); Phase B has to fire BEFORE the navigation, so a plain
|
|
24
|
-
* in-memory Map is the right shape (no SSR / cross-tab concerns).
|
|
25
|
-
*
|
|
26
|
-
* No-op when no handler is registered (consumer has no collab plugin,
|
|
27
|
-
* or the active binding doesn't implement `renameRow`).
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* One UUID → PK rename emitted by the server. Shape mirrors
|
|
32
|
-
* `pilotiq/src/elements/dispatchForm.ts` `RelationshipRename`; we
|
|
33
|
-
* duplicate the shape here so this module stays free of server-only
|
|
34
|
-
* imports (would otherwise pull the form-submit pipeline into the
|
|
35
|
-
* client bundle).
|
|
36
|
-
*/
|
|
37
|
-
export interface RelationshipRenameEntry {
|
|
38
|
-
field: string
|
|
39
|
-
old: string
|
|
40
|
-
new: string
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type RelationshipRenameHandler = (
|
|
44
|
-
renames: ReadonlyArray<RelationshipRenameEntry>,
|
|
45
|
-
) => void
|
|
46
|
-
|
|
47
|
-
const handlers = new Map<string, RelationshipRenameHandler>()
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Called by `FormStateProvider` when its `FormCollabBinding` mounts AND
|
|
51
|
-
* implements `renameRow`. Returns the unsubscribe fn to call on
|
|
52
|
-
* unmount. Idempotent under re-registration on the same `formId` —
|
|
53
|
-
* later writers replace earlier handlers (Forms only mount one
|
|
54
|
-
* provider per id; a second mount means the first unmounted without
|
|
55
|
-
* firing its cleanup, which is acceptable to overwrite).
|
|
56
|
-
*/
|
|
57
|
-
export function registerRelationshipRenameHandler(
|
|
58
|
-
formId: string,
|
|
59
|
-
fn: RelationshipRenameHandler,
|
|
60
|
-
): () => void {
|
|
61
|
-
if (!formId) return () => {}
|
|
62
|
-
handlers.set(formId, fn)
|
|
63
|
-
return () => {
|
|
64
|
-
// Only clear when the current handler is still ours — protects
|
|
65
|
-
// against StrictMode dev double-mount where the cleanup of the
|
|
66
|
-
// first mount fires AFTER the second mount has installed its
|
|
67
|
-
// handler. Without this guard, the second mount's handler would
|
|
68
|
-
// be wiped before any submit completes.
|
|
69
|
-
if (handlers.get(formId) === fn) handlers.delete(formId)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Called by `FormRenderer`'s `onSubmit` success path. Invokes the
|
|
75
|
-
* formId's registered handler with the rename list, or no-ops when
|
|
76
|
-
* either side is empty.
|
|
77
|
-
*
|
|
78
|
-
* Errors thrown by the handler propagate — pilotiq's submit path
|
|
79
|
-
* already catches in a `try`, so a misbehaving binding fails the
|
|
80
|
-
* navigate cleanly with a toast rather than wedging the form.
|
|
81
|
-
*/
|
|
82
|
-
export function applyRelationshipRenames(
|
|
83
|
-
formId: string,
|
|
84
|
-
renames: ReadonlyArray<RelationshipRenameEntry> | undefined,
|
|
85
|
-
): void {
|
|
86
|
-
if (!formId) return
|
|
87
|
-
if (!renames || renames.length === 0) return
|
|
88
|
-
const fn = handlers.get(formId)
|
|
89
|
-
if (!fn) return
|
|
90
|
-
fn(renames)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Test seam — drop every registered handler. Not exported from the
|
|
94
|
-
* react barrel. */
|
|
95
|
-
export function _resetRelationshipRenameRegistryForTests(): void {
|
|
96
|
-
handlers.clear()
|
|
97
|
-
}
|