@pilotiq/pilotiq 0.24.1 → 0.24.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/boost/guidelines.md +571 -0
- package/boost/skills/pilotiq-actions/SKILL.md +49 -0
- package/boost/skills/pilotiq-actions/rules/dispatch-modes.md +177 -0
- package/boost/skills/pilotiq-actions/rules/factories.md +130 -0
- package/boost/skills/pilotiq-actions/rules/visibility-and-authorization.md +125 -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/Pilotiq.d.ts +31 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +3 -1
- package/dist/Pilotiq.js.map +1 -1
- package/dist/PilotiqRegistry.d.ts +13 -0
- package/dist/PilotiqRegistry.d.ts.map +1 -1
- package/dist/PilotiqRegistry.js +15 -0
- package/dist/PilotiqRegistry.js.map +1 -1
- package/dist/pageData/misc.d.ts.map +1 -1
- package/dist/pageData/misc.js +6 -0
- package/dist/pageData/misc.js.map +1 -1
- package/dist/pageData/navigation.d.ts +1 -0
- package/dist/pageData/navigation.d.ts.map +1 -1
- package/dist/pageData/navigation.js +3 -0
- package/dist/pageData/navigation.js.map +1 -1
- package/dist/pageData/relationPages.d.ts.map +1 -1
- package/dist/pageData/relationPages.js +3 -0
- package/dist/pageData/relationPages.js.map +1 -1
- package/dist/pageData/resourcePages.d.ts.map +1 -1
- package/dist/pageData/resourcePages.js +8 -0
- package/dist/pageData/resourcePages.js.map +1 -1
- package/dist/react/AppShell.d.ts +8 -0
- package/dist/react/AppShell.d.ts.map +1 -1
- package/dist/react/AppShell.js.map +1 -1
- package/dist/react/layouts/SidebarLayout.d.ts.map +1 -1
- package/dist/react/layouts/SidebarLayout.js +10 -2
- package/dist/react/layouts/SidebarLayout.js.map +1 -1
- package/dist/react/widgets/StatsOverviewRenderer.d.ts.map +1 -1
- package/dist/react/widgets/StatsOverviewRenderer.js +32 -18
- package/dist/react/widgets/StatsOverviewRenderer.js.map +1 -1
- package/dist/routes/relations.d.ts.map +1 -1
- package/dist/routes/relations.js +25 -18
- package/dist/routes/relations.js.map +1 -1
- package/dist/routes/resources.js.map +1 -1
- package/package.json +10 -5
- 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,280 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { PlusIcon } from 'lucide-react'
|
|
3
|
-
import type { ElementMeta } from '../../schema/Element.js'
|
|
4
|
-
import { useFieldState, FormIdContext } from '../FormStateContext.js'
|
|
5
|
-
import { registerPendingSuggestionApplier, type PendingSuggestionApplier } from '../PendingSuggestionApplierRegistry.js'
|
|
6
|
-
import { useToast } from '../Toaster.js'
|
|
7
|
-
import { renderFormChild } from '../SchemaRenderer.js'
|
|
8
|
-
import {
|
|
9
|
-
Dialog,
|
|
10
|
-
DialogContent,
|
|
11
|
-
DialogDescription,
|
|
12
|
-
DialogFooter,
|
|
13
|
-
DialogHeader,
|
|
14
|
-
DialogTitle,
|
|
15
|
-
} from '../ui/dialog.js'
|
|
16
|
-
import {
|
|
17
|
-
Select,
|
|
18
|
-
SelectContent,
|
|
19
|
-
SelectItem,
|
|
20
|
-
SelectTrigger,
|
|
21
|
-
SelectValue,
|
|
22
|
-
} from '../ui/select.js'
|
|
23
|
-
|
|
24
|
-
interface CreateOptionMeta {
|
|
25
|
-
formId: string
|
|
26
|
-
schema: ElementMeta[]
|
|
27
|
-
url?: string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface NewOption { value: string; label: string }
|
|
31
|
-
|
|
32
|
-
export function SelectFieldInput({
|
|
33
|
-
name, defaultValue, disabled, required, placeholder, options,
|
|
34
|
-
fieldLabel, createOption,
|
|
35
|
-
}: {
|
|
36
|
-
name: string
|
|
37
|
-
defaultValue: string | undefined
|
|
38
|
-
disabled: boolean
|
|
39
|
-
required: boolean
|
|
40
|
-
placeholder: string | undefined
|
|
41
|
-
options: Array<{ value: string; label: string; disabled?: boolean }>
|
|
42
|
-
fieldLabel?: string
|
|
43
|
-
createOption?: CreateOptionMeta
|
|
44
|
-
}): React.ReactElement {
|
|
45
|
-
const fs = useFieldState(name)
|
|
46
|
-
// Always-controlled. Initialize to '' (not undefined) so Base UI's Select
|
|
47
|
-
// doesn't see the value flip from undefined → string when the user picks
|
|
48
|
-
// an option (warns: "changing the uncontrolled value state to controlled").
|
|
49
|
-
const [localValue, setLocalValue] = useState<string>(defaultValue ?? '')
|
|
50
|
-
const value = fs.controlled
|
|
51
|
-
? (fs.value !== undefined && fs.value !== null ? String(fs.value) : '')
|
|
52
|
-
: localValue
|
|
53
|
-
// Locally-appended options from inline create. Survives until the next
|
|
54
|
-
// server-canonical re-resolve (live() roundtrip or full submit) — when
|
|
55
|
-
// that lands, the canonical `options(fn)` resolver wins. Until then, the
|
|
56
|
-
// newly-created option is part of the visible option list and round-trips
|
|
57
|
-
// through submit via the hidden input below.
|
|
58
|
-
const [extraOptions, setExtraOptions] = useState<NewOption[]>([])
|
|
59
|
-
const allOptions = extraOptions.length > 0
|
|
60
|
-
? [...options, ...extraOptions]
|
|
61
|
-
: options
|
|
62
|
-
// Base UI Select's onValueChange is a callback (no native bubbling
|
|
63
|
-
// change event). Inner-Repeater rows need the explicit triggerLive
|
|
64
|
-
// in both paths to fire `live()` re-resolve. Pass the new value as
|
|
65
|
-
// `valueOverride` so dotted-path inner fields hit the right slot.
|
|
66
|
-
const onValueChange = (v: string | null): void => {
|
|
67
|
-
const next = v ?? ''
|
|
68
|
-
if (fs.controlled) { fs.setValue(next); fs.triggerLive(next) }
|
|
69
|
-
else { setLocalValue(next); fs.triggerLive(next) }
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const onCreated = (option: NewOption): void => {
|
|
73
|
-
setExtraOptions(prev => [...prev, option])
|
|
74
|
-
if (fs.controlled) { fs.setValue(option.value); fs.triggerLive(option.value) }
|
|
75
|
-
else { setLocalValue(option.value); fs.triggerLive(option.value) }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Cross-tree applier registration. FieldShell's generic applier writes
|
|
79
|
-
// to the matching `[name]` input via the React prototype-descriptor
|
|
80
|
-
// setter — but Base UI Select isn't driven by the hidden `<input>` in
|
|
81
|
-
// this component, it's driven by `value`/`localValue` React state. So
|
|
82
|
-
// a DOM write moves the hidden input but leaves the visible select
|
|
83
|
-
// unchanged. Register a Select-aware applier that writes to local
|
|
84
|
-
// state instead. FieldShell skips the generic registration for
|
|
85
|
-
// fieldType === 'select' so this one stays the winner.
|
|
86
|
-
const fsRef = useRef(fs)
|
|
87
|
-
useEffect(() => { fsRef.current = fs }, [fs])
|
|
88
|
-
const formId = useContext(FormIdContext) || undefined
|
|
89
|
-
useEffect(() => {
|
|
90
|
-
if (name.includes('.')) return
|
|
91
|
-
const applier: PendingSuggestionApplier = (suggestion) => {
|
|
92
|
-
const next = suggestion.suggestedValue == null ? '' : String(suggestion.suggestedValue)
|
|
93
|
-
const cur = fsRef.current
|
|
94
|
-
if (cur.controlled) { cur.setValue(next); cur.triggerLive(next) }
|
|
95
|
-
else { setLocalValue(next); cur.triggerLive(next) }
|
|
96
|
-
}
|
|
97
|
-
return registerPendingSuggestionApplier(formId, name, applier)
|
|
98
|
-
}, [name, formId])
|
|
99
|
-
|
|
100
|
-
const showCreateTrigger = createOption !== undefined
|
|
101
|
-
&& typeof createOption.url === 'string'
|
|
102
|
-
&& createOption.url.length > 0
|
|
103
|
-
|
|
104
|
-
const select = (
|
|
105
|
-
<Select
|
|
106
|
-
value={value}
|
|
107
|
-
onValueChange={(v) => onValueChange(v as string)}
|
|
108
|
-
disabled={disabled}
|
|
109
|
-
required={required}
|
|
110
|
-
>
|
|
111
|
-
<SelectTrigger className="w-full" id={name}>
|
|
112
|
-
<SelectValue placeholder={placeholder ?? 'Select…'} />
|
|
113
|
-
</SelectTrigger>
|
|
114
|
-
<SelectContent>
|
|
115
|
-
{allOptions.map((o) => (
|
|
116
|
-
<SelectItem key={o.value} value={o.value} disabled={(o as { disabled?: boolean }).disabled}>
|
|
117
|
-
{o.label}
|
|
118
|
-
</SelectItem>
|
|
119
|
-
))}
|
|
120
|
-
</SelectContent>
|
|
121
|
-
</Select>
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
return (
|
|
125
|
-
<>
|
|
126
|
-
<input type="hidden" name={name} value={value} />
|
|
127
|
-
{showCreateTrigger ? (
|
|
128
|
-
<div className="flex items-center gap-2">
|
|
129
|
-
<div className="flex-1 min-w-0">{select}</div>
|
|
130
|
-
<CreateOptionTrigger
|
|
131
|
-
url={createOption!.url!}
|
|
132
|
-
schema={createOption!.schema}
|
|
133
|
-
fieldLabel={fieldLabel ?? name}
|
|
134
|
-
onCreated={onCreated}
|
|
135
|
-
/>
|
|
136
|
-
</div>
|
|
137
|
-
) : select}
|
|
138
|
-
</>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* "+" icon-button next to the Select trigger that opens a Dialog hosting
|
|
144
|
-
* the resolved `createOption.schema` as a controlled mini-form. Submit
|
|
145
|
-
* POSTs `{ values }` JSON to the stamped url; on success appends + selects
|
|
146
|
-
* the returned option.
|
|
147
|
-
*/
|
|
148
|
-
function CreateOptionTrigger({
|
|
149
|
-
url, schema, fieldLabel, onCreated,
|
|
150
|
-
}: {
|
|
151
|
-
url: string
|
|
152
|
-
schema: ElementMeta[]
|
|
153
|
-
fieldLabel: string
|
|
154
|
-
onCreated: (option: NewOption) => void
|
|
155
|
-
}): React.ReactElement {
|
|
156
|
-
const [open, setOpen] = useState(false)
|
|
157
|
-
const [errors, setErrors] = useState<Record<string, string[]>>({})
|
|
158
|
-
const [serverError, setServerError] = useState<string | null>(null)
|
|
159
|
-
const [submitting, setSubmitting] = useState(false)
|
|
160
|
-
const { notify } = useToast()
|
|
161
|
-
|
|
162
|
-
const reset = (): void => {
|
|
163
|
-
setErrors({})
|
|
164
|
-
setServerError(null)
|
|
165
|
-
setSubmitting(false)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const onSubmit = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
|
|
169
|
-
e.preventDefault()
|
|
170
|
-
setSubmitting(true)
|
|
171
|
-
setServerError(null)
|
|
172
|
-
setErrors({})
|
|
173
|
-
|
|
174
|
-
const fd = new FormData(e.currentTarget)
|
|
175
|
-
const values = formDataToValues(fd)
|
|
176
|
-
|
|
177
|
-
try {
|
|
178
|
-
const res = await fetch(url, {
|
|
179
|
-
method: 'POST',
|
|
180
|
-
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
|
|
181
|
-
body: JSON.stringify({ values }),
|
|
182
|
-
})
|
|
183
|
-
const data = await res.json().catch(() => ({})) as {
|
|
184
|
-
ok?: boolean
|
|
185
|
-
option?: NewOption
|
|
186
|
-
errors?: Record<string, string[]>
|
|
187
|
-
error?: string
|
|
188
|
-
}
|
|
189
|
-
if (res.status === 422) {
|
|
190
|
-
setErrors(data.errors ?? {})
|
|
191
|
-
setSubmitting(false)
|
|
192
|
-
return
|
|
193
|
-
}
|
|
194
|
-
if (!res.ok || !data.ok || !data.option) {
|
|
195
|
-
setServerError(String(data.error ?? `Request failed (${res.status})`))
|
|
196
|
-
setSubmitting(false)
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
onCreated(data.option)
|
|
200
|
-
setOpen(false)
|
|
201
|
-
reset()
|
|
202
|
-
notify({ type: 'success', title: `${fieldLabel} created` })
|
|
203
|
-
} catch (err) {
|
|
204
|
-
setServerError(err instanceof Error ? err.message : 'Submit failed')
|
|
205
|
-
setSubmitting(false)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const triggerClass = 'inline-flex items-center justify-center rounded-md border border-input bg-background h-9 w-9 shrink-0 hover:bg-accent hover:text-accent-foreground disabled:opacity-50'
|
|
210
|
-
const cancelClass = 'inline-flex items-center justify-center rounded-md border border-input bg-background px-3 h-9 text-sm font-medium hover:bg-accent hover:text-accent-foreground'
|
|
211
|
-
const submitClass = 'inline-flex items-center justify-center rounded-md bg-primary px-3 h-9 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50'
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<>
|
|
215
|
-
<button
|
|
216
|
-
type="button"
|
|
217
|
-
aria-label={`Create ${fieldLabel}`}
|
|
218
|
-
onClick={() => { reset(); setOpen(true) }}
|
|
219
|
-
className={triggerClass}
|
|
220
|
-
>
|
|
221
|
-
<PlusIcon className="size-4" />
|
|
222
|
-
</button>
|
|
223
|
-
<Dialog open={open} onOpenChange={(o) => { if (!o) reset(); setOpen(o) }}>
|
|
224
|
-
<DialogContent className="sm:max-w-lg">
|
|
225
|
-
<form onSubmit={onSubmit}>
|
|
226
|
-
<DialogHeader>
|
|
227
|
-
<DialogTitle>{`Create ${fieldLabel}`}</DialogTitle>
|
|
228
|
-
<DialogDescription className="sr-only">
|
|
229
|
-
{`Create a new ${fieldLabel} option to select.`}
|
|
230
|
-
</DialogDescription>
|
|
231
|
-
</DialogHeader>
|
|
232
|
-
{schema.length > 0 && (
|
|
233
|
-
<div className="flex flex-col gap-3 py-2">
|
|
234
|
-
{schema.map((child, i) => renderFormChild(child, i, {}, errors))}
|
|
235
|
-
</div>
|
|
236
|
-
)}
|
|
237
|
-
{serverError && (
|
|
238
|
-
<p className="py-2 text-sm text-destructive">{serverError}</p>
|
|
239
|
-
)}
|
|
240
|
-
<DialogFooter>
|
|
241
|
-
<button type="button" onClick={() => setOpen(false)} className={cancelClass}>
|
|
242
|
-
Cancel
|
|
243
|
-
</button>
|
|
244
|
-
<button type="submit" disabled={submitting} className={submitClass}>
|
|
245
|
-
{submitting ? 'Working…' : 'Create'}
|
|
246
|
-
</button>
|
|
247
|
-
</DialogFooter>
|
|
248
|
-
</form>
|
|
249
|
-
</DialogContent>
|
|
250
|
-
</Dialog>
|
|
251
|
-
</>
|
|
252
|
-
)
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Convert a FormData snapshot into the `{ name: value }` shape the
|
|
257
|
-
* `formCreateOptionData` builder expects for `coerceFormValues`.
|
|
258
|
-
*
|
|
259
|
-
* - Skips `_formId` / `_method` framework keys (a FormStateProvider above
|
|
260
|
-
* us could conceivably emit them, though our modal renders bare).
|
|
261
|
-
* - Repeated keys (multi-select / checkbox-list) accumulate into arrays.
|
|
262
|
-
* - File entries collapse to '' — createOption forms that accept files
|
|
263
|
-
* already pre-resolve uploads to URL strings via FileUploadField's
|
|
264
|
-
* hidden input, so the File slot is ignored.
|
|
265
|
-
*/
|
|
266
|
-
function formDataToValues(fd: FormData): Record<string, unknown> {
|
|
267
|
-
const out: Record<string, unknown> = {}
|
|
268
|
-
for (const [k, raw] of fd.entries()) {
|
|
269
|
-
if (k === '_formId' || k === '_method') continue
|
|
270
|
-
const v = typeof raw === 'string' ? raw : ''
|
|
271
|
-
if (k in out) {
|
|
272
|
-
const prev = out[k]
|
|
273
|
-
if (Array.isArray(prev)) prev.push(v)
|
|
274
|
-
else out[k] = [prev, v]
|
|
275
|
-
} else {
|
|
276
|
-
out[k] = v
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return out
|
|
280
|
-
}
|
|
@@ -1,81 +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 { Slider } from '../ui/slider.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Numeric slider. Value persisted as a number (coerced via the
|
|
8
|
-
* shared 'number' branch in `coerceFormValues`). When `showValue`
|
|
9
|
-
* is true, renders the current value to the right of the track.
|
|
10
|
-
*/
|
|
11
|
-
export function SliderInput({
|
|
12
|
-
name, defaultValue, disabled, min, max, step, showValue,
|
|
13
|
-
}: {
|
|
14
|
-
name: string
|
|
15
|
-
defaultValue: unknown
|
|
16
|
-
disabled: boolean
|
|
17
|
-
min: number
|
|
18
|
-
max: number
|
|
19
|
-
step: number
|
|
20
|
-
showValue: boolean
|
|
21
|
-
}): React.ReactElement {
|
|
22
|
-
const fs = useFieldState(name)
|
|
23
|
-
|
|
24
|
-
const toNumber = (v: unknown): number => {
|
|
25
|
-
const n = Number(v)
|
|
26
|
-
return Number.isFinite(n) ? n : min
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const [localValue, setLocalValue] = useState<number>(toNumber(defaultValue))
|
|
30
|
-
const value = fs.controlled ? toNumber(fs.value) : localValue
|
|
31
|
-
|
|
32
|
-
// Base UI Slider's onValueChange callback does NOT dispatch a native
|
|
33
|
-
// bubbling change event, so RepeaterInput's container-level delegate
|
|
34
|
-
// can't pick up inner-row live() triggers. Call triggerLive explicitly
|
|
35
|
-
// in BOTH paths (no-op outside FormStateProvider / when field has no
|
|
36
|
-
// `live` config). Value passed as `valueOverride` so dotted-path
|
|
37
|
-
// inner-Repeater fields pass through correctly.
|
|
38
|
-
const onChange = (next: number | readonly number[]): void => {
|
|
39
|
-
const v = Array.isArray(next) ? Number(next[0]) : Number(next)
|
|
40
|
-
if (fs.controlled) { fs.setValue(v); fs.triggerLive(v) }
|
|
41
|
-
else { setLocalValue(v); fs.triggerLive(v) }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Cross-tree applier — Base UI Slider drives via the `value` prop, not
|
|
45
|
-
// the hidden mirror input below. FieldShell skips its generic registration
|
|
46
|
-
// for fieldType === 'slider'.
|
|
47
|
-
const fsRef = useRef(fs)
|
|
48
|
-
useEffect(() => { fsRef.current = fs }, [fs])
|
|
49
|
-
const formId = useContext(FormIdContext) || undefined
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (name.includes('.')) return
|
|
52
|
-
const applier: PendingSuggestionApplier = (suggestion) => {
|
|
53
|
-
const v = toNumber(suggestion.suggestedValue)
|
|
54
|
-
const cur = fsRef.current
|
|
55
|
-
if (cur.controlled) { cur.setValue(v); cur.triggerLive(v) }
|
|
56
|
-
else { setLocalValue(v); cur.triggerLive(v) }
|
|
57
|
-
}
|
|
58
|
-
return registerPendingSuggestionApplier(formId, name, applier)
|
|
59
|
-
}, [name, formId])
|
|
60
|
-
|
|
61
|
-
return (
|
|
62
|
-
<div className="flex items-center gap-3">
|
|
63
|
-
<input type="hidden" name={name} value={String(value)} />
|
|
64
|
-
<div className="flex-1">
|
|
65
|
-
<Slider
|
|
66
|
-
value={value}
|
|
67
|
-
onValueChange={onChange}
|
|
68
|
-
min={min}
|
|
69
|
-
max={max}
|
|
70
|
-
step={step}
|
|
71
|
-
disabled={disabled}
|
|
72
|
-
/>
|
|
73
|
-
</div>
|
|
74
|
-
{showValue && (
|
|
75
|
-
<span className="text-sm tabular-nums text-muted-foreground w-10 text-right">
|
|
76
|
-
{value}
|
|
77
|
-
</span>
|
|
78
|
-
)}
|
|
79
|
-
</div>
|
|
80
|
-
)
|
|
81
|
-
}
|
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import { GripVerticalIcon, XIcon } from 'lucide-react'
|
|
3
|
-
import { useFieldState, FormIdContext } from '../FormStateContext.js'
|
|
4
|
-
import { registerPendingSuggestionApplier, type PendingSuggestionApplier } from '../PendingSuggestionApplierRegistry.js'
|
|
5
|
-
import { reorderRows } from './RepeaterInput.js'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Free-text tag chips. Value is `string[]`. Renders pill-shaped chips
|
|
9
|
-
* for each committed tag plus an inline text input. Pressing Enter (or
|
|
10
|
-
* any key in `splitKeys`) commits the trimmed draft to the chip set.
|
|
11
|
-
* Pasting a string containing `separator` splits into multiple chips.
|
|
12
|
-
* Backspace on an empty draft removes the last chip.
|
|
13
|
-
*
|
|
14
|
-
* The chip set serializes to a single hidden input as JSON; the server's
|
|
15
|
-
* `coerceFormValues` `tagsInput` branch parses it back into `string[]`.
|
|
16
|
-
*
|
|
17
|
-
* When `reorderable` is set, each chip becomes draggable via native HTML5
|
|
18
|
-
* drag-and-drop. A 2px vertical drop indicator hints where the dragged chip
|
|
19
|
-
* will land. Reuses `reorderRows` from RepeaterInput so behavior matches.
|
|
20
|
-
*/
|
|
21
|
-
export function TagsInput({
|
|
22
|
-
name, defaultValue, disabled, placeholder, suggestions, separator, splitKeys, maxTags, reorderable,
|
|
23
|
-
}: {
|
|
24
|
-
name: string
|
|
25
|
-
defaultValue: unknown
|
|
26
|
-
disabled: boolean
|
|
27
|
-
placeholder: string | undefined
|
|
28
|
-
suggestions: string[]
|
|
29
|
-
separator: string | null
|
|
30
|
-
splitKeys: string[]
|
|
31
|
-
maxTags: number | null
|
|
32
|
-
reorderable: boolean
|
|
33
|
-
}): React.ReactElement {
|
|
34
|
-
const fs = useFieldState(name)
|
|
35
|
-
|
|
36
|
-
const initial = useMemo<string[]>(() => toArray(defaultValue), [])
|
|
37
|
-
const [localTags, setLocalTags] = useState<string[]>(initial)
|
|
38
|
-
const [draft, setDraft] = useState<string>('')
|
|
39
|
-
const [focused, setFocused] = useState<boolean>(false)
|
|
40
|
-
const [dragFromIdx, setDragFromIdx] = useState<number | null>(null)
|
|
41
|
-
const [dropAt, setDropAt] = useState<number | null>(null)
|
|
42
|
-
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
43
|
-
const canReorder = reorderable && !disabled
|
|
44
|
-
|
|
45
|
-
const tags = fs.controlled ? toArray(fs.value) : localTags
|
|
46
|
-
|
|
47
|
-
const setTags = (next: string[]): void => {
|
|
48
|
-
if (fs.controlled) { fs.setValue(next); fs.triggerLive(next) }
|
|
49
|
-
else { setLocalTags(next); fs.triggerLive(next) }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Cross-tree applier — chip set lives in React; hidden mirror is a
|
|
53
|
-
// write-only JSON serialization. FieldShell skips its generic
|
|
54
|
-
// registration for fieldType === 'tagsInput'.
|
|
55
|
-
const fsRef = useRef(fs)
|
|
56
|
-
useEffect(() => { fsRef.current = fs }, [fs])
|
|
57
|
-
const formId = useContext(FormIdContext) || undefined
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
if (name.includes('.')) return
|
|
60
|
-
const applier: PendingSuggestionApplier = (suggestion) => {
|
|
61
|
-
const next = toArray(suggestion.suggestedValue)
|
|
62
|
-
const cur = fsRef.current
|
|
63
|
-
if (cur.controlled) { cur.setValue(next); cur.triggerLive(next) }
|
|
64
|
-
else { setLocalTags(next); cur.triggerLive(next) }
|
|
65
|
-
}
|
|
66
|
-
return registerPendingSuggestionApplier(formId, name, applier)
|
|
67
|
-
}, [name, formId])
|
|
68
|
-
|
|
69
|
-
const canAddMore = maxTags == null || tags.length < maxTags
|
|
70
|
-
|
|
71
|
-
const addTag = (raw: string): void => {
|
|
72
|
-
const t = raw.trim()
|
|
73
|
-
if (!t) return
|
|
74
|
-
if (tags.includes(t)) return
|
|
75
|
-
if (!canAddMore) return
|
|
76
|
-
setTags([...tags, t])
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const addMany = (raws: string[]): void => {
|
|
80
|
-
if (raws.length === 0) return
|
|
81
|
-
const next = tags.slice()
|
|
82
|
-
const seen = new Set(next)
|
|
83
|
-
for (const r of raws) {
|
|
84
|
-
const t = r.trim()
|
|
85
|
-
if (!t || seen.has(t)) continue
|
|
86
|
-
if (maxTags != null && next.length >= maxTags) break
|
|
87
|
-
next.push(t)
|
|
88
|
-
seen.add(t)
|
|
89
|
-
}
|
|
90
|
-
if (next.length !== tags.length) setTags(next)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const removeTag = (idx: number): void => {
|
|
94
|
-
setTags(tags.filter((_, i) => i !== idx))
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const onChipDragStart = (idx: number) => (e: React.DragEvent<HTMLSpanElement>): void => {
|
|
98
|
-
if (!canReorder) return
|
|
99
|
-
setDragFromIdx(idx)
|
|
100
|
-
e.dataTransfer.effectAllowed = 'move'
|
|
101
|
-
// Firefox refuses to start a drag without setData on the dataTransfer.
|
|
102
|
-
e.dataTransfer.setData('text/plain', String(idx))
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const onChipDragOver = (idx: number) => (e: React.DragEvent<HTMLSpanElement>): void => {
|
|
106
|
-
if (dragFromIdx == null) return
|
|
107
|
-
e.preventDefault()
|
|
108
|
-
e.dataTransfer.dropEffect = 'move'
|
|
109
|
-
const rect = (e.currentTarget as HTMLElement).getBoundingClientRect()
|
|
110
|
-
const before = e.clientX < rect.left + rect.width / 2
|
|
111
|
-
setDropAt(before ? idx : idx + 1)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const onChipDrop = (e: React.DragEvent<HTMLSpanElement>): void => {
|
|
115
|
-
if (dragFromIdx == null || dropAt == null) {
|
|
116
|
-
setDragFromIdx(null); setDropAt(null)
|
|
117
|
-
return
|
|
118
|
-
}
|
|
119
|
-
e.preventDefault()
|
|
120
|
-
const next = reorderRows(tags, dragFromIdx, dropAt)
|
|
121
|
-
if (next !== tags) setTags(next)
|
|
122
|
-
setDragFromIdx(null)
|
|
123
|
-
setDropAt(null)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const onChipDragEnd = (): void => {
|
|
127
|
-
setDragFromIdx(null)
|
|
128
|
-
setDropAt(null)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>): void => {
|
|
132
|
-
// Backspace on an empty draft pops the most recent chip.
|
|
133
|
-
if (e.key === 'Backspace' && draft === '' && tags.length > 0) {
|
|
134
|
-
e.preventDefault()
|
|
135
|
-
removeTag(tags.length - 1)
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
// Commit on splitKeys OR on the configured separator char.
|
|
139
|
-
const isSplit = splitKeys.includes(e.key) || (separator !== null && e.key === separator)
|
|
140
|
-
if (isSplit && draft.trim() !== '') {
|
|
141
|
-
e.preventDefault()
|
|
142
|
-
addTag(draft)
|
|
143
|
-
setDraft('')
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const onChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
148
|
-
const v = e.target.value
|
|
149
|
-
// Paste-friendly: split on separator inline as the user types.
|
|
150
|
-
if (separator !== null && v.includes(separator)) {
|
|
151
|
-
const parts = v.split(separator)
|
|
152
|
-
const tail = parts.pop() ?? ''
|
|
153
|
-
addMany(parts)
|
|
154
|
-
setDraft(tail)
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
setDraft(v)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const onBlur = (): void => {
|
|
161
|
-
setFocused(false)
|
|
162
|
-
if (draft.trim() !== '') {
|
|
163
|
-
addTag(draft)
|
|
164
|
-
setDraft('')
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const filteredSuggestions = useMemo(() => {
|
|
169
|
-
if (!focused || suggestions.length === 0) return []
|
|
170
|
-
const needle = draft.trim().toLowerCase()
|
|
171
|
-
const taken = new Set(tags)
|
|
172
|
-
return suggestions
|
|
173
|
-
.filter(s => !taken.has(s))
|
|
174
|
-
.filter(s => needle === '' || s.toLowerCase().includes(needle))
|
|
175
|
-
.slice(0, 8)
|
|
176
|
-
}, [focused, draft, suggestions, tags])
|
|
177
|
-
|
|
178
|
-
const hiddenValue = useMemo(() => JSON.stringify(tags), [tags])
|
|
179
|
-
|
|
180
|
-
return (
|
|
181
|
-
<div className="relative">
|
|
182
|
-
<input type="hidden" name={name} value={hiddenValue} readOnly />
|
|
183
|
-
<div
|
|
184
|
-
className={[
|
|
185
|
-
'flex flex-wrap items-center gap-1 min-h-9 px-2 py-1 rounded-md border bg-transparent text-sm',
|
|
186
|
-
'focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2',
|
|
187
|
-
disabled ? 'opacity-50 pointer-events-none' : 'cursor-text',
|
|
188
|
-
].join(' ')}
|
|
189
|
-
onClick={() => inputRef.current?.focus()}
|
|
190
|
-
>
|
|
191
|
-
{tags.map((t, i) => (
|
|
192
|
-
<React.Fragment key={`${t}-${i}`}>
|
|
193
|
-
{canReorder && dropAt === i && dragFromIdx !== null && dragFromIdx !== i && dragFromIdx + 1 !== i && (
|
|
194
|
-
<span aria-hidden className="inline-block w-0.5 h-5 bg-primary rounded self-center" />
|
|
195
|
-
)}
|
|
196
|
-
<span
|
|
197
|
-
className={[
|
|
198
|
-
'inline-flex items-center gap-1 rounded bg-secondary text-secondary-foreground text-xs px-2 py-0.5',
|
|
199
|
-
canReorder ? 'cursor-grab active:cursor-grabbing' : '',
|
|
200
|
-
dragFromIdx === i ? 'opacity-50' : '',
|
|
201
|
-
].join(' ')}
|
|
202
|
-
draggable={canReorder}
|
|
203
|
-
onDragStart={canReorder ? onChipDragStart(i) : undefined}
|
|
204
|
-
onDragOver={canReorder ? onChipDragOver(i) : undefined}
|
|
205
|
-
onDrop={canReorder ? onChipDrop : undefined}
|
|
206
|
-
onDragEnd={canReorder ? onChipDragEnd : undefined}
|
|
207
|
-
>
|
|
208
|
-
{canReorder && (
|
|
209
|
-
<GripVerticalIcon
|
|
210
|
-
className="size-3 text-muted-foreground -ml-0.5"
|
|
211
|
-
aria-hidden
|
|
212
|
-
/>
|
|
213
|
-
)}
|
|
214
|
-
<span>{t}</span>
|
|
215
|
-
<button
|
|
216
|
-
type="button"
|
|
217
|
-
className="text-muted-foreground hover:text-foreground"
|
|
218
|
-
onClick={(e) => { e.stopPropagation(); removeTag(i) }}
|
|
219
|
-
disabled={disabled}
|
|
220
|
-
aria-label={`Remove ${t}`}
|
|
221
|
-
>
|
|
222
|
-
<XIcon className="size-3" />
|
|
223
|
-
</button>
|
|
224
|
-
</span>
|
|
225
|
-
</React.Fragment>
|
|
226
|
-
))}
|
|
227
|
-
{canReorder && dropAt === tags.length && dragFromIdx !== null && dragFromIdx !== tags.length - 1 && (
|
|
228
|
-
<span aria-hidden className="inline-block w-0.5 h-5 bg-primary rounded self-center" />
|
|
229
|
-
)}
|
|
230
|
-
<input
|
|
231
|
-
ref={inputRef}
|
|
232
|
-
type="text"
|
|
233
|
-
className="flex-1 min-w-[6ch] bg-transparent outline-none border-none text-sm py-0.5"
|
|
234
|
-
value={draft}
|
|
235
|
-
onChange={onChange}
|
|
236
|
-
onKeyDown={onKeyDown}
|
|
237
|
-
onFocus={() => setFocused(true)}
|
|
238
|
-
onBlur={onBlur}
|
|
239
|
-
disabled={disabled || !canAddMore}
|
|
240
|
-
placeholder={tags.length === 0 ? placeholder : undefined}
|
|
241
|
-
/>
|
|
242
|
-
</div>
|
|
243
|
-
{filteredSuggestions.length > 0 && (
|
|
244
|
-
<div className="absolute z-50 mt-1 w-full max-h-56 overflow-auto rounded-md border bg-popover text-popover-foreground shadow-md">
|
|
245
|
-
{filteredSuggestions.map((s) => (
|
|
246
|
-
<button
|
|
247
|
-
key={s}
|
|
248
|
-
type="button"
|
|
249
|
-
className="block w-full text-left text-sm px-2 py-1 hover:bg-accent"
|
|
250
|
-
// mousedown beats the input's blur — otherwise the click
|
|
251
|
-
// never lands because blur clears `focused` first and the
|
|
252
|
-
// dropdown unmounts before onClick fires.
|
|
253
|
-
onMouseDown={(e) => {
|
|
254
|
-
e.preventDefault()
|
|
255
|
-
addTag(s)
|
|
256
|
-
setDraft('')
|
|
257
|
-
inputRef.current?.focus()
|
|
258
|
-
}}
|
|
259
|
-
>
|
|
260
|
-
{s}
|
|
261
|
-
</button>
|
|
262
|
-
))}
|
|
263
|
-
</div>
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function toArray(v: unknown): string[] {
|
|
270
|
-
if (v === undefined || v === null || v === '') return []
|
|
271
|
-
if (Array.isArray(v)) return v.map(String).filter(s => s !== '')
|
|
272
|
-
if (typeof v === 'string') {
|
|
273
|
-
// Tolerate JSON-encoded arrays for record-load default-values.
|
|
274
|
-
if (v.startsWith('[')) {
|
|
275
|
-
try {
|
|
276
|
-
const parsed = JSON.parse(v)
|
|
277
|
-
if (Array.isArray(parsed)) return parsed.map(String).filter(s => s !== '')
|
|
278
|
-
} catch { /* fall through */ }
|
|
279
|
-
}
|
|
280
|
-
return [v]
|
|
281
|
-
}
|
|
282
|
-
return []
|
|
283
|
-
}
|