@firecms/core 3.2.0-canary.9c3d298 → 3.3.0-canary.040c21c
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/dist/app/AppBar.d.ts +1 -1
- package/dist/app/Drawer.d.ts +1 -1
- package/dist/components/AIIcon.d.ts +3 -2
- package/dist/components/ArrayContainer.d.ts +3 -3
- package/dist/components/CircularProgressCenter.d.ts +2 -1
- package/dist/components/ClearFilterSortButton.d.ts +1 -1
- package/dist/components/ConfirmationDialog.d.ts +1 -1
- package/dist/components/DeleteEntityDialog.d.ts +2 -1
- package/dist/components/EntityCollectionTable/EntityCollectionRowActions.d.ts +1 -1
- package/dist/components/EntityCollectionTable/EntityCollectionTable.d.ts +2 -1
- package/dist/components/EntityCollectionTable/fields/TableReferenceField.d.ts +1 -1
- package/dist/components/EntityCollectionTable/fields/TableStorageUpload.d.ts +2 -2
- package/dist/components/EntityCollectionTable/internal/CollectionTableToolbar.d.ts +1 -1
- package/dist/components/EntityCollectionTable/internal/EntityTableCellActions.d.ts +1 -1
- package/dist/components/EntityCollectionTable/internal/popup_field/PopupFormField.d.ts +4 -3
- package/dist/components/EntityCollectionView/Board.d.ts +2 -1
- package/dist/components/EntityCollectionView/BoardColumnTitle.d.ts +1 -1
- package/dist/components/EntityCollectionView/BoardSortableList.d.ts +1 -1
- package/dist/components/EntityCollectionView/CollectionDataErrorBanner.d.ts +2 -1
- package/dist/components/EntityCollectionView/EntityBoardCard.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCard.d.ts +2 -1
- package/dist/components/EntityCollectionView/EntityCollectionBoardView.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionCardView.d.ts +1 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewActions.d.ts +2 -1
- package/dist/components/EntityCollectionView/EntityCollectionViewStartActions.d.ts +2 -1
- package/dist/components/EntityCollectionView/FiltersDialog.d.ts +2 -1
- package/dist/components/EntityCollectionView/ViewModeToggle.d.ts +2 -1
- package/dist/components/EntityJsonPreview.d.ts +2 -1
- package/dist/components/EntityPreview.d.ts +1 -1
- package/dist/components/EntityView.d.ts +2 -1
- package/dist/components/ErrorBoundary.d.ts +1 -1
- package/dist/components/ErrorTooltip.d.ts +2 -1
- package/dist/components/FieldCaption.d.ts +1 -1
- package/dist/components/FireCMSLogo.d.ts +1 -1
- package/dist/components/HomePage/DefaultHomePage.d.ts +1 -1
- package/dist/components/HomePage/FavouritesView.d.ts +1 -1
- package/dist/components/HomePage/HomePageDnD.d.ts +4 -4
- package/dist/components/HomePage/NavigationCardBinding.d.ts +2 -1
- package/dist/components/HomePage/NavigationGroup.d.ts +2 -2
- package/dist/components/HomePage/RenameGroupDialog.d.ts +2 -1
- package/dist/components/HomePage/SmallNavigationCard.d.ts +1 -1
- package/dist/components/LanguageToggle.d.ts +2 -1
- package/dist/components/NotFoundPage.d.ts +2 -1
- package/dist/components/PropertyCollectionView.d.ts +2 -1
- package/dist/components/PropertyIdCopyTooltip.d.ts +2 -2
- package/dist/components/ReferenceTable/ReferenceSelectionTable.d.ts +1 -1
- package/dist/components/ReferenceWidget.d.ts +2 -1
- package/dist/components/SearchIconsView.d.ts +2 -1
- package/dist/components/SelectableTable/SelectableTable.d.ts +1 -1
- package/dist/components/SelectableTable/filters/BooleanFilterField.d.ts +2 -1
- package/dist/components/SelectableTable/filters/DateTimeFilterField.d.ts +2 -1
- package/dist/components/SelectableTable/filters/ReferenceFilterField.d.ts +2 -1
- package/dist/components/SelectableTable/filters/StringNumberFilterField.d.ts +2 -1
- package/dist/components/UnsavedChangesDialog.d.ts +1 -1
- package/dist/components/UserDisplay.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +1 -0
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +2 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +6 -1
- package/dist/components/VirtualTable/fields/VirtualTableDateField.d.ts +1 -1
- package/dist/components/VirtualTable/fields/VirtualTableInput.d.ts +2 -1
- package/dist/components/VirtualTable/fields/VirtualTableNumberInput.d.ts +2 -1
- package/dist/components/VirtualTable/fields/VirtualTableSelect.d.ts +1 -1
- package/dist/components/VirtualTable/fields/VirtualTableSwitch.d.ts +2 -1
- package/dist/components/VirtualTable/fields/VirtualTableUserSelect.d.ts +1 -1
- package/dist/components/VirtualTable/types.d.ts +1 -0
- package/dist/core/DefaultAppBar.d.ts +1 -1
- package/dist/core/DefaultDrawer.d.ts +2 -2
- package/dist/core/DrawerNavigationGroup.d.ts +1 -1
- package/dist/core/DrawerNavigationItem.d.ts +1 -1
- package/dist/core/EntityEditView.d.ts +2 -2
- package/dist/core/EntityEditViewFormActions.d.ts +2 -1
- package/dist/core/EntitySidePanel.d.ts +2 -1
- package/dist/core/FireCMS.d.ts +2 -1
- package/dist/core/FireCMSRouter.d.ts +1 -1
- package/dist/core/SideDialogs.d.ts +1 -1
- package/dist/editor/components/SlashCommandMenu.d.ts +2 -1
- package/dist/editor/editor.d.ts +1 -1
- package/dist/editor/selectors/color-selector.d.ts +1 -1
- package/dist/editor/selectors/link-selector.d.ts +1 -1
- package/dist/editor/selectors/node-selector.d.ts +1 -1
- package/dist/editor/selectors/text-buttons.d.ts +1 -1
- package/dist/form/EntityForm.d.ts +1 -1
- package/dist/form/EntityFormActions.d.ts +1 -1
- package/dist/form/components/CustomIdField.d.ts +2 -1
- package/dist/form/components/FieldHelperText.d.ts +1 -1
- package/dist/form/components/FormEntry.d.ts +1 -1
- package/dist/form/components/FormLayout.d.ts +1 -1
- package/dist/form/components/LabelWithIconAndTooltip.d.ts +1 -1
- package/dist/form/components/LocalChangesMenu.d.ts +2 -1
- package/dist/form/components/StorageItemPreview.d.ts +2 -1
- package/dist/form/components/StorageUploadProgress.d.ts +2 -1
- package/dist/form/field_bindings/ArrayCustomShapedFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/ArrayOfReferencesFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/BlockFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/DateTimeFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/KeyValueFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/MapFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/MarkdownEditorFieldBinding.d.ts +3 -2
- package/dist/form/field_bindings/MultiSelectFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/ReadOnlyFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/ReferenceAsStringFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/ReferenceFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/RepeatFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/SelectFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/StorageUploadFieldBinding.d.ts +3 -2
- package/dist/form/field_bindings/SwitchFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/TextFieldBinding.d.ts +2 -1
- package/dist/form/field_bindings/UserSelectFieldBinding.d.ts +2 -1
- package/dist/i18n/FireCMSi18nProvider.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +24457 -23726
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +22959 -22228
- package/dist/index.umd.js.map +1 -1
- package/dist/preview/components/ArrayEnumPreview.d.ts +2 -1
- package/dist/preview/components/AsyncPreviewComponent.d.ts +1 -1
- package/dist/preview/components/EmptyValue.d.ts +2 -1
- package/dist/preview/components/EnumValuesChip.d.ts +1 -1
- package/dist/preview/components/ImagePreview.d.ts +2 -1
- package/dist/preview/components/ReferencePreview.d.ts +1 -1
- package/dist/preview/components/StorageThumbnail.d.ts +1 -1
- package/dist/preview/components/UserPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfMapsPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfReferencesPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOfStorageComponentsPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +2 -1
- package/dist/preview/property_previews/ArrayPropertyEnumPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +2 -1
- package/dist/preview/property_previews/MapPropertyPreview.d.ts +3 -2
- package/dist/preview/property_previews/SkeletonPropertyComponent.d.ts +5 -4
- package/dist/routes/FireCMSRoute.d.ts +2 -1
- package/dist/types/collections.d.ts +38 -0
- package/dist/types/properties.d.ts +9 -8
- package/dist/types/translations.d.ts +23 -0
- package/dist/util/index.d.ts +1 -0
- package/dist/util/lazy_eager.d.ts +7 -0
- package/dist/util/objects.d.ts +1 -0
- package/dist/util/property_utils.d.ts +1 -1
- package/package.json +5 -5
- package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +9 -3
- package/src/components/EntityCollectionTable/internal/common.tsx +2 -2
- package/src/components/EntityCollectionView/EntityCollectionView.tsx +3 -5
- package/src/components/EntityJsonPreview.tsx +2 -1
- package/src/components/ErrorBoundary.tsx +3 -3
- package/src/components/VirtualTable/VirtualTable.tsx +5 -3
- package/src/components/VirtualTable/VirtualTableHeader.tsx +9 -8
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +8 -3
- package/src/components/VirtualTable/VirtualTableProps.tsx +7 -1
- package/src/components/VirtualTable/types.tsx +1 -0
- package/src/core/DrawerNavigationGroup.tsx +1 -1
- package/src/core/EntityEditView.tsx +50 -5
- package/src/core/EntitySidePanel.tsx +2 -1
- package/src/core/field_configs.tsx +4 -2
- package/src/editor/editor.tsx +20 -1
- package/src/editor/markdown.ts +89 -1
- package/src/form/EntityForm.tsx +64 -4
- package/src/form/PropertyFieldBinding.tsx +4 -3
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +18 -5
- package/src/form/field_bindings/ArrayOfReferencesFieldBinding.tsx +18 -5
- package/src/form/field_bindings/BlockFieldBinding.tsx +21 -7
- package/src/form/field_bindings/DateTimeFieldBinding.tsx +1 -1
- package/src/form/field_bindings/KeyValueFieldBinding.tsx +23 -6
- package/src/form/field_bindings/MapFieldBinding.tsx +23 -8
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +43 -20
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +15 -1
- package/src/form/field_bindings/ReferenceAsStringFieldBinding.tsx +25 -11
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +25 -11
- package/src/form/field_bindings/RepeatFieldBinding.tsx +18 -5
- package/src/form/field_bindings/SelectFieldBinding.tsx +7 -5
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +24 -7
- package/src/form/field_bindings/SwitchFieldBinding.tsx +31 -14
- package/src/form/field_bindings/TextFieldBinding.tsx +10 -7
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +7 -5
- package/src/index.ts +1 -0
- package/src/internal/useBuildSideEntityController.tsx +1 -1
- package/src/locales/de.ts +28 -1
- package/src/locales/en.ts +27 -0
- package/src/locales/es.ts +28 -1
- package/src/locales/fr.ts +28 -1
- package/src/locales/hi.ts +28 -1
- package/src/locales/it.ts +28 -1
- package/src/locales/pt.ts +28 -1
- package/src/preview/PropertyPreview.tsx +6 -5
- package/src/preview/components/ReferencePreview.tsx +2 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +49 -27
- package/src/routes/FireCMSRoute.tsx +63 -54
- package/src/types/collections.ts +40 -0
- package/src/types/properties.ts +11 -10
- package/src/types/translations.ts +27 -0
- package/src/util/index.ts +1 -0
- package/src/util/lazy_eager.tsx +33 -0
- package/src/util/objects.ts +15 -0
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
useFireCMSContext,
|
|
27
27
|
useLargeLayout
|
|
28
28
|
} from "../hooks";
|
|
29
|
-
import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography } from "@firecms/ui";
|
|
29
|
+
import { CircularProgress, cls, CodeIcon, defaultBorderMixin, Tab, Tabs, Typography, Menu, MenuItem, ExpandMoreIcon } from "@firecms/ui";
|
|
30
30
|
import { getEntityFromMemoryCache } from "../util/entity_cache";
|
|
31
31
|
import { EntityForm, EntityFormProps } from "../form";
|
|
32
32
|
import { EntityEditViewFormActions } from "./EntityEditViewFormActions";
|
|
@@ -230,6 +230,10 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
230
230
|
const includeJsonView = collection.includeJsonView === undefined ? true : collection.includeJsonView;
|
|
231
231
|
const hasAdditionalViews = customViewsCount > 0 || subcollectionsCount > 0 || includeJsonView;
|
|
232
232
|
|
|
233
|
+
const groupedViews = useMemo(() => {
|
|
234
|
+
return (collection.viewGroups ?? []).flatMap(g => g.views);
|
|
235
|
+
}, [collection.viewGroups]);
|
|
236
|
+
|
|
233
237
|
const {
|
|
234
238
|
resolvedEntityViews,
|
|
235
239
|
selectedEntityView,
|
|
@@ -419,16 +423,18 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
419
423
|
Builder={selectedSecondaryForm?.Builder}
|
|
420
424
|
/>;
|
|
421
425
|
|
|
422
|
-
const subcollectionTabs = subcollections && subcollections
|
|
426
|
+
const subcollectionTabs = subcollections && subcollections
|
|
427
|
+
.filter(sub => !groupedViews.includes(sub.id ?? sub.path))
|
|
428
|
+
.map((subcollection) =>
|
|
423
429
|
<Tab
|
|
424
430
|
className="text-sm min-w-[120px]"
|
|
425
|
-
value={subcollection.id}
|
|
431
|
+
value={subcollection.id ?? subcollection.path}
|
|
426
432
|
key={`entity_detail_collection_tab_${subcollection.name}`}>
|
|
427
433
|
{subcollection.name}
|
|
428
434
|
</Tab>
|
|
429
435
|
);
|
|
430
436
|
|
|
431
|
-
const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start")
|
|
437
|
+
const customViewTabsStart = resolvedEntityViews.filter(view => view.position === "start" && !groupedViews.includes(view.key))
|
|
432
438
|
.map((view) =>
|
|
433
439
|
<Tab
|
|
434
440
|
className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
|
|
@@ -437,7 +443,7 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
437
443
|
{view.tabComponent ?? view.name}
|
|
438
444
|
</Tab>
|
|
439
445
|
);
|
|
440
|
-
const customViewTabsEnd = resolvedEntityViews.filter(view => !view.position || view.position === "end")
|
|
446
|
+
const customViewTabsEnd = resolvedEntityViews.filter(view => (!view.position || view.position === "end") && !groupedViews.includes(view.key))
|
|
441
447
|
.map((view) =>
|
|
442
448
|
<Tab
|
|
443
449
|
className={!view.tabComponent ? "text-sm min-w-[120px]" : undefined}
|
|
@@ -447,6 +453,43 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
447
453
|
</Tab>
|
|
448
454
|
);
|
|
449
455
|
|
|
456
|
+
const viewGroupMenus = collection.viewGroups?.map(group => {
|
|
457
|
+
const isActive = group.views.includes(selectedTab);
|
|
458
|
+
return (
|
|
459
|
+
<Menu
|
|
460
|
+
key={`view_group_${group.name}`}
|
|
461
|
+
trigger={
|
|
462
|
+
<button
|
|
463
|
+
type="button"
|
|
464
|
+
className={cls(
|
|
465
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-white transition-all",
|
|
466
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-surface-400 focus-visible:ring-offset-2",
|
|
467
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
468
|
+
isActive ? "bg-white text-surface-900 dark:bg-surface-950 dark:text-surface-50" : "text-surface-600 dark:text-surface-400 hover:bg-surface-200 dark:hover:bg-surface-800"
|
|
469
|
+
)}
|
|
470
|
+
>
|
|
471
|
+
{group.name}
|
|
472
|
+
<ExpandMoreIcon className="ml-1 -mr-1" size="small" />
|
|
473
|
+
</button>
|
|
474
|
+
}>
|
|
475
|
+
{group.views.map(viewId => {
|
|
476
|
+
const subcollection = subcollections.find(s => (s.id ?? s.path) === viewId);
|
|
477
|
+
const customView = resolvedEntityViews.find(v => v.key === viewId);
|
|
478
|
+
const name = subcollection?.name ?? customView?.name ?? viewId;
|
|
479
|
+
return (
|
|
480
|
+
<MenuItem
|
|
481
|
+
key={`view_group_${group.name}_${viewId}`}
|
|
482
|
+
onClick={() => onSideTabClick(viewId)}
|
|
483
|
+
className={selectedTab === viewId ? "bg-surface-accent-100 dark:bg-surface-accent-900" : ""}
|
|
484
|
+
>
|
|
485
|
+
{name}
|
|
486
|
+
</MenuItem>
|
|
487
|
+
);
|
|
488
|
+
})}
|
|
489
|
+
</Menu>
|
|
490
|
+
);
|
|
491
|
+
});
|
|
492
|
+
|
|
450
493
|
const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
|
|
451
494
|
|
|
452
495
|
let result = <div className="relative flex flex-col h-full w-full bg-white dark:bg-surface-900">
|
|
@@ -494,6 +537,8 @@ export function EntityEditViewInner<M extends Record<string, any>>({
|
|
|
494
537
|
|
|
495
538
|
{customViewTabsEnd}
|
|
496
539
|
|
|
540
|
+
{viewGroupMenus}
|
|
541
|
+
|
|
497
542
|
{subcollectionTabs}
|
|
498
543
|
</Tabs>}
|
|
499
544
|
</div>}
|
|
@@ -78,7 +78,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
78
78
|
return navigationController.getParentCollectionIds(path);
|
|
79
79
|
}, [navigationController, path]);
|
|
80
80
|
|
|
81
|
-
const collection = navigationController.getCollection(fullIdPath ?? path)
|
|
81
|
+
const collection = props.collection ?? navigationController.getCollection(fullIdPath ?? path);
|
|
82
82
|
|
|
83
83
|
useEffect(() => {
|
|
84
84
|
function beforeunload(e: any) {
|
|
@@ -112,6 +112,7 @@ export function EntitySidePanel(props: EntitySidePanelProps) {
|
|
|
112
112
|
return (
|
|
113
113
|
<>
|
|
114
114
|
<ErrorBoundary>
|
|
115
|
+
|
|
115
116
|
<EntityEditView
|
|
116
117
|
{...props}
|
|
117
118
|
fullIdPath={fullIdPath}
|
|
@@ -8,16 +8,18 @@ import {
|
|
|
8
8
|
DateTimeFieldBinding,
|
|
9
9
|
KeyValueFieldBinding,
|
|
10
10
|
MapFieldBinding,
|
|
11
|
-
MarkdownEditorFieldBinding,
|
|
12
11
|
MultiSelectFieldBinding,
|
|
13
12
|
ReferenceAsStringFieldBinding,
|
|
14
13
|
ReferenceFieldBinding,
|
|
15
14
|
RepeatFieldBinding,
|
|
16
15
|
SelectFieldBinding,
|
|
17
|
-
StorageUploadFieldBinding,
|
|
18
16
|
SwitchFieldBinding,
|
|
19
17
|
TextFieldBinding
|
|
20
18
|
} from "../form";
|
|
19
|
+
import { lazyEager } from "../util/lazy_eager";
|
|
20
|
+
|
|
21
|
+
const MarkdownEditorFieldBinding = lazyEager<typeof import("../form/field_bindings/MarkdownEditorFieldBinding")["MarkdownEditorFieldBinding"]>(() => import("../form/field_bindings/MarkdownEditorFieldBinding"), "MarkdownEditorFieldBinding");
|
|
22
|
+
const StorageUploadFieldBinding = lazyEager<typeof import("../form/field_bindings/StorageUploadFieldBinding")["StorageUploadFieldBinding"]>(() => import("../form/field_bindings/StorageUploadFieldBinding"), "StorageUploadFieldBinding");
|
|
21
23
|
import { isPropertyBuilder, mergeDeep } from "../util";
|
|
22
24
|
|
|
23
25
|
import {
|
package/src/editor/editor.tsx
CHANGED
|
@@ -148,8 +148,26 @@ export const FireCMSEditor = ({
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
const doc = state?.doc;
|
|
151
|
+
const mountedRef = useRef(false);
|
|
152
|
+
|
|
153
|
+
// Enable flushing after the initial render cycle completes.
|
|
154
|
+
// ProseMirror initialization (including trailingNodePlugin and
|
|
155
|
+
// appendTransaction) runs synchronously during mount, so any doc
|
|
156
|
+
// change after the first effect cycle is from user interaction.
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
const raf = requestAnimationFrame(() => {
|
|
159
|
+
mountedRef.current = true;
|
|
160
|
+
});
|
|
161
|
+
return () => cancelAnimationFrame(raf);
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
151
164
|
useEffect(() => {
|
|
152
165
|
if (!state) return;
|
|
166
|
+
// Skip flush until after mount — the round-trip through
|
|
167
|
+
// parse → ProseMirror → serialize is not idempotent and would
|
|
168
|
+
// produce subtly different markdown on init, making the form
|
|
169
|
+
// dirty without user interaction.
|
|
170
|
+
if (!mountedRef.current) return;
|
|
153
171
|
const timeout = setTimeout(() => {
|
|
154
172
|
flushChanges(state);
|
|
155
173
|
}, 250);
|
|
@@ -160,6 +178,7 @@ export const FireCMSEditor = ({
|
|
|
160
178
|
if (!view) return;
|
|
161
179
|
const dom = view.dom;
|
|
162
180
|
const handleBlur = () => {
|
|
181
|
+
if (!mountedRef.current) return;
|
|
163
182
|
flushChanges(view.state);
|
|
164
183
|
};
|
|
165
184
|
dom.addEventListener("blur", handleBlur);
|
|
@@ -236,7 +255,7 @@ export const FireCMSEditor = ({
|
|
|
236
255
|
onChange={handleMarkdownChange as any}
|
|
237
256
|
onBlur={handleMarkdownBlur as any}
|
|
238
257
|
className={cls(
|
|
239
|
-
"w-full
|
|
258
|
+
"w-full min-h-[300px] p-12 bg-transparent resize-none font-mono focus:ring-0 focus:outline-none outline-none",
|
|
240
259
|
proseClass
|
|
241
260
|
)}
|
|
242
261
|
style={{
|
package/src/editor/markdown.ts
CHANGED
|
@@ -49,6 +49,66 @@ const md = markdownIt({ html: false })
|
|
|
49
49
|
.use(markdownItMark)
|
|
50
50
|
.use(markdownItIns);
|
|
51
51
|
|
|
52
|
+
// Override the escape rule so that `\` before a newline is kept as literal
|
|
53
|
+
// text instead of being silently consumed as a hardbreak. The default
|
|
54
|
+
// markdown-it behaviour strips the backslash and produces a <br>, which
|
|
55
|
+
// causes users to lose visible `\` characters in their content.
|
|
56
|
+
md.inline.ruler.at("escape", function escapeOverride(state: any, silent: boolean): boolean {
|
|
57
|
+
let pos = state.pos;
|
|
58
|
+
const max = state.posMax;
|
|
59
|
+
|
|
60
|
+
if (state.src.charCodeAt(pos) !== 0x5C /* \ */) return false;
|
|
61
|
+
pos++;
|
|
62
|
+
|
|
63
|
+
if (pos >= max) return false;
|
|
64
|
+
|
|
65
|
+
const ch1 = state.src.charCodeAt(pos);
|
|
66
|
+
|
|
67
|
+
// KEY CHANGE: when `\` is followed by a newline, output the backslash as
|
|
68
|
+
// literal text and let the normal softbreak handling deal with the newline.
|
|
69
|
+
if (ch1 === 0x0A) {
|
|
70
|
+
if (!silent) {
|
|
71
|
+
state.pending += "\\";
|
|
72
|
+
}
|
|
73
|
+
state.pos = pos; // leave the newline for softbreak to handle
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// For escaped ASCII punctuation, output the character without the backslash
|
|
78
|
+
// (standard markdown escape behaviour: `\*` → `*`).
|
|
79
|
+
let escapedStr = state.src[pos];
|
|
80
|
+
// Handle surrogate pairs
|
|
81
|
+
if (ch1 >= 0xD800 && ch1 <= 0xDBFF && pos + 1 < max) {
|
|
82
|
+
const ch2 = state.src.charCodeAt(pos + 1);
|
|
83
|
+
if (ch2 >= 0xDC00 && ch2 <= 0xDFFF) {
|
|
84
|
+
escapedStr += state.src[pos + 1];
|
|
85
|
+
pos++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const origStr = "\\" + escapedStr;
|
|
90
|
+
|
|
91
|
+
if (!silent) {
|
|
92
|
+
// Check if the character is an ASCII punctuation that
|
|
93
|
+
// markdown-it considers escapable (codes < 256 in its lookup table).
|
|
94
|
+
const isEscapable = ch1 < 256 && /[\\!"#$%&'()*+,./:;<=>?@[\]^_`{|}~-]/.test(String.fromCharCode(ch1));
|
|
95
|
+
const token = state.push("text_special", "", 0);
|
|
96
|
+
if (isEscapable) {
|
|
97
|
+
token.content = escapedStr;
|
|
98
|
+
} else {
|
|
99
|
+
token.content = origStr;
|
|
100
|
+
}
|
|
101
|
+
token.markup = origStr;
|
|
102
|
+
token.info = "escape";
|
|
103
|
+
}
|
|
104
|
+
state.pos = pos + 1;
|
|
105
|
+
return true;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Also disable the newline rule which redundantly converts `\` + newline
|
|
109
|
+
// to hardbreaks via a separate code path.
|
|
110
|
+
md.inline.ruler.disable(["newline"]);
|
|
111
|
+
|
|
52
112
|
// Unwrap images from paragraphs so they can be parsed as block nodes by ProseMirror
|
|
53
113
|
md.core.ruler.after("inline", "image-to-block", (state: any) => {
|
|
54
114
|
const tokens = state.tokens;
|
|
@@ -145,7 +205,35 @@ export const markdownSerializer = new MarkdownSerializer(
|
|
|
145
205
|
},
|
|
146
206
|
table_row() {},
|
|
147
207
|
table_cell() {},
|
|
148
|
-
table_header() {}
|
|
208
|
+
table_header() {},
|
|
209
|
+
// Custom text serializer: since our parser override keeps `\` as
|
|
210
|
+
// literal text (instead of consuming it), we must avoid the default
|
|
211
|
+
// esc() from double-escaping it. We escape all standard markdown
|
|
212
|
+
// specials *except* the backslash itself.
|
|
213
|
+
text(state: any, node: any) {
|
|
214
|
+
const escaped = node.text.replace(/[`*~\[\]_]/g, (m: string, i: number) => {
|
|
215
|
+
// Don't escape mid-word underscores (same logic as default esc)
|
|
216
|
+
if (m === "_" && i > 0 && i + 1 < node.text.length
|
|
217
|
+
&& /\w/.test(node.text[i - 1]) && /\w/.test(node.text[i + 1])) {
|
|
218
|
+
return m;
|
|
219
|
+
}
|
|
220
|
+
return "\\" + m;
|
|
221
|
+
});
|
|
222
|
+
// Handle start-of-line patterns that could be parsed as block syntax
|
|
223
|
+
const lines = escaped.split("\n");
|
|
224
|
+
for (let i = 0; i < lines.length; i++) {
|
|
225
|
+
state.write();
|
|
226
|
+
let line = lines[i];
|
|
227
|
+
if (state.atBlockStart || i > 0) {
|
|
228
|
+
line = line
|
|
229
|
+
.replace(/^(\+[ ]|[-*>])/, "\\$&")
|
|
230
|
+
.replace(/^(\s*)(#{1,6})(\s|$)/, '$1\\$2$3')
|
|
231
|
+
.replace(/^(\s*\d+)\.\s/, "$1\\. ");
|
|
232
|
+
}
|
|
233
|
+
state.out += line;
|
|
234
|
+
if (i !== lines.length - 1) state.out += "\n";
|
|
235
|
+
}
|
|
236
|
+
}
|
|
149
237
|
},
|
|
150
238
|
{
|
|
151
239
|
...defaultMarkdownSerializer.marks,
|
package/src/form/EntityForm.tsx
CHANGED
|
@@ -125,6 +125,35 @@ export function extractTouchedValues(values: any, touched: Record<string, boolea
|
|
|
125
125
|
return acc;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Recursively removes empty plain objects `{}` and empty arrays `[]` from a value tree.
|
|
130
|
+
* This prevents ghost containers created by `setIn` intermediate path construction
|
|
131
|
+
* (e.g. `{ address: {} }` when only `address.city` was touched but value is undefined)
|
|
132
|
+
* from falsely triggering the unsaved local changes indicator.
|
|
133
|
+
*/
|
|
134
|
+
function removeEmptyContainers(obj: any): any {
|
|
135
|
+
if (Array.isArray(obj)) {
|
|
136
|
+
const cleaned = obj.map(removeEmptyContainers);
|
|
137
|
+
// Keep arrays even if they contain only nulls/undefined — that's intentional data
|
|
138
|
+
return cleaned;
|
|
139
|
+
}
|
|
140
|
+
if (obj && typeof obj === "object" && Object.getPrototypeOf(obj) === Object.prototype) {
|
|
141
|
+
const result: Record<string, any> = {};
|
|
142
|
+
for (const key of Object.keys(obj)) {
|
|
143
|
+
const cleaned = removeEmptyContainers(obj[key]);
|
|
144
|
+
// Skip empty plain objects
|
|
145
|
+
if (cleaned && typeof cleaned === "object" && !Array.isArray(cleaned)
|
|
146
|
+
&& Object.getPrototypeOf(cleaned) === Object.prototype
|
|
147
|
+
&& Object.keys(cleaned).length === 0) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
result[key] = cleaned;
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
return obj;
|
|
155
|
+
}
|
|
156
|
+
|
|
128
157
|
export function getChanges<T extends object>(source: Partial<T>, comparison: Partial<T>): Partial<T> {
|
|
129
158
|
const changes: Partial<T> = {};
|
|
130
159
|
|
|
@@ -251,7 +280,7 @@ export function EntityForm<M extends Record<string, any>>({
|
|
|
251
280
|
const context = useFireCMSContext();
|
|
252
281
|
const analyticsController = useAnalyticsController();
|
|
253
282
|
|
|
254
|
-
const [underlyingChanges] = useState<Partial<EntityValues<M>>>({});
|
|
283
|
+
const [underlyingChanges, setUnderlyingChanges] = useState<Partial<EntityValues<M>>>({});
|
|
255
284
|
|
|
256
285
|
const [customIdLoading, setCustomIdLoading] = useState<boolean>(false);
|
|
257
286
|
|
|
@@ -332,7 +361,15 @@ export function EntityForm<M extends Record<string, any>>({
|
|
|
332
361
|
return [initialValues, initialDirty];
|
|
333
362
|
}, [autoApplyLocalChanges, localChangesDataRaw, baseInitialValues, initialDirtyValues]);
|
|
334
363
|
|
|
335
|
-
const hasLocalChanges =
|
|
364
|
+
const hasLocalChanges = useMemo(() => {
|
|
365
|
+
if (localChangesCleared || !localChangesDataRaw || Object.keys(localChangesDataRaw).length === 0) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
// Compare cached values against entity values to check for real differences
|
|
369
|
+
const entityValues = entity?.values ?? {};
|
|
370
|
+
const realChanges = getChanges(localChangesDataRaw as Partial<M>, entityValues as Partial<M>);
|
|
371
|
+
return Object.keys(realChanges).length > 0;
|
|
372
|
+
}, [localChangesCleared, localChangesDataRaw, entity?.values]);
|
|
336
373
|
|
|
337
374
|
const formex: FormexController<M> = formexProp ?? useCreateFormex<M>({
|
|
338
375
|
initialValues: initialValues as M,
|
|
@@ -352,8 +389,10 @@ export function EntityForm<M extends Record<string, any>>({
|
|
|
352
389
|
onValuesChangeDeferred: (values: M, controller: FormexController<M>) => {
|
|
353
390
|
const key = (status === "new" || status === "copy") ? path + "#new" : path + "/" + entityId;
|
|
354
391
|
if (controller.dirty) {
|
|
355
|
-
const touchedValues = extractTouchedValues(values, controller.touched);
|
|
356
|
-
|
|
392
|
+
const touchedValues = removeEmptyContainers(extractTouchedValues(values, controller.touched));
|
|
393
|
+
if (touchedValues && Object.keys(touchedValues).length > 0) {
|
|
394
|
+
saveEntityToCache(key, touchedValues);
|
|
395
|
+
}
|
|
357
396
|
}
|
|
358
397
|
},
|
|
359
398
|
validation: (values) => {
|
|
@@ -666,6 +705,27 @@ export function EntityForm<M extends Record<string, any>>({
|
|
|
666
705
|
|
|
667
706
|
useOnAutoSave(autoSave, formex, lastSavedValues, save);
|
|
668
707
|
|
|
708
|
+
// Detect external changes to the entity (e.g. from onSnapshot after Admin SDK writes)
|
|
709
|
+
const prevEntityValuesRef = useRef<EntityValues<M> | undefined>(entity?.values);
|
|
710
|
+
useEffect(() => {
|
|
711
|
+
if (!entity?.values || status !== "existing") return;
|
|
712
|
+
const prev = prevEntityValuesRef.current;
|
|
713
|
+
prevEntityValuesRef.current = entity.values;
|
|
714
|
+
if (prev && !equal(prev, entity.values)) {
|
|
715
|
+
// Compute the diff between the old and new entity values
|
|
716
|
+
const changes: Partial<EntityValues<M>> = {};
|
|
717
|
+
const allKeys = new Set([...Object.keys(prev), ...Object.keys(entity.values)]);
|
|
718
|
+
for (const key of allKeys) {
|
|
719
|
+
if (!equal((prev as any)[key], (entity.values as any)[key])) {
|
|
720
|
+
(changes as any)[key] = (entity.values as any)[key];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (Object.keys(changes).length > 0) {
|
|
724
|
+
setUnderlyingChanges(changes);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}, [entity?.values, status]);
|
|
728
|
+
|
|
669
729
|
useEffect(() => {
|
|
670
730
|
if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
|
|
671
731
|
// we update the form fields from the Firestore data
|
|
@@ -20,7 +20,7 @@ import { isHidden, isPropertyBuilder, isReadOnly, resolveProperty } from "../uti
|
|
|
20
20
|
import { useAuthController, useCustomizationController, useTranslation } from "../hooks";
|
|
21
21
|
import { Typography } from "@firecms/ui";
|
|
22
22
|
import { getFieldConfig, getFieldId } from "../core";
|
|
23
|
-
import { ErrorBoundary } from "../components";
|
|
23
|
+
import { ErrorBoundary, CircularProgressCenter } from "../components";
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* This component renders a form field creating the corresponding configuration
|
|
@@ -287,8 +287,9 @@ function FieldInternal<T extends CMSType, CustomProps, M extends Record<string,
|
|
|
287
287
|
|
|
288
288
|
return (
|
|
289
289
|
<ErrorBoundary>
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
<React.Suspense fallback={<CircularProgressCenter />}>
|
|
291
|
+
<UsedComponent {...cmsFieldProps} />
|
|
292
|
+
</React.Suspense>
|
|
292
293
|
|
|
293
294
|
{underlyingValueHasChanged && !isSubmitting &&
|
|
294
295
|
<Typography variant={"caption"} className={"ml-3.5"}>
|
|
@@ -2,7 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { FieldProps } from "../../types";
|
|
3
3
|
import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
|
|
4
4
|
import { PropertyFieldBinding } from "../PropertyFieldBinding";
|
|
5
|
-
import { ExpandablePanel,
|
|
5
|
+
import { ExpandablePanel, IconButton, CloseIcon } from "@firecms/ui";
|
|
6
6
|
import { getArrayResolvedProperties, getIconForProperty, isReadOnly } from "../../util";
|
|
7
7
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
8
8
|
import { useAuthController } from "../../hooks";
|
|
@@ -50,15 +50,28 @@ export function ArrayCustomShapedFieldBinding<T extends Array<any>>({
|
|
|
50
50
|
setValue
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
const title = (
|
|
53
|
+
const title = (<div className="flex items-center w-full">
|
|
54
54
|
<LabelWithIconAndTooltip
|
|
55
55
|
propertyKey={propertyKey}
|
|
56
56
|
icon={getIconForProperty(property, "small")}
|
|
57
57
|
required={property.validation?.required}
|
|
58
58
|
title={property.name}
|
|
59
|
-
className={"
|
|
60
|
-
{Array.isArray(value) && <
|
|
61
|
-
|
|
59
|
+
className={"text-text-secondary dark:text-text-secondary-dark"}/>
|
|
60
|
+
{Array.isArray(value) && <span className={"text-sm text-text-secondary dark:text-text-secondary-dark ml-1"}>({value.length})</span>}
|
|
61
|
+
<div className="flex-grow"/>
|
|
62
|
+
{(property.nullable || property.clearable) && !disabled && (
|
|
63
|
+
<IconButton
|
|
64
|
+
size="small"
|
|
65
|
+
onClick={(e) => {
|
|
66
|
+
e.stopPropagation();
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
setValue(null);
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<CloseIcon size={"small"}/>
|
|
72
|
+
</IconButton>
|
|
73
|
+
)}
|
|
74
|
+
</div>);
|
|
62
75
|
|
|
63
76
|
const body = resolvedProperties.map((childProperty, index) => {
|
|
64
77
|
const thisDisabled = isReadOnly(childProperty) || Boolean(childProperty.disabled);
|
|
@@ -5,7 +5,7 @@ import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
|
|
|
5
5
|
import { ArrayContainer, ArrayEntryParams, ErrorView } from "../../components";
|
|
6
6
|
import { getIconForProperty, getReferenceFrom } from "../../util";
|
|
7
7
|
import { useNavigationController, useReferenceDialog, useTranslation } from "../../hooks";
|
|
8
|
-
import { Button, cls, EditIcon, ExpandablePanel, fieldBackgroundMixin, Typography } from "@firecms/ui";
|
|
8
|
+
import { Button, cls, EditIcon, ExpandablePanel, fieldBackgroundMixin, Typography, IconButton, CloseIcon } from "@firecms/ui";
|
|
9
9
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
10
10
|
|
|
11
11
|
type ArrayOfReferencesFieldProps = FieldProps<EntityReference[]>;
|
|
@@ -100,15 +100,28 @@ export function ArrayOfReferencesFieldBinding({
|
|
|
100
100
|
);
|
|
101
101
|
}, [ofProperty.path, ofProperty.previewProperties, value]);
|
|
102
102
|
|
|
103
|
-
const title = (
|
|
103
|
+
const title = (<div className="flex items-center w-full">
|
|
104
104
|
<LabelWithIconAndTooltip
|
|
105
105
|
propertyKey={propertyKey}
|
|
106
106
|
icon={getIconForProperty(property, "small")}
|
|
107
107
|
required={property.validation?.required}
|
|
108
108
|
title={property.name}
|
|
109
|
-
className={"
|
|
110
|
-
{Array.isArray(value) && <
|
|
111
|
-
|
|
109
|
+
className={"text-text-secondary dark:text-text-secondary-dark"}/>
|
|
110
|
+
{Array.isArray(value) && <span className={"text-sm text-text-secondary dark:text-text-secondary-dark ml-1"}>({value.length})</span>}
|
|
111
|
+
<div className="flex-grow"/>
|
|
112
|
+
{(property.nullable || property.clearable) && !disabled && (
|
|
113
|
+
<IconButton
|
|
114
|
+
size="small"
|
|
115
|
+
onClick={(e) => {
|
|
116
|
+
e.stopPropagation();
|
|
117
|
+
e.preventDefault();
|
|
118
|
+
setValue(null);
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<CloseIcon size={"small"}/>
|
|
122
|
+
</IconButton>
|
|
123
|
+
)}
|
|
124
|
+
</div>);
|
|
112
125
|
|
|
113
126
|
const body = <>
|
|
114
127
|
{!collection && <ErrorView
|
|
@@ -8,7 +8,7 @@ import { EnumValuesChip } from "../../preview";
|
|
|
8
8
|
import { FieldProps, FormContext, PropertyFieldBindingProps, PropertyOrBuilder } from "../../types";
|
|
9
9
|
import { getDefaultValueFor, getIconForProperty, mergeDeep, } from "../../util";
|
|
10
10
|
import { DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE } from "../../util/common";
|
|
11
|
-
import { cls, ExpandablePanel, paperMixin, Select, SelectItem, Typography } from "@firecms/ui";
|
|
11
|
+
import { cls, ExpandablePanel, paperMixin, Select, SelectItem, Typography, IconButton, CloseIcon } from "@firecms/ui";
|
|
12
12
|
import { useClearRestoreValue } from "../useClearRestoreValue";
|
|
13
13
|
import { ArrayContainer, ArrayEntryParams } from "../../components";
|
|
14
14
|
import { useTranslation } from "../../hooks/useTranslation";
|
|
@@ -74,12 +74,26 @@ export function BlockFieldBinding<T extends Array<any>>({
|
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
const title = (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
77
|
+
<div className="flex items-center w-full">
|
|
78
|
+
<LabelWithIconAndTooltip
|
|
79
|
+
propertyKey={propertyKey}
|
|
80
|
+
icon={getIconForProperty(property, "small")}
|
|
81
|
+
required={property.validation?.required}
|
|
82
|
+
title={property.name}
|
|
83
|
+
className={"text-text-secondary dark:text-text-secondary-dark flex-grow"}/>
|
|
84
|
+
{(property.nullable || property.clearable) && !disabled && (
|
|
85
|
+
<IconButton
|
|
86
|
+
size="small"
|
|
87
|
+
onClick={(e) => {
|
|
88
|
+
e.stopPropagation();
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
setValue(null);
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<CloseIcon size={"small"}/>
|
|
94
|
+
</IconButton>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
83
97
|
);
|
|
84
98
|
|
|
85
99
|
const firstOneOfKey = Object.keys(property.oneOf.properties)[0];
|
|
@@ -48,7 +48,7 @@ export function DateTimeFieldBinding({
|
|
|
48
48
|
onChange={(dateValue) => setValue(dateValue)}
|
|
49
49
|
size={"large"}
|
|
50
50
|
mode={property.mode}
|
|
51
|
-
clearable={property.clearable}
|
|
51
|
+
clearable={property.nullable || property.clearable}
|
|
52
52
|
locale={locale}
|
|
53
53
|
timezone={property.timezone}
|
|
54
54
|
error={showError}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
defaultBorderMixin,
|
|
14
14
|
ExpandablePanel,
|
|
15
15
|
IconButton,
|
|
16
|
+
CloseIcon,
|
|
16
17
|
Menu,
|
|
17
18
|
MenuItem,
|
|
18
19
|
RemoveIcon,
|
|
@@ -64,12 +65,28 @@ export function KeyValueFieldBinding({
|
|
|
64
65
|
initialValue={initialValues}
|
|
65
66
|
fieldName={property.name ?? propertyKey}/>;
|
|
66
67
|
|
|
67
|
-
const title =
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
const title = (
|
|
69
|
+
<div className="flex items-center w-full">
|
|
70
|
+
<LabelWithIconAndTooltip
|
|
71
|
+
propertyKey={propertyKey}
|
|
72
|
+
icon={getIconForProperty(property, "small")}
|
|
73
|
+
required={property.validation?.required}
|
|
74
|
+
title={property.name}
|
|
75
|
+
className={"text-text-secondary dark:text-text-secondary-dark flex-grow"}/>
|
|
76
|
+
{(property.nullable || property.clearable) && !disabled && (
|
|
77
|
+
<IconButton
|
|
78
|
+
size="small"
|
|
79
|
+
onClick={(e) => {
|
|
80
|
+
e.stopPropagation();
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
setValue(null);
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<CloseIcon size={"small"}/>
|
|
86
|
+
</IconButton>
|
|
87
|
+
)}
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
73
90
|
|
|
74
91
|
return (
|
|
75
92
|
<>
|
|
@@ -6,7 +6,7 @@ import { getIconForProperty, isHidden, isReadOnly, pick } from "../../util";
|
|
|
6
6
|
import { FieldHelperText, LabelWithIconAndTooltip } from "../components";
|
|
7
7
|
import { FormEntry } from "../components/FormEntry";
|
|
8
8
|
import { PropertyFieldBinding } from "../PropertyFieldBinding";
|
|
9
|
-
import { cls, ExpandablePanel, InputLabel, Select, SelectItem } from "@firecms/ui";
|
|
9
|
+
import { cls, ExpandablePanel, InputLabel, Select, SelectItem, IconButton, CloseIcon } from "@firecms/ui";
|
|
10
10
|
import { useTranslation } from "../../hooks";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -28,7 +28,8 @@ export function MapFieldBinding({
|
|
|
28
28
|
includeDescription,
|
|
29
29
|
autoFocus,
|
|
30
30
|
context,
|
|
31
|
-
onPropertyChange
|
|
31
|
+
onPropertyChange,
|
|
32
|
+
setValue
|
|
32
33
|
}: FieldProps<Record<string, any>>) {
|
|
33
34
|
|
|
34
35
|
const pickOnlySomeKeys = property.pickOnlySomeKeys || false;
|
|
@@ -108,12 +109,26 @@ export function MapFieldBinding({
|
|
|
108
109
|
}}
|
|
109
110
|
className={property.widthPercentage !== undefined ? "mt-8" : undefined}
|
|
110
111
|
innerClassName={"px-2 md:px-4 pb-2 md:pb-4 pt-1 md:pt-2 bg-white dark:bg-surface-900"}
|
|
111
|
-
title={<
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
title={<div className="flex items-center w-full">
|
|
113
|
+
<LabelWithIconAndTooltip
|
|
114
|
+
propertyKey={propertyKey}
|
|
115
|
+
icon={getIconForProperty(property, "small")}
|
|
116
|
+
required={property.validation?.required}
|
|
117
|
+
title={property.name}
|
|
118
|
+
className={"text-text-secondary dark:text-text-secondary-dark flex-grow"} />
|
|
119
|
+
{(property.nullable || property.clearable) && !disabled && (
|
|
120
|
+
<IconButton
|
|
121
|
+
size="small"
|
|
122
|
+
onClick={(e) => {
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
e.preventDefault();
|
|
125
|
+
setValue(null);
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
<CloseIcon size={"small"}/>
|
|
129
|
+
</IconButton>
|
|
130
|
+
)}
|
|
131
|
+
</div>}>
|
|
117
132
|
{mapFormView}
|
|
118
133
|
</ExpandablePanel>}
|
|
119
134
|
|