@rebasepro/cms 0.17.3 → 0.18.1
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/README.md +4 -0
- package/dist/{CollectionEditorDialog-B-akreOU.js → CollectionEditorDialog-BeX4b92r.js} +13 -7
- package/dist/{CollectionEditorDialog-B-akreOU.js.map → CollectionEditorDialog-BeX4b92r.js.map} +1 -1
- package/dist/{PropertyEditView-CiY84wkR.js → PropertyEditView-C7thfj4L.js} +3 -3
- package/dist/{PropertyEditView-CiY84wkR.js.map → PropertyEditView-C7thfj4L.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-DkAWda4N.js → RouterCollectionsStudioView-BtjzvB6E.js} +4 -4
- package/dist/{RouterCollectionsStudioView-DkAWda4N.js.map → RouterCollectionsStudioView-BtjzvB6E.js.map} +1 -1
- package/dist/{SchemaChangeDialog-DceT8QFX.js → SchemaChangeDialog-CkqK6KOC.js} +4 -2
- package/dist/{SchemaChangeDialog-DceT8QFX.js.map → SchemaChangeDialog-CkqK6KOC.js.map} +1 -1
- package/dist/collection_editor/api/generateCollectionApi.d.ts +1 -2
- package/dist/collection_editor_ui.d.ts +2 -0
- package/dist/collection_editor_ui.js +6 -5
- package/dist/collection_editor_ui.js.map +1 -1
- package/dist/components/DeleteEntityDialog.d.ts +2 -3
- package/dist/components/RebaseLayout.d.ts +11 -4
- package/dist/components/field_configs.d.ts +1 -1
- package/dist/editor/sanitize-html.d.ts +15 -0
- package/dist/editor.js +195 -10
- package/dist/editor.js.map +1 -1
- package/dist/{export-Ben8E355.js → export--arni_VY.js} +2 -2
- package/dist/{export-Ben8E355.js.map → export--arni_VY.js.map} +1 -1
- package/dist/form/components/LabelWithIcon.d.ts +10 -0
- package/dist/{history-DIEVd26f.js → history-C2TpoQwi.js} +2 -2
- package/dist/{history-DIEVd26f.js.map → history-C2TpoQwi.js.map} +1 -1
- package/dist/hooks/navigation/useBuildNavigationStateController.d.ts +1 -1
- package/dist/hooks/navigation/useResolvedViews.d.ts +1 -1
- package/dist/hooks/navigation/useTopLevelNavigation.d.ts +1 -1
- package/dist/{import-D25x7ko-.js → import-DTkXNoBg.js} +2 -2
- package/dist/{import-D25x7ko-.js.map → import-DTkXNoBg.js.map} +1 -1
- package/dist/index.js +78 -33
- package/dist/index.js.map +1 -1
- package/dist/util/propertyConfigMapper.d.ts +1 -1
- package/dist/util-CfLFggP7.js +57 -0
- package/dist/util-CfLFggP7.js.map +1 -0
- package/dist/{util-DVqnqlLn.js → util-DE1WQW6h.js} +134 -112
- package/dist/util-DE1WQW6h.js.map +1 -0
- package/package.json +42 -34
- package/dist/util-DVqnqlLn.js.map +0 -1
- package/src/collection_editor/CollectionEditorDialogsContext.tsx +0 -25
- package/src/collection_editor/ConfigControllerProvider.tsx +0 -395
- package/src/collection_editor/_cms_internals.ts +0 -41
- package/src/collection_editor/api/generateCollectionApi.ts +0 -126
- package/src/collection_editor/api/index.ts +0 -1
- package/src/collection_editor/extensibility_types.ts +0 -168
- package/src/collection_editor/index.ts +0 -108
- package/src/collection_editor/liveSchemaClient.ts +0 -285
- package/src/collection_editor/relation_kinds.ts +0 -44
- package/src/collection_editor/serializable_types.ts +0 -535
- package/src/collection_editor/serializable_utils.ts +0 -729
- package/src/collection_editor/types/collection_editor_controller.tsx +0 -62
- package/src/collection_editor/types/collection_inference.ts +0 -18
- package/src/collection_editor/types/config_controller.tsx +0 -121
- package/src/collection_editor/ui/AddKanbanColumnAction.tsx +0 -199
- package/src/collection_editor/ui/CollectionViewHeaderAction.tsx +0 -51
- package/src/collection_editor/ui/EditorCollectionAction.tsx +0 -54
- package/src/collection_editor/ui/EditorCollectionActionStart.tsx +0 -90
- package/src/collection_editor/ui/EditorEntityAction.tsx +0 -50
- package/src/collection_editor/ui/HomePageEditorCollectionAction.tsx +0 -114
- package/src/collection_editor/ui/KanbanSetupAction.tsx +0 -43
- package/src/collection_editor/ui/MissingReferenceWidget.tsx +0 -35
- package/src/collection_editor/ui/NewCollectionButton.tsx +0 -27
- package/src/collection_editor/ui/NewCollectionCard.tsx +0 -48
- package/src/collection_editor/ui/PropertyAddColumnComponent.tsx +0 -49
- package/src/collection_editor/ui/collection_editor/AICollectionGeneratorPopover.tsx +0 -260
- package/src/collection_editor/ui/collection_editor/AIModifiedPathsContext.tsx +0 -90
- package/src/collection_editor/ui/collection_editor/CollectionDetailsForm.tsx +0 -388
- package/src/collection_editor/ui/collection_editor/CollectionEditorDialog.tsx +0 -1098
- package/src/collection_editor/ui/collection_editor/CollectionEditorWelcomeView.tsx +0 -301
- package/src/collection_editor/ui/collection_editor/CollectionJsonImportDialog.tsx +0 -172
- package/src/collection_editor/ui/collection_editor/CollectionPropertiesEditorForm.tsx +0 -620
- package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +0 -530
- package/src/collection_editor/ui/collection_editor/CollectionRelationsTab.tsx +0 -462
- package/src/collection_editor/ui/collection_editor/CollectionStudioView.tsx +0 -104
- package/src/collection_editor/ui/collection_editor/CollectionYupValidation.tsx +0 -9
- package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +0 -199
- package/src/collection_editor/ui/collection_editor/DisplaySettingsForm.tsx +0 -315
- package/src/collection_editor/ui/collection_editor/EntityActionsEditTab.tsx +0 -164
- package/src/collection_editor/ui/collection_editor/EntityActionsSelectDialog.tsx +0 -40
- package/src/collection_editor/ui/collection_editor/EntityCustomViewsSelectDialog.tsx +0 -39
- package/src/collection_editor/ui/collection_editor/EnumForm.tsx +0 -373
- package/src/collection_editor/ui/collection_editor/GeneralSettingsForm.tsx +0 -288
- package/src/collection_editor/ui/collection_editor/GetCodeDialog.tsx +0 -141
- package/src/collection_editor/ui/collection_editor/KanbanConfigSection.tsx +0 -194
- package/src/collection_editor/ui/collection_editor/LayoutModeSwitch.tsx +0 -53
- package/src/collection_editor/ui/collection_editor/PropertyEditView.tsx +0 -980
- package/src/collection_editor/ui/collection_editor/PropertyFieldPreview.tsx +0 -164
- package/src/collection_editor/ui/collection_editor/PropertyTree.tsx +0 -364
- package/src/collection_editor/ui/collection_editor/RouterCollectionStudioView.tsx +0 -44
- package/src/collection_editor/ui/collection_editor/RouterCollectionsStudioView.tsx +0 -41
- package/src/collection_editor/ui/collection_editor/SchemaChangeDialog.tsx +0 -470
- package/src/collection_editor/ui/collection_editor/SubcollectionsEditTab.tsx +0 -256
- package/src/collection_editor/ui/collection_editor/SwitchControl.tsx +0 -39
- package/src/collection_editor/ui/collection_editor/ViewModeSwitch.tsx +0 -68
- package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportDataPreview.tsx +0 -67
- package/src/collection_editor/ui/collection_editor/import/CollectionEditorImportMapping.tsx +0 -296
- package/src/collection_editor/ui/collection_editor/import/clean_import_data.ts +0 -57
- package/src/collection_editor/ui/collection_editor/properties/BlockPropertyField.tsx +0 -147
- package/src/collection_editor/ui/collection_editor/properties/BooleanPropertyField.tsx +0 -41
- package/src/collection_editor/ui/collection_editor/properties/CommonPropertyFields.tsx +0 -113
- package/src/collection_editor/ui/collection_editor/properties/DateTimePropertyField.tsx +0 -250
- package/src/collection_editor/ui/collection_editor/properties/EnumPropertyField.tsx +0 -126
- package/src/collection_editor/ui/collection_editor/properties/KeyValuePropertyField.tsx +0 -20
- package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +0 -153
- package/src/collection_editor/ui/collection_editor/properties/MarkdownPropertyField.tsx +0 -217
- package/src/collection_editor/ui/collection_editor/properties/NumberPropertyField.tsx +0 -117
- package/src/collection_editor/ui/collection_editor/properties/ReferencePropertyField.tsx +0 -129
- package/src/collection_editor/ui/collection_editor/properties/RelationPropertyField.tsx +0 -335
- package/src/collection_editor/ui/collection_editor/properties/RepeatPropertyField.tsx +0 -107
- package/src/collection_editor/ui/collection_editor/properties/StoragePropertyField.tsx +0 -403
- package/src/collection_editor/ui/collection_editor/properties/StringPropertyField.tsx +0 -148
- package/src/collection_editor/ui/collection_editor/properties/UrlPropertyField.tsx +0 -91
- package/src/collection_editor/ui/collection_editor/properties/VectorPropertyField.tsx +0 -34
- package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +0 -49
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsEditor.tsx +0 -925
- package/src/collection_editor/ui/collection_editor/properties/conditions/ConditionsPanel.tsx +0 -28
- package/src/collection_editor/ui/collection_editor/properties/conditions/EnumConditionsEditor.tsx +0 -626
- package/src/collection_editor/ui/collection_editor/properties/conditions/index.ts +0 -6
- package/src/collection_editor/ui/collection_editor/properties/conditions/property_paths.ts +0 -93
- package/src/collection_editor/ui/collection_editor/properties/validation/ArrayPropertyValidation.tsx +0 -52
- package/src/collection_editor/ui/collection_editor/properties/validation/GeneralPropertyValidation.tsx +0 -63
- package/src/collection_editor/ui/collection_editor/properties/validation/NumberPropertyValidation.tsx +0 -114
- package/src/collection_editor/ui/collection_editor/properties/validation/StringPropertyValidation.tsx +0 -154
- package/src/collection_editor/ui/collection_editor/properties/validation/ValidationPanel.tsx +0 -28
- package/src/collection_editor/ui/collection_editor/templates/blog_template.ts +0 -112
- package/src/collection_editor/ui/collection_editor/templates/pages_template.ts +0 -183
- package/src/collection_editor/ui/collection_editor/templates/products_template.ts +0 -88
- package/src/collection_editor/ui/collection_editor/templates/users_template.ts +0 -42
- package/src/collection_editor/ui/collection_editor/util.ts +0 -28
- package/src/collection_editor/ui/collection_editor/utils/strings.ts +0 -1
- package/src/collection_editor/ui/collection_editor/utils/supported_fields.tsx +0 -39
- package/src/collection_editor/ui/collection_editor/utils/update_property_for_widget.ts +0 -276
- package/src/collection_editor/useCollectionEditorController.tsx +0 -9
- package/src/collection_editor/useCollectionsConfigController.tsx +0 -9
- package/src/collection_editor/useJsonCollectionsConfigController.tsx +0 -297
- package/src/collection_editor/useLiveSchemaEditing.tsx +0 -315
- package/src/collection_editor/useLocalCollectionsConfigController.tsx +0 -341
- package/src/collection_editor/useSafeSnackbarController.ts +0 -9
- package/src/collection_editor/validateCollectionJson.ts +0 -440
- package/src/collection_editor_ui.ts +0 -21
- package/src/components/AdditionalFieldValue.tsx +0 -68
- package/src/components/AdminModeSyncer.tsx +0 -56
- package/src/components/ArrayContainer.tsx +0 -542
- package/src/components/ClearFilterSortButton.tsx +0 -56
- package/src/components/CollectionEditorDialogs.tsx +0 -69
- package/src/components/CollectionPanel.tsx +0 -179
- package/src/components/CollectionTableBinding/CollectionRowActions.tsx +0 -244
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +0 -394
- package/src/components/CollectionTableBinding/CollectionTableBindingProps.tsx +0 -172
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +0 -350
- package/src/components/CollectionTableBinding/column_utils.tsx +0 -155
- package/src/components/CollectionTableBinding/fields/TableMultipleRelationField.tsx +0 -131
- package/src/components/CollectionTableBinding/fields/TableReferenceField.tsx +0 -193
- package/src/components/CollectionTableBinding/fields/TableRelationField.tsx +0 -192
- package/src/components/CollectionTableBinding/fields/TableRelationSelectorField.tsx +0 -42
- package/src/components/CollectionTableBinding/fields/TableStorageUpload.tsx +0 -324
- package/src/components/CollectionTableBinding/fields/VirtualTableSelect.tsx +0 -132
- package/src/components/CollectionTableBinding/fields/VirtualTableUserSelect.tsx +0 -41
- package/src/components/CollectionTableBinding/index.tsx +0 -14
- package/src/components/CollectionTableBinding/internal/CollectionTableToolbar.tsx +0 -96
- package/src/components/CollectionTableBinding/internal/EntityTableCell.tsx +0 -309
- package/src/components/CollectionTableBinding/internal/EntityTableCellActions.tsx +0 -80
- package/src/components/CollectionTableBinding/internal/common.tsx +0 -74
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +0 -422
- package/src/components/CollectionTableBinding/internal/popup_field/useDraggable.tsx +0 -99
- package/src/components/CollectionTableBinding/internal/popup_field/useWindowSize.tsx +0 -22
- package/src/components/CollectionTableBinding/table_bindings.tsx +0 -350
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +0 -263
- package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +0 -662
- package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +0 -159
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +0 -1430
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +0 -227
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +0 -1603
- package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +0 -240
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +0 -239
- package/src/components/CollectionViewBinding/FilterPresetsButton.tsx +0 -298
- package/src/components/CollectionViewBinding/FiltersDialog.tsx +0 -218
- package/src/components/CollectionViewBinding/SearchExplanation.tsx +0 -106
- package/src/components/CollectionViewBinding/SearchHighlight.tsx +0 -311
- package/src/components/CollectionViewBinding/SlotValue.tsx +0 -156
- package/src/components/CollectionViewBinding/SortButton.tsx +0 -354
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +0 -49
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +0 -28
- package/src/components/CollectionViewBinding/SplitListView.tsx +0 -488
- package/src/components/CollectionViewBinding/SplitViewContext.tsx +0 -34
- package/src/components/CollectionViewBinding/ViewModeToggle.tsx +0 -250
- package/src/components/CollectionViewBinding/hooks/useCollectionInlineEditor.ts +0 -81
- package/src/components/CollectionViewBinding/hooks/useKanbanDragAndDrop.ts +0 -181
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +0 -802
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +0 -703
- package/src/components/CollectionViewBinding/useSelectionController.tsx +0 -40
- package/src/components/CollectionViewBinding/utils.ts +0 -22
- package/src/components/DefaultAppBar.tsx +0 -264
- package/src/components/DefaultDrawer.tsx +0 -593
- package/src/components/DeleteEntityDialog.tsx +0 -205
- package/src/components/DetailViewBinding.tsx +0 -731
- package/src/components/DrawerNavigationGroup.tsx +0 -141
- package/src/components/DrawerNavigationItem.tsx +0 -115
- package/src/components/EditFormActions.tsx +0 -303
- package/src/components/EditViewBinding.tsx +0 -1069
- package/src/components/EntityDisplayHeader.tsx +0 -170
- package/src/components/EntityIdentityBar.tsx +0 -428
- package/src/components/EntityInspector.tsx +0 -156
- package/src/components/EntityPreviewBinding.tsx +0 -432
- package/src/components/EntityPreviewNesting.tsx +0 -50
- package/src/components/EntityViewBinding.tsx +0 -374
- package/src/components/FieldCaption.tsx +0 -14
- package/src/components/HomePage/ContentHomePage.tsx +0 -586
- package/src/components/HomePage/FavouritesView.tsx +0 -60
- package/src/components/HomePage/HomePageDnD.tsx +0 -704
- package/src/components/HomePage/NavigationCard.tsx +0 -81
- package/src/components/HomePage/NavigationCardBinding.tsx +0 -107
- package/src/components/HomePage/NavigationGroup.tsx +0 -156
- package/src/components/HomePage/RenameGroupDialog.tsx +0 -123
- package/src/components/HomePage/SmallNavigationCard.tsx +0 -41
- package/src/components/HomePage/index.tsx +0 -5
- package/src/components/InlineEntityPreview.tsx +0 -149
- package/src/components/JsonPreviewBinding.tsx +0 -66
- package/src/components/PropertyCollectionView.tsx +0 -336
- package/src/components/PropertyConfigBadge.tsx +0 -28
- package/src/components/PropertyIdCopyTooltip.tsx +0 -47
- package/src/components/RebaseAuthGate.tsx +0 -44
- package/src/components/RebaseCMS.tsx +0 -30
- package/src/components/RebaseLayout.tsx +0 -80
- package/src/components/RebaseNavigation.tsx +0 -369
- package/src/components/RebaseRouteDefs.tsx +0 -127
- package/src/components/RebaseShell.tsx +0 -83
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +0 -389
- package/src/components/ReferenceWidget.tsx +0 -156
- package/src/components/RelationSelector.tsx +0 -926
- package/src/components/SearchIconsView.tsx +0 -78
- package/src/components/SelectableTable/SelectableTable.tsx +0 -309
- package/src/components/SelectableTable/SelectableTableContext.tsx +0 -7
- package/src/components/SelectableTable/SelectionStore.ts +0 -43
- package/src/components/SelectableTable/filters/BooleanFilterField.tsx +0 -59
- package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +0 -146
- package/src/components/SelectableTable/filters/FilterFieldBinding.tsx +0 -190
- package/src/components/SelectableTable/filters/ReferenceFilterField.tsx +0 -250
- package/src/components/SelectableTable/filters/RelationFilterField.tsx +0 -238
- package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +0 -282
- package/src/components/SelectableTable/filters/null_filter.ts +0 -63
- package/src/components/SideDialogs.tsx +0 -238
- package/src/components/SidePanelBinding.tsx +0 -361
- package/src/components/SidePanelProvider.tsx +0 -80
- package/src/components/UserSelector.tsx +0 -513
- package/src/components/admin/CreationResultDialog.tsx +0 -138
- package/src/components/admin/index.ts +0 -1
- package/src/components/app/AppBar.tsx +0 -17
- package/src/components/app/Drawer.tsx +0 -38
- package/src/components/app/Scaffold.tsx +0 -366
- package/src/components/app/index.ts +0 -4
- package/src/components/app/useApp.tsx +0 -36
- package/src/components/common/default_entity_actions.tsx +0 -440
- package/src/components/common/index.ts +0 -1
- package/src/components/field_configs.tsx +0 -506
- package/src/components/history/EntityHistoryEntry.tsx +0 -203
- package/src/components/history/EntityHistoryView.tsx +0 -206
- package/src/components/history/LastEditedByFormAction.tsx +0 -24
- package/src/components/history/LastEditedByIndicator.tsx +0 -95
- package/src/components/history/UserChip.tsx +0 -15
- package/src/components/history/index.tsx +0 -5
- package/src/components/index.ts +0 -63
- package/src/contexts/BreacrumbsContext.tsx +0 -45
- package/src/contexts/SideDialogsControllerContext.tsx +0 -5
- package/src/contexts/SidePanelControllerContext.tsx +0 -5
- package/src/data_export/export/BasicExportAction.tsx +0 -142
- package/src/data_export/export/ExportCollectionAction.tsx +0 -321
- package/src/data_export/export/export.ts +0 -295
- package/src/data_export/export/fetch_export_data.ts +0 -90
- package/src/data_export/export/index.ts +0 -4
- package/src/data_export/index.ts +0 -1
- package/src/data_import/components/DataNewPropertiesMapping.tsx +0 -261
- package/src/data_import/components/ImportFileUpload.tsx +0 -42
- package/src/data_import/components/ImportNewPropertyFieldPreview.tsx +0 -58
- package/src/data_import/components/ImportSaveInProgress.tsx +0 -121
- package/src/data_import/components/index.ts +0 -4
- package/src/data_import/hooks/index.ts +0 -1
- package/src/data_import/hooks/useImportConfig.tsx +0 -34
- package/src/data_import/import/ImportCollectionAction.tsx +0 -461
- package/src/data_import/import/index.ts +0 -1
- package/src/data_import/index.ts +0 -5
- package/src/data_import/types/column_mapping.ts +0 -32
- package/src/data_import/types/index.ts +0 -1
- package/src/data_import/utils/csv.ts +0 -153
- package/src/data_import/utils/data.ts +0 -181
- package/src/data_import/utils/file_headers.ts +0 -14
- package/src/data_import/utils/file_to_json.ts +0 -149
- package/src/data_import/utils/get_import_inference_type.ts +0 -33
- package/src/data_import/utils/get_properties_mapping.ts +0 -67
- package/src/data_import/utils/index.ts +0 -5
- package/src/data_import/utils/save_entities.ts +0 -138
- package/src/data_import/utils/transforms.ts +0 -70
- package/src/editor/components/SlashCommandMenu.tsx +0 -532
- package/src/editor/components/editor-bubble-item.tsx +0 -32
- package/src/editor/components/editor-bubble.tsx +0 -143
- package/src/editor/components/image-bubble.tsx +0 -183
- package/src/editor/components/index.ts +0 -14
- package/src/editor/components/table-bubble.tsx +0 -233
- package/src/editor/editor.tsx +0 -529
- package/src/editor/extensions/HighlightDecorationExtension.ts +0 -115
- package/src/editor/extensions/Image.ts +0 -159
- package/src/editor/extensions/TextLoadingDecorationExtension.tsx +0 -107
- package/src/editor/extensions/clipboard.ts +0 -72
- package/src/editor/extensions/custom-keymap.ts +0 -25
- package/src/editor/extensions/drag-and-drop.tsx +0 -508
- package/src/editor/hooks/useProseMirror.ts +0 -126
- package/src/editor/hooks/useProseMirrorContext.ts +0 -15
- package/src/editor/index.ts +0 -2
- package/src/editor/markdown-it-plugins.d.ts +0 -24
- package/src/editor/markdown.ts +0 -204
- package/src/editor/nodeViews/ImageComponent.tsx +0 -20
- package/src/editor/nodeViews/ReactNodeView.tsx +0 -89
- package/src/editor/nodeViews/TaskItemComponent.tsx +0 -30
- package/src/editor/nodeViews/index.ts +0 -35
- package/src/editor/plugins/index.ts +0 -58
- package/src/editor/plugins/inputrules.ts +0 -82
- package/src/editor/plugins/placeholderPlugin.ts +0 -55
- package/src/editor/plugins/slashCommandPlugin.ts +0 -65
- package/src/editor/schema.ts +0 -339
- package/src/editor/selectors/ai-selector.tsx +0 -110
- package/src/editor/selectors/color-selector.tsx +0 -199
- package/src/editor/selectors/link-selector.tsx +0 -122
- package/src/editor/selectors/node-selector.tsx +0 -159
- package/src/editor/selectors/text-buttons.tsx +0 -79
- package/src/editor/types.ts +0 -6
- package/src/editor/useProseMirror.ts +0 -125
- package/src/editor/utils/prosemirror-utils.ts +0 -113
- package/src/editor/utils/remove_classes.ts +0 -18
- package/src/editor/utils/useDebouncedCallback.ts +0 -25
- package/src/form/EntityForm.tsx +0 -761
- package/src/form/EntityFormActions.tsx +0 -160
- package/src/form/EntityFormBinding.tsx +0 -342
- package/src/form/PropertyFieldBinding.tsx +0 -348
- package/src/form/components/ErrorFocus.tsx +0 -44
- package/src/form/components/FieldBlock.tsx +0 -189
- package/src/form/components/FieldHelperText.tsx +0 -84
- package/src/form/components/FormRail.tsx +0 -153
- package/src/form/components/FormSections.tsx +0 -145
- package/src/form/components/LabelWithIcon.tsx +0 -43
- package/src/form/components/LabelWithIconAndTooltip.tsx +0 -28
- package/src/form/components/LocalChangesMenu.tsx +0 -147
- package/src/form/components/StorageItemPreview.tsx +0 -81
- package/src/form/components/StorageUploadProgress.tsx +0 -118
- package/src/form/components/index.tsx +0 -6
- package/src/form/components/useRailVisible.tsx +0 -47
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +0 -109
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +0 -172
- package/src/form/field_bindings/BinaryFieldBinding.tsx +0 -177
- package/src/form/field_bindings/BlockFieldBinding.tsx +0 -282
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +0 -78
- package/src/form/field_bindings/GeopointFieldBinding.tsx +0 -200
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +0 -574
- package/src/form/field_bindings/MapFieldBinding.tsx +0 -120
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +0 -259
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +0 -130
- package/src/form/field_bindings/MultipleRelationFieldBinding.tsx +0 -168
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +0 -94
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +0 -146
- package/src/form/field_bindings/RelationFieldBinding.tsx +0 -202
- package/src/form/field_bindings/RepeatFieldBinding.tsx +0 -143
- package/src/form/field_bindings/SelectFieldBinding.tsx +0 -111
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +0 -521
- package/src/form/field_bindings/SwitchFieldBinding.tsx +0 -70
- package/src/form/field_bindings/TextFieldBinding.tsx +0 -189
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +0 -71
- package/src/form/field_bindings/VectorFieldBinding.tsx +0 -209
- package/src/form/form_utils.ts +0 -243
- package/src/form/index.tsx +0 -36
- package/src/form/useClearRestoreValue.tsx +0 -36
- package/src/form/useUndoableDiscard.ts +0 -39
- package/src/form/validation.ts +0 -583
- package/src/hooks/index.ts +0 -16
- package/src/hooks/navigation/contexts/CollectionRegistryContext.tsx +0 -22
- package/src/hooks/navigation/contexts/NavigationStateContext.tsx +0 -16
- package/src/hooks/navigation/contexts/UrlContext.tsx +0 -24
- package/src/hooks/navigation/contexts/index.ts +0 -14
- package/src/hooks/navigation/useAsyncResolver.ts +0 -100
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +0 -218
- package/src/hooks/navigation/useBuildNavigationStateController.tsx +0 -134
- package/src/hooks/navigation/useBuildUrlController.tsx +0 -66
- package/src/hooks/navigation/useNavigationResolution.ts +0 -271
- package/src/hooks/navigation/useResolvedCollections.ts +0 -102
- package/src/hooks/navigation/useResolvedViews.tsx +0 -139
- package/src/hooks/navigation/useTopLevelNavigation.ts +0 -273
- package/src/hooks/navigation/utils.ts +0 -200
- package/src/hooks/useAdminContext.tsx +0 -83
- package/src/hooks/useBreadcrumbsController.tsx +0 -17
- package/src/hooks/useBuildSideDialogsController.tsx +0 -145
- package/src/hooks/useBuildSidePanel.tsx +0 -315
- package/src/hooks/useChildViewSource.ts +0 -46
- package/src/hooks/useEntityDisplay.tsx +0 -262
- package/src/hooks/useEntityDisplayTitle.tsx +0 -49
- package/src/hooks/useHistory.ts +0 -178
- package/src/hooks/useRecordActions.tsx +0 -69
- package/src/hooks/useResolvedNavigationFrom.tsx +0 -167
- package/src/hooks/useResolvedUsers.ts +0 -148
- package/src/hooks/useSelectionDialog.tsx +0 -58
- package/src/hooks/useSideDialogsController.tsx +0 -25
- package/src/hooks/useSidePanel.tsx +0 -13
- package/src/index.ts +0 -124
- package/src/preview/PropertyPreview.tsx +0 -331
- package/src/preview/compact_text.ts +0 -68
- package/src/preview/components/ArrayEnumPreview.tsx +0 -39
- package/src/preview/components/BooleanPreview.tsx +0 -28
- package/src/preview/components/DatePreview.tsx +0 -106
- package/src/preview/components/EmptyValue.tsx +0 -10
- package/src/preview/components/EnumValuesChip.tsx +0 -39
- package/src/preview/components/ImagePreview.tsx +0 -144
- package/src/preview/components/InlineEntityListPreview.tsx +0 -42
- package/src/preview/components/ReferencePreview.tsx +0 -235
- package/src/preview/components/RelationPreview.tsx +0 -271
- package/src/preview/components/StorageThumbnail.tsx +0 -147
- package/src/preview/components/UrlComponentPreview.tsx +0 -116
- package/src/preview/components/UserPreview.tsx +0 -30
- package/src/preview/components/date_fns_locales.ts +0 -125
- package/src/preview/index.ts +0 -26
- package/src/preview/property_previews/ArrayOfMapsPreview.tsx +0 -73
- package/src/preview/property_previews/ArrayOfReferencesPreview.tsx +0 -67
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +0 -77
- package/src/preview/property_previews/ArrayOfStorageComponentsPreview.tsx +0 -49
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +0 -70
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +0 -79
- package/src/preview/property_previews/ArrayPropertyEnumPreview.tsx +0 -35
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +0 -79
- package/src/preview/property_previews/MapPropertyPreview.tsx +0 -198
- package/src/preview/property_previews/NumberPropertyPreview.tsx +0 -39
- package/src/preview/property_previews/SkeletonPropertyComponent.tsx +0 -276
- package/src/preview/property_previews/StringPropertyPreview.tsx +0 -70
- package/src/preview/util.ts +0 -39
- package/src/routes/CustomViewRoute.tsx +0 -33
- package/src/routes/RebaseRoute.tsx +0 -467
- package/src/routes/index.ts +0 -2
- package/src/types/components/EntityFormActionsProps.tsx +0 -20
- package/src/types/components/EntityFormProps.tsx +0 -119
- package/src/types/components/PropertyPreviewProps.tsx +0 -85
- package/src/types/components/formex.tsx +0 -2
- package/src/types/components/index.ts +0 -3
- package/src/types/fields.tsx +0 -340
- package/src/types/index.ts +0 -4
- package/src/util/dataControllerAdapter.ts +0 -88
- package/src/util/entity_actions.ts +0 -28
- package/src/util/index.ts +0 -5
- package/src/util/navigation_utils.ts +0 -89
- package/src/util/number_format.ts +0 -46
- package/src/util/previews.ts +0 -131
- package/src/util/propertyConfigMapper.ts +0 -134
- package/src/util/property_utils.tsx +0 -236
- package/src/util/resolutions.ts +0 -96
- package/src/util/view_constants.ts +0 -33
- package/src/util/view_mode.ts +0 -176
|
@@ -1,926 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
ArrowRightToLineIcon,
|
|
4
|
-
CheckIcon,
|
|
5
|
-
ChevronDownIcon,
|
|
6
|
-
Chip,
|
|
7
|
-
CircularProgress,
|
|
8
|
-
cls,
|
|
9
|
-
defaultBorderMixin,
|
|
10
|
-
fieldBackgroundDisabledMixin,
|
|
11
|
-
fieldBackgroundHoverMixin,
|
|
12
|
-
fieldBackgroundInvisibleMixin,
|
|
13
|
-
fieldBackgroundMixin,
|
|
14
|
-
focusedDisabled,
|
|
15
|
-
IconButton,
|
|
16
|
-
iconSize,
|
|
17
|
-
PlusIcon,
|
|
18
|
-
PopoverPrimitive,
|
|
19
|
-
SearchIcon,
|
|
20
|
-
Separator,
|
|
21
|
-
Tooltip,
|
|
22
|
-
usePortalContainer,
|
|
23
|
-
XIcon
|
|
24
|
-
} from "@rebasepro/ui";
|
|
25
|
-
import * as React from "react";
|
|
26
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
27
|
-
import { Command as CommandPrimitive } from "cmdk";
|
|
28
|
-
import { Entity, EntityRelation, FilterValues, Relation, getCollectionDataPath } from "@rebasepro/types";
|
|
29
|
-
import { EntityPreviewBindingData } from "./EntityPreviewBinding";
|
|
30
|
-
import { getTitlePropertyKey, useData, usePermissions, useRelationSelector, useTranslation } from "@rebasepro/app";
|
|
31
|
-
import { useSidePanel } from "../hooks/useSidePanel";
|
|
32
|
-
import { normalizeToEntityRelation } from "@rebasepro/common";
|
|
33
|
-
|
|
34
|
-
/** Whether an authored relation yields many rows. Derived from its kind. */
|
|
35
|
-
function relationCardinality(relation: { kind?: string; cardinality?: string } | undefined): "one" | "many" | undefined {
|
|
36
|
-
if (!relation) return undefined;
|
|
37
|
-
if (relation.kind === "via") return relation.cardinality as "one" | "many" | undefined;
|
|
38
|
-
if (relation.kind === "hasMany" || relation.kind === "manyToMany") return "many";
|
|
39
|
-
if (relation.kind === "belongsTo" || relation.kind === "hasOne") return "one";
|
|
40
|
-
return relation.cardinality as "one" | "many" | undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export interface RelationItem {
|
|
45
|
-
id: string | number;
|
|
46
|
-
label: string;
|
|
47
|
-
description?: string;
|
|
48
|
-
data?: Entity;
|
|
49
|
-
relation: EntityRelation;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface RelationSelectorProps {
|
|
53
|
-
className?: string;
|
|
54
|
-
name?: string;
|
|
55
|
-
id?: string;
|
|
56
|
-
value?: EntityRelation | EntityRelation[] | null;
|
|
57
|
-
/** Callback returning selected EntityRelation(s) */
|
|
58
|
-
|
|
59
|
-
onValueChange?: (updatedValue: EntityRelation | EntityRelation[] | undefined) => void;
|
|
60
|
-
placeholder?: React.ReactNode;
|
|
61
|
-
/**
|
|
62
|
-
* `large` is the form's size and is on the shared 28/32/40/48 control
|
|
63
|
-
* scale, so a relation picker lines up with the text field beside it.
|
|
64
|
-
*
|
|
65
|
-
* `small` and `medium` predate that scale (42 and 56) and are kept as they
|
|
66
|
-
* are because the table cells and the filter row are built around them.
|
|
67
|
-
*/
|
|
68
|
-
size?: "small" | "medium" | "large";
|
|
69
|
-
useChips?: boolean;
|
|
70
|
-
disabled?: boolean;
|
|
71
|
-
invisible?: boolean;
|
|
72
|
-
|
|
73
|
-
relation: Relation;
|
|
74
|
-
/**
|
|
75
|
-
* Whether the selector picks many rows or one.
|
|
76
|
-
*
|
|
77
|
-
* Defaults to what the relation implies — a `hasMany` or `manyToMany`
|
|
78
|
-
* picks many — which is right wherever the selector *edits* the relation,
|
|
79
|
-
* because there the cardinality is the shape of the stored value.
|
|
80
|
-
*
|
|
81
|
-
* A filter is the case where it is not. `tags == <one tag>` asks a single
|
|
82
|
-
* question about a to-many link, and the operator is what decides how many
|
|
83
|
-
* values it takes. Deriving multiplicity from the relation there meant a
|
|
84
|
-
* to-many relation could only ever emit a list, so the filter field had to
|
|
85
|
-
* hide every single-value operator to avoid handing `==` an array.
|
|
86
|
-
*/
|
|
87
|
-
multiple?: boolean;
|
|
88
|
-
fixedFilter?: FilterValues<string>;
|
|
89
|
-
pageSize?: number;
|
|
90
|
-
emptyPlaceholder?: string;
|
|
91
|
-
searchPlaceholder?: string;
|
|
92
|
-
/** Shown in the open list when a search matched nothing. */
|
|
93
|
-
noResultsText?: string;
|
|
94
|
-
/**
|
|
95
|
-
* Shown on the closed trigger when nothing is selected. Says only that the
|
|
96
|
-
* field is empty — see the note by `resolvedPlaceholder` for why it cannot
|
|
97
|
-
* say whether there is anything to pick.
|
|
98
|
-
*/
|
|
99
|
-
emptyText?: string;
|
|
100
|
-
/**
|
|
101
|
-
* Shown in the open list when the target collection turned out to hold no
|
|
102
|
-
* rows at all, as opposed to a search simply not matching. Defaults to a
|
|
103
|
-
* sentence naming the target collection.
|
|
104
|
-
*/
|
|
105
|
-
emptyCollectionText?: string;
|
|
106
|
-
loadingText?: string;
|
|
107
|
-
/**
|
|
108
|
-
* Whether the open list offers to create a row in the target collection.
|
|
109
|
-
* Defaults to `true`. The offer is shown only when the user could actually
|
|
110
|
-
* insert into that collection — the same permission check the selection
|
|
111
|
-
* dialog's "Add" button makes — so turning this off is for the cases where
|
|
112
|
-
* creating is possible but not wanted, like a filter.
|
|
113
|
-
*/
|
|
114
|
-
allowCreate?: boolean;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export const RelationSelector = React.forwardRef<
|
|
118
|
-
HTMLButtonElement,
|
|
119
|
-
RelationSelectorProps
|
|
120
|
-
>(
|
|
121
|
-
(
|
|
122
|
-
{
|
|
123
|
-
value,
|
|
124
|
-
size = "medium",
|
|
125
|
-
onValueChange,
|
|
126
|
-
invisible,
|
|
127
|
-
disabled,
|
|
128
|
-
placeholder,
|
|
129
|
-
useChips = true,
|
|
130
|
-
className,
|
|
131
|
-
relation,
|
|
132
|
-
multiple: multipleOverride,
|
|
133
|
-
fixedFilter,
|
|
134
|
-
pageSize,
|
|
135
|
-
emptyPlaceholder,
|
|
136
|
-
searchPlaceholder = "Search...",
|
|
137
|
-
noResultsText = "No matches.",
|
|
138
|
-
emptyText = "Select…",
|
|
139
|
-
emptyCollectionText,
|
|
140
|
-
loadingText = "Loading...",
|
|
141
|
-
allowCreate = true
|
|
142
|
-
},
|
|
143
|
-
ref
|
|
144
|
-
) => {
|
|
145
|
-
|
|
146
|
-
const collection = relation.target();
|
|
147
|
-
const dataPath = getCollectionDataPath(collection);
|
|
148
|
-
const dataClient = useData();
|
|
149
|
-
const sidePanelController = useSidePanel();
|
|
150
|
-
const { canCreate } = usePermissions();
|
|
151
|
-
const { t } = useTranslation();
|
|
152
|
-
const contextPortalContainer = usePortalContainer();
|
|
153
|
-
const multiple = multipleOverride ?? relationCardinality(relation) === "many";
|
|
154
|
-
|
|
155
|
-
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
|
156
|
-
const isPopoverOpenRef = useRef(false);
|
|
157
|
-
// The list is only worth fetching once the picker has been opened. A
|
|
158
|
-
// table cell mounts one of these per row, and the selected value is
|
|
159
|
-
// resolved by its own effect below, not from the list. Latches on the
|
|
160
|
-
// first open so a closed popover keeps its results.
|
|
161
|
-
const [listRequested, setListRequested] = useState(false);
|
|
162
|
-
const [selectedItems, setSelectedItems] = useState<RelationItem[]>([]);
|
|
163
|
-
const [isLoadingSelectedItems, setIsLoadingSelectedItems] = useState(false);
|
|
164
|
-
const [searchString, setSearchString] = useState<string>("");
|
|
165
|
-
// Track IDs that were set via local user interaction (onItemClick / handleClear / handleRemoveItem).
|
|
166
|
-
// When an incoming value change matches these IDs exactly, we skip async re-resolution.
|
|
167
|
-
const localSelectionIdsRef = useRef<string | null>(null);
|
|
168
|
-
// Entity of selected IDs captured when the popover opens.
|
|
169
|
-
// Used to sort the dropdown list so selected items appear at the top.
|
|
170
|
-
// Stays stable for the entire popover session so items don't jump around.
|
|
171
|
-
const pinnedIdsRef = useRef<Set<string> | null>(null);
|
|
172
|
-
|
|
173
|
-
const {
|
|
174
|
-
items: availableItems,
|
|
175
|
-
isLoading,
|
|
176
|
-
hasMore,
|
|
177
|
-
search,
|
|
178
|
-
loadMore,
|
|
179
|
-
entityToRelationItem
|
|
180
|
-
} = useRelationSelector({
|
|
181
|
-
path: dataPath,
|
|
182
|
-
collection,
|
|
183
|
-
fixedFilter,
|
|
184
|
-
pageSize,
|
|
185
|
-
enabled: listRequested
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
|
189
|
-
const sentinelRef = useRef<HTMLDivElement>(null);
|
|
190
|
-
const observerRef = useRef<IntersectionObserver | null>(null);
|
|
191
|
-
const localTriggerRef = useRef<HTMLButtonElement | null>(null);
|
|
192
|
-
|
|
193
|
-
const handleButtonRef = useCallback((node: HTMLButtonElement | null) => {
|
|
194
|
-
localTriggerRef.current = node;
|
|
195
|
-
if (typeof ref === "function") {
|
|
196
|
-
ref(node);
|
|
197
|
-
} else if (ref) {
|
|
198
|
-
ref.current = node;
|
|
199
|
-
}
|
|
200
|
-
}, [ref]);
|
|
201
|
-
const contentRef = useRef<HTMLDivElement | null>(null);
|
|
202
|
-
const searchInputRef = useRef<HTMLInputElement | null>(null);
|
|
203
|
-
|
|
204
|
-
// Keep stable refs for dependencies used in the value-resolution effect
|
|
205
|
-
// so the effect only re-fires when the actual `value` identity changes.
|
|
206
|
-
const dataClientRef = useRef(dataClient);
|
|
207
|
-
dataClientRef.current = dataClient;
|
|
208
|
-
const entityToRelationItemRef = useRef(entityToRelationItem);
|
|
209
|
-
entityToRelationItemRef.current = entityToRelationItem;
|
|
210
|
-
const collectionSlugRef = useRef(collection.slug);
|
|
211
|
-
collectionSlugRef.current = collection.slug;
|
|
212
|
-
|
|
213
|
-
// Stable ref to track which IDs we've already resolved
|
|
214
|
-
const resolvedIdsRef = useRef<string>("");
|
|
215
|
-
// Track whether we have resolved items (used for skip guard without adding to deps)
|
|
216
|
-
const hasResolvedItemsRef = useRef(false);
|
|
217
|
-
|
|
218
|
-
const selectedItemsRef = useRef(selectedItems);
|
|
219
|
-
selectedItemsRef.current = selectedItems;
|
|
220
|
-
|
|
221
|
-
// Normalize incoming values — plain { __type: "relation" } objects
|
|
222
|
-
// from the server are converted to proper EntityRelation instances.
|
|
223
|
-
const rawArray = !value ? [] : Array.isArray(value) ? value : [value];
|
|
224
|
-
const relationsArray = rawArray.map(rel => {
|
|
225
|
-
if (typeof rel === "string" || typeof rel === "number") return rel;
|
|
226
|
-
return normalizeToEntityRelation(rel) ?? rel;
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
// Build a fingerprint of the incoming value's IDs
|
|
230
|
-
const incomingIds = relationsArray
|
|
231
|
-
.map(rel => {
|
|
232
|
-
const isPrimitive = typeof rel === "string" || typeof rel === "number";
|
|
233
|
-
return String(isPrimitive ? rel : (rel as EntityRelation).id);
|
|
234
|
-
})
|
|
235
|
-
.sort()
|
|
236
|
-
.join(",");
|
|
237
|
-
|
|
238
|
-
// CheckIcon if every relation already has embedded data
|
|
239
|
-
const allHaveData = relationsArray.length > 0 && relationsArray.every(rel => {
|
|
240
|
-
if (typeof rel === "string" || typeof rel === "number") return false;
|
|
241
|
-
return !!(rel as EntityRelation)?.data;
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const relationsArrayRef = useRef(relationsArray);
|
|
245
|
-
relationsArrayRef.current = relationsArray;
|
|
246
|
-
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
let active = true;
|
|
249
|
-
const currentRelationsArray = relationsArrayRef.current;
|
|
250
|
-
|
|
251
|
-
// If the IDs haven't changed and we already resolved them, skip entirely
|
|
252
|
-
if (incomingIds === resolvedIdsRef.current && (allHaveData || hasResolvedItemsRef.current)) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// FAST PATH: If the incoming IDs match what we just set locally via user interaction,
|
|
257
|
-
// our selectedItems already have the correct data — no re-resolution needed.
|
|
258
|
-
if (localSelectionIdsRef.current !== null && incomingIds === localSelectionIdsRef.current) {
|
|
259
|
-
resolvedIdsRef.current = incomingIds;
|
|
260
|
-
hasResolvedItemsRef.current = true;
|
|
261
|
-
// Do NOT call setSelectedItems or setIsLoadingSelectedItems — they are already correct.
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const currentSelected = selectedItemsRef.current;
|
|
266
|
-
const currentSelectedMap = new Map(currentSelected.map(item => [String(item.id), item]));
|
|
267
|
-
|
|
268
|
-
let allCovered = true;
|
|
269
|
-
const newResolvedItems: RelationItem[] = [];
|
|
270
|
-
const toRelationItem = entityToRelationItemRef.current;
|
|
271
|
-
const slug = collectionSlugRef.current;
|
|
272
|
-
|
|
273
|
-
for (const rel of currentRelationsArray) {
|
|
274
|
-
const isPrimitive = typeof rel === "string" || typeof rel === "number";
|
|
275
|
-
const relId = String(isPrimitive ? rel : (rel as EntityRelation).id);
|
|
276
|
-
const hasEmbeddedData = !isPrimitive && !!(rel as EntityRelation).data;
|
|
277
|
-
|
|
278
|
-
if (hasEmbeddedData) {
|
|
279
|
-
const r = rel as EntityRelation;
|
|
280
|
-
// admin wire format embeds relation data as { id, path, values }
|
|
281
|
-
newResolvedItems.push(toRelationItem(r.data as unknown as Entity, r));
|
|
282
|
-
} else if (currentSelectedMap.has(relId)) {
|
|
283
|
-
// We already have it in currentSelected
|
|
284
|
-
const existingItem = currentSelectedMap.get(relId)!;
|
|
285
|
-
newResolvedItems.push(existingItem);
|
|
286
|
-
} else {
|
|
287
|
-
allCovered = false;
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// FAST PATH: We have all necessary data embedded or in local cache
|
|
293
|
-
if (allCovered) {
|
|
294
|
-
setSelectedItems(newResolvedItems);
|
|
295
|
-
hasResolvedItemsRef.current = true;
|
|
296
|
-
resolvedIdsRef.current = incomingIds;
|
|
297
|
-
setIsLoadingSelectedItems(false);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// SLOW PATH: fetch missing data — but don't show loading if popover is open
|
|
302
|
-
// (user is actively interacting; showing a spinner is disruptive)
|
|
303
|
-
if (!isPopoverOpenRef.current) {
|
|
304
|
-
setIsLoadingSelectedItems(true);
|
|
305
|
-
}
|
|
306
|
-
const client = dataClientRef.current;
|
|
307
|
-
|
|
308
|
-
Promise.all(
|
|
309
|
-
currentRelationsArray.map(async (rel) => {
|
|
310
|
-
const isPrimitive = typeof rel === "string" || typeof rel === "number";
|
|
311
|
-
const relId = isPrimitive ? rel : (rel as EntityRelation).id;
|
|
312
|
-
const path = isPrimitive ? slug : (rel as EntityRelation).path;
|
|
313
|
-
const relation = isPrimitive ? new EntityRelation(relId, path) : rel as EntityRelation;
|
|
314
|
-
|
|
315
|
-
if (!isPrimitive && (rel as EntityRelation).data) {
|
|
316
|
-
// admin wire format embeds relation data as { id, path, values }
|
|
317
|
-
return toRelationItem((rel as EntityRelation).data as unknown as Entity, relation);
|
|
318
|
-
}
|
|
319
|
-
if (currentSelectedMap.has(String(relId))) {
|
|
320
|
-
return currentSelectedMap.get(String(relId))!;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
try {
|
|
324
|
-
const entity = await client.collection(path).findById(relId);
|
|
325
|
-
if (entity) return toRelationItem(entity, relation);
|
|
326
|
-
} catch (e) {
|
|
327
|
-
console.warn("RelationSelector: could not fetch entity for relation", rel, e);
|
|
328
|
-
}
|
|
329
|
-
return { id: relId,
|
|
330
|
-
label: String(relId),
|
|
331
|
-
relation } as RelationItem;
|
|
332
|
-
})
|
|
333
|
-
).then(resolved => {
|
|
334
|
-
if (active) {
|
|
335
|
-
setSelectedItems(resolved);
|
|
336
|
-
setIsLoadingSelectedItems(false);
|
|
337
|
-
hasResolvedItemsRef.current = true;
|
|
338
|
-
resolvedIdsRef.current = incomingIds;
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
return () => {
|
|
343
|
-
active = false;
|
|
344
|
-
};
|
|
345
|
-
}, [incomingIds, allHaveData]);
|
|
346
|
-
|
|
347
|
-
const sentinelCallbackRef = useCallback((node: HTMLDivElement | null) => {
|
|
348
|
-
if (observerRef.current) {
|
|
349
|
-
observerRef.current.disconnect();
|
|
350
|
-
observerRef.current = null;
|
|
351
|
-
}
|
|
352
|
-
if (sentinelRef.current !== node) {
|
|
353
|
-
(sentinelRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
|
|
354
|
-
}
|
|
355
|
-
if (!node || !hasMore || isLoading || !loadMore) return;
|
|
356
|
-
const observer = new IntersectionObserver(
|
|
357
|
-
(entries) => {
|
|
358
|
-
const entry = entries[0];
|
|
359
|
-
if (entry.isIntersecting && hasMore && !isLoading) loadMore();
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
root: scrollContainerRef.current,
|
|
363
|
-
rootMargin: "20px",
|
|
364
|
-
threshold: 0
|
|
365
|
-
}
|
|
366
|
-
);
|
|
367
|
-
observer.observe(node);
|
|
368
|
-
observerRef.current = observer;
|
|
369
|
-
}, [hasMore, isLoading, loadMore]);
|
|
370
|
-
|
|
371
|
-
useEffect(() => () => {
|
|
372
|
-
if (observerRef.current) observerRef.current.disconnect();
|
|
373
|
-
}, []);
|
|
374
|
-
|
|
375
|
-
// Enhanced scroll event listener specifically for dialog contexts
|
|
376
|
-
useEffect(() => {
|
|
377
|
-
const scrollContainer = scrollContainerRef.current;
|
|
378
|
-
if (!scrollContainer || !hasMore || isLoading || !isPopoverOpen) return;
|
|
379
|
-
|
|
380
|
-
const handleScroll = () => {
|
|
381
|
-
const { scrollTop, scrollHeight, clientHeight } = scrollContainer;
|
|
382
|
-
const isNearBottom = scrollTop + clientHeight >= scrollHeight - 100;
|
|
383
|
-
|
|
384
|
-
if (isNearBottom && hasMore && !isLoading) {
|
|
385
|
-
loadMore();
|
|
386
|
-
}
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
// Add scroll listener directly to the container
|
|
390
|
-
scrollContainer.addEventListener("scroll", handleScroll, { passive: true });
|
|
391
|
-
|
|
392
|
-
return () => {
|
|
393
|
-
scrollContainer.removeEventListener("scroll", handleScroll);
|
|
394
|
-
};
|
|
395
|
-
}, [hasMore, isLoading, loadMore, isPopoverOpen]);
|
|
396
|
-
|
|
397
|
-
const handleSearchChange = useCallback((newSearchString: string) => {
|
|
398
|
-
setSearchString(newSearchString);
|
|
399
|
-
search(newSearchString);
|
|
400
|
-
}, [search]);
|
|
401
|
-
|
|
402
|
-
const emitValueChange = useCallback((selected: RelationItem[]) => {
|
|
403
|
-
if (multiple) onValueChange?.(selected.length ? selected.map(i => i.relation) : undefined);
|
|
404
|
-
else onValueChange?.(selected[0]?.relation);
|
|
405
|
-
}, [onValueChange, multiple]);
|
|
406
|
-
|
|
407
|
-
// The picker can hand its selection over long after the render that
|
|
408
|
-
// built the callback — a row created in the side panel arrives whenever
|
|
409
|
-
// the person saves it — so the emitter is read from a ref rather than
|
|
410
|
-
// captured.
|
|
411
|
-
const emitValueChangeRef = useRef(emitValueChange);
|
|
412
|
-
emitValueChangeRef.current = emitValueChange;
|
|
413
|
-
|
|
414
|
-
// Helper: compute the "IDs fingerprint" for a set of RelationItems
|
|
415
|
-
// so we can mark it in localSelectionIdsRef and skip re-resolution.
|
|
416
|
-
const computeSelectionFingerprint = useCallback((items: RelationItem[]): string => {
|
|
417
|
-
return items.map(i => String(i.id)).sort().join(",");
|
|
418
|
-
}, []);
|
|
419
|
-
|
|
420
|
-
const onItemClick = useCallback((item: RelationItem) => {
|
|
421
|
-
let newSelected: RelationItem[];
|
|
422
|
-
if (multiple) {
|
|
423
|
-
const isSelected = selectedItems.some(v => String(v.id) === String(item.id));
|
|
424
|
-
newSelected = isSelected
|
|
425
|
-
? selectedItems.filter(v => String(v.id) !== String(item.id))
|
|
426
|
-
: [...selectedItems, item];
|
|
427
|
-
} else {
|
|
428
|
-
newSelected = [item];
|
|
429
|
-
setIsPopoverOpen(false);
|
|
430
|
-
isPopoverOpenRef.current = false;
|
|
431
|
-
pinnedIdsRef.current = null;
|
|
432
|
-
}
|
|
433
|
-
setSelectedItems(newSelected);
|
|
434
|
-
// Mark this fingerprint so the resolution effect skips async work
|
|
435
|
-
// when the parent echoes the same IDs back via props.
|
|
436
|
-
localSelectionIdsRef.current = computeSelectionFingerprint(newSelected);
|
|
437
|
-
emitValueChange(newSelected);
|
|
438
|
-
}, [multiple, selectedItems, emitValueChange, computeSelectionFingerprint]);
|
|
439
|
-
|
|
440
|
-
const handleClear = useCallback(() => {
|
|
441
|
-
setSelectedItems([]);
|
|
442
|
-
localSelectionIdsRef.current = "";
|
|
443
|
-
onValueChange?.(undefined);
|
|
444
|
-
}, [onValueChange]);
|
|
445
|
-
|
|
446
|
-
const handleRemoveItem = useCallback((itemToRemove: RelationItem) => {
|
|
447
|
-
const newSelected = selectedItems.filter(v => String(v.id) !== String(itemToRemove.id));
|
|
448
|
-
setSelectedItems(newSelected);
|
|
449
|
-
localSelectionIdsRef.current = computeSelectionFingerprint(newSelected);
|
|
450
|
-
emitValueChange(newSelected);
|
|
451
|
-
}, [selectedItems, emitValueChange, computeSelectionFingerprint]);
|
|
452
|
-
|
|
453
|
-
const handleRootOpenChange = useCallback((next: boolean) => {
|
|
454
|
-
if (disabled) return;
|
|
455
|
-
// We control open manually; only allow opening attempts from Radix (e.g. trigger press)
|
|
456
|
-
if (next) {
|
|
457
|
-
// Capture current selection so we can pin those items to the top of the list
|
|
458
|
-
pinnedIdsRef.current = new Set(selectedItems.map(i => String(i.id)));
|
|
459
|
-
setListRequested(true);
|
|
460
|
-
setIsPopoverOpen(true);
|
|
461
|
-
isPopoverOpenRef.current = true;
|
|
462
|
-
}
|
|
463
|
-
// Ignore close attempts here; outside click/Escape handled manually; single select closes explicitly on selection.
|
|
464
|
-
}, [disabled, selectedItems]);
|
|
465
|
-
|
|
466
|
-
// Outside click + Escape handling (simple and reliable)
|
|
467
|
-
useEffect(() => {
|
|
468
|
-
if (!isPopoverOpen) return;
|
|
469
|
-
|
|
470
|
-
function handlePointerDown(ev: MouseEvent) {
|
|
471
|
-
const target = ev.target as Node;
|
|
472
|
-
const triggerEl = localTriggerRef.current;
|
|
473
|
-
const contentEl = contentRef.current;
|
|
474
|
-
if (triggerEl?.contains(target)) return;
|
|
475
|
-
if (contentEl?.contains(target)) return;
|
|
476
|
-
// Outside
|
|
477
|
-
setIsPopoverOpen(false);
|
|
478
|
-
isPopoverOpenRef.current = false;
|
|
479
|
-
pinnedIdsRef.current = null;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function handleKey(ev: KeyboardEvent) {
|
|
483
|
-
if (ev.key === "Escape") {
|
|
484
|
-
setIsPopoverOpen(false);
|
|
485
|
-
isPopoverOpenRef.current = false;
|
|
486
|
-
pinnedIdsRef.current = null;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
document.addEventListener("mousedown", handlePointerDown, true);
|
|
491
|
-
document.addEventListener("keydown", handleKey, true);
|
|
492
|
-
return () => {
|
|
493
|
-
document.removeEventListener("mousedown", handlePointerDown, true);
|
|
494
|
-
document.removeEventListener("keydown", handleKey, true);
|
|
495
|
-
};
|
|
496
|
-
}, [isPopoverOpen]);
|
|
497
|
-
|
|
498
|
-
const closePopover = useCallback(() => {
|
|
499
|
-
setIsPopoverOpen(false);
|
|
500
|
-
isPopoverOpenRef.current = false;
|
|
501
|
-
pinnedIdsRef.current = null;
|
|
502
|
-
}, []);
|
|
503
|
-
|
|
504
|
-
// Pointing at a row that does not exist yet.
|
|
505
|
-
//
|
|
506
|
-
// Until now the only way out of that was to abandon the form, go to the
|
|
507
|
-
// other collection, create the row, come back and start again — and on
|
|
508
|
-
// a new record that meant losing everything typed so far. The side
|
|
509
|
-
// panel already stacks (the selection dialog creates from inside itself
|
|
510
|
-
// this way), so the picker can open a create form over the form it
|
|
511
|
-
// belongs to and select what comes back.
|
|
512
|
-
const canCreateTarget = allowCreate && canCreate(collection, dataPath);
|
|
513
|
-
|
|
514
|
-
// A search that matched nothing is the name the person was looking for,
|
|
515
|
-
// so it seeds the new row. Only when the title lands on a plain string
|
|
516
|
-
// property: putting free text into an enum, a relation or a computed
|
|
517
|
-
// property would be worse than not prefilling at all.
|
|
518
|
-
const buildDefaultValues = useCallback((searchText: string): Record<string, unknown> | undefined => {
|
|
519
|
-
const trimmed = searchText.trim();
|
|
520
|
-
if (!trimmed) return undefined;
|
|
521
|
-
const titleKey = getTitlePropertyKey(collection);
|
|
522
|
-
if (!titleKey || titleKey.includes(".")) return undefined;
|
|
523
|
-
const titleProperty = collection.properties?.[titleKey];
|
|
524
|
-
// A property builder is a function, and it decides its own value.
|
|
525
|
-
if (!titleProperty || typeof titleProperty === "function") return undefined;
|
|
526
|
-
if (titleProperty.type !== "string" || titleProperty.enum) return undefined;
|
|
527
|
-
return { [titleKey]: trimmed };
|
|
528
|
-
}, [collection]);
|
|
529
|
-
|
|
530
|
-
const handleCreateNew = useCallback(() => {
|
|
531
|
-
const defaultValues = buildDefaultValues(searchString);
|
|
532
|
-
closePopover();
|
|
533
|
-
sidePanelController.open({
|
|
534
|
-
path: dataPath,
|
|
535
|
-
collection,
|
|
536
|
-
// No URL. This panel is a detour inside the form you are
|
|
537
|
-
// filling in, not somewhere you navigated to, and its address
|
|
538
|
-
// would restore nothing anyway — a record that does not exist
|
|
539
|
-
// yet has none.
|
|
540
|
-
//
|
|
541
|
-
// Pushing one made closing it a *pathname* change, which is
|
|
542
|
-
// exactly what the unsaved-changes blocker watches
|
|
543
|
-
// (`useUnsavedChangesDialog`). So saving the new row raced the
|
|
544
|
-
// panel clearing its own dirty flag, and often enough lost:
|
|
545
|
-
// "There are unsaved changes" over a record that had just been
|
|
546
|
-
// saved, with the URL stranded on the target collection. Worst
|
|
547
|
-
// of all in the case this feature exists for — the outer form
|
|
548
|
-
// has content in it, which is the whole reason not to leave.
|
|
549
|
-
updateUrl: false,
|
|
550
|
-
// Saving is the whole point of the detour; the panel goes away
|
|
551
|
-
// and the picker is left holding the row it made.
|
|
552
|
-
closeOnSave: true,
|
|
553
|
-
defaultValues,
|
|
554
|
-
onUpdate: ({ entity }) => {
|
|
555
|
-
if (!entity) return;
|
|
556
|
-
const item = entityToRelationItemRef.current(entity, new EntityRelation(entity.id, dataPath));
|
|
557
|
-
const current = selectedItemsRef.current;
|
|
558
|
-
const newSelected = multiple
|
|
559
|
-
? [...current.filter(i => String(i.id) !== String(item.id)), item]
|
|
560
|
-
: [item];
|
|
561
|
-
setSelectedItems(newSelected);
|
|
562
|
-
// Same bookkeeping `onItemClick` does: mark the fingerprint
|
|
563
|
-
// so the resolution effect does not re-fetch the row we
|
|
564
|
-
// already hold when the form echoes the value back.
|
|
565
|
-
localSelectionIdsRef.current = newSelected.map(i => String(i.id)).sort().join(",");
|
|
566
|
-
emitValueChangeRef.current(newSelected);
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
}, [buildDefaultValues, searchString, closePopover, sidePanelController, dataPath, collection, multiple]);
|
|
570
|
-
|
|
571
|
-
// Text, not `EmptyValue`. That component draws a small grey rounded bar
|
|
572
|
-
// with no text in it, which is the same shape the `Skeleton` component
|
|
573
|
-
// uses while data is in flight — so a relation you simply had not filled
|
|
574
|
-
// in was indistinguishable from one that was still loading, and an empty
|
|
575
|
-
// Author or Tags field read as permanently stuck. It also left the trigger
|
|
576
|
-
// button with no accessible name, since the bar contains no text at all.
|
|
577
|
-
//
|
|
578
|
-
// This says "not filled in" rather than "nothing to choose from": which of
|
|
579
|
-
// those it is cannot be known here. The list is deliberately not fetched
|
|
580
|
-
// until the picker is first opened (`enabled: listRequested`, guarded by
|
|
581
|
-
// relation_cell_picker_gating.test.tsx, because a table renders one of
|
|
582
|
-
// these per row), so before that `availableItems` is empty because nobody
|
|
583
|
-
// asked, not because the target collection is. The genuine "nothing to
|
|
584
|
-
// pick yet" state is reported inside the popover below, where the fetch
|
|
585
|
-
// has actually happened.
|
|
586
|
-
const resolvedPlaceholder = placeholder || emptyPlaceholder || emptyText;
|
|
587
|
-
|
|
588
|
-
// Whatever dialog, side dialog or sheet we are inside offers as its
|
|
589
|
-
// portal host, falling back to `document.body`. This is not cosmetic:
|
|
590
|
-
// those are modal, and a modal locks scrolling everywhere outside its
|
|
591
|
-
// own content — a list portaled to the body opens and then refuses to
|
|
592
|
-
// scroll, because every wheel event over it is cancelled.
|
|
593
|
-
const portalContainer = (contextPortalContainer ?? (typeof document !== "undefined" ? document.body : undefined)) ?? undefined;
|
|
594
|
-
|
|
595
|
-
return (
|
|
596
|
-
<>
|
|
597
|
-
<PopoverPrimitive.Root open={isPopoverOpen} onOpenChange={handleRootOpenChange} modal={false}>
|
|
598
|
-
<PopoverPrimitive.Trigger asChild>
|
|
599
|
-
<button
|
|
600
|
-
ref={handleButtonRef}
|
|
601
|
-
type="button"
|
|
602
|
-
aria-haspopup="listbox"
|
|
603
|
-
aria-expanded={isPopoverOpen}
|
|
604
|
-
data-relation-selector-trigger
|
|
605
|
-
disabled={disabled}
|
|
606
|
-
onClick={() => {
|
|
607
|
-
if (disabled) return;
|
|
608
|
-
// Unconditional: the toggle can only close a
|
|
609
|
-
// popover that was opened, which requested the
|
|
610
|
-
// list already.
|
|
611
|
-
setListRequested(true);
|
|
612
|
-
setIsPopoverOpen(o => {
|
|
613
|
-
const next = !o;
|
|
614
|
-
isPopoverOpenRef.current = next;
|
|
615
|
-
if (next) {
|
|
616
|
-
pinnedIdsRef.current = new Set(selectedItems.map(i => String(i.id)));
|
|
617
|
-
} else {
|
|
618
|
-
pinnedIdsRef.current = null;
|
|
619
|
-
}
|
|
620
|
-
return next;
|
|
621
|
-
});
|
|
622
|
-
}}
|
|
623
|
-
className={cls(
|
|
624
|
-
{
|
|
625
|
-
"min-h-[42px] py-1 px-2": size === "small",
|
|
626
|
-
"min-h-[56px] py-2 px-4": size === "medium",
|
|
627
|
-
"min-h-[48px] py-1 px-4": size === "large"
|
|
628
|
-
},
|
|
629
|
-
// `rounded-lg` like every other field box —
|
|
630
|
-
// `rounded-md` made this the one control in a
|
|
631
|
-
// row with a different corner.
|
|
632
|
-
"w-full select-none rounded-lg text-sm relative flex items-center",
|
|
633
|
-
invisible ? fieldBackgroundInvisibleMixin : fieldBackgroundMixin,
|
|
634
|
-
disabled ? fieldBackgroundDisabledMixin : fieldBackgroundHoverMixin,
|
|
635
|
-
className
|
|
636
|
-
)}
|
|
637
|
-
>
|
|
638
|
-
<div className="flex justify-between items-center w-full">
|
|
639
|
-
{isLoadingSelectedItems ? (
|
|
640
|
-
<div className="flex items-center gap-2">
|
|
641
|
-
<CircularProgress size="smallest"/>
|
|
642
|
-
<span className="text-sm text-text-secondary dark:text-text-secondary-dark">{loadingText}</span>
|
|
643
|
-
</div>
|
|
644
|
-
) : selectedItems.length > 0 ? (
|
|
645
|
-
<div
|
|
646
|
-
className="flex flex-wrap items-center gap-1.5 text-start flex-1 min-w-0 mr-2">
|
|
647
|
-
{selectedItems.map((item) => {
|
|
648
|
-
if (!useChips || !multiple) {
|
|
649
|
-
|
|
650
|
-
return (
|
|
651
|
-
<div key={String(item.id)}
|
|
652
|
-
className="flex flex-row items-center gap-1 truncate">
|
|
653
|
-
{item.data ? (
|
|
654
|
-
<EntityPreviewBindingData size={"medium"}
|
|
655
|
-
entity={item.data}
|
|
656
|
-
includeEntityLink={false}
|
|
657
|
-
includeId={false}
|
|
658
|
-
onSidePanelClick={closePopover}
|
|
659
|
-
/>
|
|
660
|
-
) : (
|
|
661
|
-
<span className="text-sm truncate">{item.label}</span>
|
|
662
|
-
)}
|
|
663
|
-
</div>
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
return (
|
|
667
|
-
<Chip
|
|
668
|
-
size={"small"}
|
|
669
|
-
key={String(item.id)}
|
|
670
|
-
className={cls("flex flex-row items-center gap-1 truncate")}
|
|
671
|
-
>
|
|
672
|
-
{item.data ? (
|
|
673
|
-
<EntityPreviewBindingData size={"smallest"} entity={item.data}
|
|
674
|
-
includeEntityLink={false}
|
|
675
|
-
includeId={false}/>
|
|
676
|
-
) : (
|
|
677
|
-
<span className="text-sm truncate">{item.label}</span>
|
|
678
|
-
)}
|
|
679
|
-
<XIcon
|
|
680
|
-
size={iconSize.smallest}
|
|
681
|
-
onClick={(event) => {
|
|
682
|
-
event.stopPropagation();
|
|
683
|
-
handleRemoveItem(item);
|
|
684
|
-
}}
|
|
685
|
-
/>
|
|
686
|
-
</Chip>
|
|
687
|
-
);
|
|
688
|
-
})}
|
|
689
|
-
</div>
|
|
690
|
-
) :
|
|
691
|
-
(
|
|
692
|
-
<span className="text-sm text-text-secondary dark:text-text-secondary-dark">
|
|
693
|
-
{resolvedPlaceholder}
|
|
694
|
-
</span>
|
|
695
|
-
)}
|
|
696
|
-
|
|
697
|
-
<div className="flex items-center flex-shrink-0">
|
|
698
|
-
{!multiple && selectedItems.length === 1 && selectedItems[0]?.data && (
|
|
699
|
-
<Tooltip title={`Open ${selectedItems[0].label}`}>
|
|
700
|
-
<IconButton
|
|
701
|
-
component={"div"}
|
|
702
|
-
size={"small"}
|
|
703
|
-
color={"inherit"}
|
|
704
|
-
className="opacity-60 hover:opacity-100"
|
|
705
|
-
onClick={(e) => {
|
|
706
|
-
e.stopPropagation();
|
|
707
|
-
e.preventDefault();
|
|
708
|
-
const entity = selectedItems[0].data!;
|
|
709
|
-
setIsPopoverOpen(false);
|
|
710
|
-
sidePanelController.open({
|
|
711
|
-
entityId: entity.id,
|
|
712
|
-
path: entity.path,
|
|
713
|
-
collection,
|
|
714
|
-
updateUrl: true
|
|
715
|
-
});
|
|
716
|
-
}}>
|
|
717
|
-
<ArrowRightToLineIcon size={iconSize.small}/>
|
|
718
|
-
</IconButton>
|
|
719
|
-
</Tooltip>
|
|
720
|
-
)}
|
|
721
|
-
<ChevronDownIcon
|
|
722
|
-
size={size === "small" ? iconSize.small : iconSize.medium}
|
|
723
|
-
className={cls("transition", isPopoverOpen ? "rotate-180" : "")}
|
|
724
|
-
/>
|
|
725
|
-
</div>
|
|
726
|
-
</div>
|
|
727
|
-
|
|
728
|
-
</button>
|
|
729
|
-
</PopoverPrimitive.Trigger>
|
|
730
|
-
<PopoverPrimitive.Portal container={portalContainer}>
|
|
731
|
-
<PopoverPrimitive.Content
|
|
732
|
-
ref={contentRef}
|
|
733
|
-
data-relation-selector-content
|
|
734
|
-
className={cls("z-50 overflow-hidden border bg-white dark:bg-surface-800 rounded-lg min-w-72", defaultBorderMixin)}
|
|
735
|
-
align="start"
|
|
736
|
-
sideOffset={8}
|
|
737
|
-
side="bottom"
|
|
738
|
-
avoidCollisions={true}
|
|
739
|
-
collisionPadding={16}
|
|
740
|
-
// Allow default auto focus (we manually refocus anyway)
|
|
741
|
-
onOpenAutoFocus={(_e) => { /* leave default or custom manual focus */
|
|
742
|
-
}}
|
|
743
|
-
onCloseAutoFocus={(e) => {
|
|
744
|
-
e.preventDefault();
|
|
745
|
-
}}
|
|
746
|
-
style={{ width: "var(--radix-popover-trigger-width)" }}
|
|
747
|
-
>
|
|
748
|
-
<CommandPrimitive shouldFilter={false}>
|
|
749
|
-
<div className="flex flex-row items-center">
|
|
750
|
-
<div className="relative flex-1">
|
|
751
|
-
<SearchIcon
|
|
752
|
-
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-text-secondary dark:text-text-secondary-dark"
|
|
753
|
-
size={iconSize.smallest}/>
|
|
754
|
-
<CommandPrimitive.Input
|
|
755
|
-
ref={searchInputRef}
|
|
756
|
-
className={cls(
|
|
757
|
-
focusedDisabled,
|
|
758
|
-
"bg-transparent outline-hidden flex-1 h-full w-full pl-10 pr-4 py-3 text-text-primary dark:text-text-primary-dark placeholder:text-text-secondary dark:placeholder:text-text-secondary-dark"
|
|
759
|
-
)}
|
|
760
|
-
placeholder={searchPlaceholder}
|
|
761
|
-
value={searchString}
|
|
762
|
-
onValueChange={handleSearchChange}
|
|
763
|
-
/>
|
|
764
|
-
</div>
|
|
765
|
-
{isLoading && (
|
|
766
|
-
<div className="flex items-center justify-center px-3">
|
|
767
|
-
<CircularProgress size="smallest"/>
|
|
768
|
-
</div>
|
|
769
|
-
)}
|
|
770
|
-
{selectedItems.length > 0 && (
|
|
771
|
-
<div
|
|
772
|
-
onClick={handleClear}
|
|
773
|
-
className="text-sm justify-center cursor-pointer py-3 px-4 text-text-secondary dark:text-text-secondary-dark hover:text-text-primary dark:hover:text-text-primary-dark"
|
|
774
|
-
>
|
|
775
|
-
Clear
|
|
776
|
-
</div>
|
|
777
|
-
)}
|
|
778
|
-
</div>
|
|
779
|
-
<Separator orientation="horizontal" className="my-0"/>
|
|
780
|
-
<CommandPrimitive.List
|
|
781
|
-
ref={scrollContainerRef}
|
|
782
|
-
style={{
|
|
783
|
-
maxHeight: "40vh",
|
|
784
|
-
overflowY: "auto"
|
|
785
|
-
}}
|
|
786
|
-
>
|
|
787
|
-
{isLoading && availableItems.length === 0 && (
|
|
788
|
-
<div className="flex items-center justify-center py-6">
|
|
789
|
-
<CircularProgress size="small"/>
|
|
790
|
-
<span
|
|
791
|
-
className="ml-2 text-sm text-text-secondary dark:text-text-secondary-dark">{loadingText}</span>
|
|
792
|
-
</div>
|
|
793
|
-
)}
|
|
794
|
-
{/* Two different nothings, which used to share one
|
|
795
|
-
message ("No relations found.") even though the
|
|
796
|
-
fix for each is the opposite: clear the search, or
|
|
797
|
-
go and create the first row. With the fetch done,
|
|
798
|
-
the search string is what tells them apart. */}
|
|
799
|
-
{!isLoading && availableItems.length === 0 && (
|
|
800
|
-
<CommandPrimitive.Empty
|
|
801
|
-
className="px-4 py-6 text-center text-text-secondary dark:text-text-secondary-dark">
|
|
802
|
-
{searchString
|
|
803
|
-
? noResultsText
|
|
804
|
-
: (emptyCollectionText ?? `No ${collection.name ?? "entries"} yet.`)}
|
|
805
|
-
</CommandPrimitive.Empty>
|
|
806
|
-
)}
|
|
807
|
-
<CommandPrimitive.Group>
|
|
808
|
-
{(() => {
|
|
809
|
-
// Sort items so that initially-selected (pinned) items appear first.
|
|
810
|
-
// We use the entity taken when the popover opened so items don't
|
|
811
|
-
// jump around as the user checks/unchecks.
|
|
812
|
-
const pinned = pinnedIdsRef.current;
|
|
813
|
-
const sortedItems = pinned && pinned.size > 0
|
|
814
|
-
? [...availableItems].sort((a, b) => {
|
|
815
|
-
const aP = pinned.has(String(a.id)) ? 0 : 1;
|
|
816
|
-
const bP = pinned.has(String(b.id)) ? 0 : 1;
|
|
817
|
-
return aP - bP;
|
|
818
|
-
})
|
|
819
|
-
: availableItems;
|
|
820
|
-
return sortedItems;
|
|
821
|
-
})().map((item) => {
|
|
822
|
-
const isSelected = selectedItems.some(v => String(v.id) === String(item.id));
|
|
823
|
-
return (
|
|
824
|
-
<CommandPrimitive.Item
|
|
825
|
-
key={String(item.id)}
|
|
826
|
-
value={String(item.id)}
|
|
827
|
-
onMouseDown={(e) => {
|
|
828
|
-
e.preventDefault();
|
|
829
|
-
e.stopPropagation();
|
|
830
|
-
}}
|
|
831
|
-
onSelect={() => onItemClick(item)}
|
|
832
|
-
className={cls(
|
|
833
|
-
"flex flex-row items-center gap-1.5 m-1 p-1 rounded-xs cursor-pointer ring-offset-transparent",
|
|
834
|
-
isSelected && "bg-surface-accent-200 dark:bg-surface-accent-950",
|
|
835
|
-
"aria-selected:outline-hidden aria-selected:ring-2 aria-selected:ring-primary/75 aria-selected:ring-offset-2 aria-selected:bg-surface-accent-100 dark:aria-selected:bg-surface-accent-900"
|
|
836
|
-
)}
|
|
837
|
-
>
|
|
838
|
-
{multiple && (<InnerCheckBox checked={isSelected}/>)}
|
|
839
|
-
{item.data ? (
|
|
840
|
-
<div
|
|
841
|
-
className="flex flex-row items-center gap-2 min-w-0 w-full">
|
|
842
|
-
<EntityPreviewBindingData
|
|
843
|
-
size={multiple ? "smallest" : "medium"}
|
|
844
|
-
entity={item.data}
|
|
845
|
-
includeId={false}
|
|
846
|
-
includeEntityLink={true}
|
|
847
|
-
/>
|
|
848
|
-
</div>
|
|
849
|
-
) : (
|
|
850
|
-
<div>
|
|
851
|
-
<div
|
|
852
|
-
className="text-sm font-medium text-text-primary dark:text-text-primary-dark">{item.label}</div>
|
|
853
|
-
{item.description && (
|
|
854
|
-
<div
|
|
855
|
-
className="text-xs text-text-secondary dark:text-text-secondary-dark">{item.description}</div>
|
|
856
|
-
)}
|
|
857
|
-
</div>
|
|
858
|
-
)}
|
|
859
|
-
</CommandPrimitive.Item>
|
|
860
|
-
);
|
|
861
|
-
})}
|
|
862
|
-
{availableItems.length > 0 && hasMore && (
|
|
863
|
-
<div ref={sentinelCallbackRef} className="h-1 w-full"
|
|
864
|
-
style={{ visibility: "hidden" }}/>
|
|
865
|
-
)}
|
|
866
|
-
{isLoading && availableItems.length > 0 && (
|
|
867
|
-
<div className="flex items-center justify-center py-4">
|
|
868
|
-
<CircularProgress size="smallest"/>
|
|
869
|
-
<span
|
|
870
|
-
className="ml-2 text-xs text-text-secondary dark:text-text-secondary-dark">Loading...</span>
|
|
871
|
-
</div>
|
|
872
|
-
)}
|
|
873
|
-
</CommandPrimitive.Group>
|
|
874
|
-
</CommandPrimitive.List>
|
|
875
|
-
{canCreateTarget && <>
|
|
876
|
-
<Separator orientation="horizontal" className="my-0"/>
|
|
877
|
-
{/* Outside the scrolling list on purpose: it
|
|
878
|
-
is most wanted exactly when the list is
|
|
879
|
-
long and nothing in it matched. */}
|
|
880
|
-
<button
|
|
881
|
-
type="button"
|
|
882
|
-
data-relation-selector-create
|
|
883
|
-
onMouseDown={(e) => {
|
|
884
|
-
e.preventDefault();
|
|
885
|
-
e.stopPropagation();
|
|
886
|
-
}}
|
|
887
|
-
onClick={handleCreateNew}
|
|
888
|
-
className={cls(
|
|
889
|
-
"w-full flex flex-row items-center gap-2 px-4 py-3 text-sm text-left",
|
|
890
|
-
"text-primary hover:bg-surface-accent-50 dark:hover:bg-surface-800"
|
|
891
|
-
)}>
|
|
892
|
-
<PlusIcon size={iconSize.smallest}/>
|
|
893
|
-
<span className="truncate">
|
|
894
|
-
{searchString.trim()
|
|
895
|
-
? t("add_named", { name: searchString.trim() })
|
|
896
|
-
: t("add_specific", { name: collection.singularName ?? collection.name })}
|
|
897
|
-
</span>
|
|
898
|
-
</button>
|
|
899
|
-
</>}
|
|
900
|
-
</CommandPrimitive>
|
|
901
|
-
</PopoverPrimitive.Content>
|
|
902
|
-
</PopoverPrimitive.Portal>
|
|
903
|
-
</PopoverPrimitive.Root>
|
|
904
|
-
</>
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
);
|
|
908
|
-
|
|
909
|
-
RelationSelector.displayName = "RelationSelector";
|
|
910
|
-
|
|
911
|
-
function InnerCheckBox({ checked }: { checked: boolean }) {
|
|
912
|
-
return (
|
|
913
|
-
<div className={cls(
|
|
914
|
-
"p-2 w-8 h-8 inline-flex items-center justify-center text-sm font-medium focus:outline-hidden transition-colors ease-in-out duration-150"
|
|
915
|
-
)}>
|
|
916
|
-
<div
|
|
917
|
-
className={cls(
|
|
918
|
-
"border-2 relative transition-colors ease-in-out duration-150 w-4 h-4 rounded-xs flex items-center justify-center",
|
|
919
|
-
checked ? "bg-primary text-surface-accent-100 dark:text-surface-accent-900 border-transparent" : "bg-white dark:bg-surface-accent-900 border-surface-accent-800 dark:border-surface-accent-200"
|
|
920
|
-
)}
|
|
921
|
-
>
|
|
922
|
-
{checked && <CheckIcon size={iconSize.smallest} className="absolute"/>}
|
|
923
|
-
</div>
|
|
924
|
-
</div>
|
|
925
|
-
);
|
|
926
|
-
}
|