@rebasepro/cms 0.22.0 → 0.23.0
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/{CollectionEditorDialog-CUtfS_49.js → CollectionEditorDialog-DLHCka00.js} +103 -58
- package/dist/CollectionEditorDialog-DLHCka00.js.map +1 -0
- package/dist/{PropertyEditView-BpEbC0Q2.js → PropertyEditView-_pu5hzZn.js} +53 -21
- package/dist/PropertyEditView-_pu5hzZn.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-EK8Knq5I.js → RouterCollectionsStudioView-ChPNl_uU.js} +34 -10
- package/dist/RouterCollectionsStudioView-ChPNl_uU.js.map +1 -0
- package/dist/collection_editor/_cms_internals.d.ts +1 -0
- package/dist/collection_editor/relation_kinds.d.ts +11 -0
- package/dist/collection_editor/ui/collection_editor/CollectionStudioView.d.ts +8 -1
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/CollectionTableBinding/fields/CompactEntityCellField.d.ts +12 -13
- package/dist/components/CollectionTableBinding/fields/TableMultipleRelationField.d.ts +6 -3
- package/dist/components/CollectionTableBinding/fields/TableReferenceField.d.ts +8 -1
- package/dist/components/CollectionTableBinding/fields/TableRelationField.d.ts +8 -1
- package/dist/components/CollectionTableBinding/fields/TableRelationSelectorField.d.ts +13 -3
- package/dist/components/CollectionTableBinding/fields/VirtualTableSelect.d.ts +10 -0
- package/dist/components/CollectionTableBinding/fields/VirtualTableUserSelect.d.ts +10 -0
- package/dist/components/CollectionTableBinding/internal/EntityTableCell.d.ts +21 -0
- package/dist/components/CollectionTableBinding/internal/TableCellOpener.d.ts +45 -0
- package/dist/components/CollectionTableBinding/table_bindings.d.ts +24 -0
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +7 -1
- package/dist/components/EntityPreviewBinding.d.ts +8 -1
- package/dist/components/EntityPreviewNesting.d.ts +15 -0
- package/dist/components/PickerTrigger.d.ts +17 -0
- package/dist/components/ReferenceTable/SelectionTableBinding.d.ts +12 -0
- package/dist/components/RelationSelector.d.ts +24 -7
- package/dist/components/UserSelector.d.ts +8 -1
- package/dist/data_export/export/additional_fields.d.ts +13 -0
- package/dist/data_export/export/export.d.ts +6 -0
- package/dist/data_import/utils/csv.d.ts +3 -2
- package/dist/data_import/utils/id_column.d.ts +16 -0
- package/dist/editor.js +1 -1
- package/dist/{export-LvIpE7sE.js → export-4bJWdlDK.js} +63 -209
- package/dist/export-4bJWdlDK.js.map +1 -0
- package/dist/form/form_utils.d.ts +13 -0
- package/dist/form/validation.d.ts +13 -0
- package/dist/{history-DG6Ln7CV.js → history-BElrZ5Rw.js} +2 -2
- package/dist/{history-DG6Ln7CV.js.map → history-BElrZ5Rw.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +4 -5
- package/dist/{import-Cxnmpyjl.js → import-TCgkTVlS.js} +3 -4
- package/dist/import-TCgkTVlS.js.map +1 -0
- package/dist/index.js +10 -10
- package/dist/index.js.map +1 -1
- package/dist/preview/util.d.ts +25 -0
- package/dist/util/navigation_utils.d.ts +14 -2
- package/dist/util/view_mode.d.ts +12 -18
- package/dist/{util-DB31IoJE.js → util-CC0ozHoD.js} +35 -2
- package/dist/util-CC0ozHoD.js.map +1 -0
- package/dist/{util-CyIcgnv2.js → util-DybQBkug.js} +1363 -636
- package/dist/util-DybQBkug.js.map +1 -0
- package/package.json +9 -9
- package/dist/CollectionEditorDialog-CUtfS_49.js.map +0 -1
- package/dist/PropertyEditView-BpEbC0Q2.js.map +0 -1
- package/dist/RouterCollectionsStudioView-EK8Knq5I.js.map +0 -1
- package/dist/export-LvIpE7sE.js.map +0 -1
- package/dist/import-Cxnmpyjl.js.map +0 -1
- package/dist/util-CyIcgnv2.js.map +0 -1
- package/dist/util-DB31IoJE.js.map +0 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as isEmptyPreviewValue, i as getThumbnailMeasure, n as getDatePropertyTimezone, o as sanitizeUrl, r as getPreviewSizeFrom, t as getDatePropertyMode } from "./util-CC0ozHoD.js";
|
|
2
2
|
import * as React$1 from "react";
|
|
3
3
|
import React, { Suspense, createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
4
|
import { deepEqual, shallowEqual } from "fast-equals";
|
|
5
5
|
import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowDownIcon, ArrowLeftIcon, ArrowRightToLineIcon, ArrowUpDownIcon, ArrowUpIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, CHIP_SEED_KEYS, CalendarIcon, Card, CardView, CenteredView, CheckCircleIcon, CheckIcon, Checkbox, ChevronDownIcon, ChevronRightIcon, ChevronUpIcon, ChevronsUpDownIcon, Chip, CircleIcon, CircularProgress, CircularProgressCenter, CodeIcon, Collapse, ColumnsIcon, CopyIcon, DateTimeField, Dialog, DialogActions, DialogContent, DialogTitle, ErrorBoundary, ExpandablePanel, ExternalLinkIcon, EyeIcon, EyeOffIcon, FileTextIcon, FileUpload, FilterChip, FilterIcon, FilterXIcon, FlagIcon, FolderUpIcon, FunctionSquareIcon, GlobeIcon, HandleIcon, HashIcon, HistoryIcon, IconButton, ImageOffIcon, KanbanIcon, KanbanView, KeyRoundIcon, Label, LayoutGridIcon, Link2Icon, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoadingButton, LucideIconByName, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, PanelLeftCloseIcon, PanelLeftOpenIcon, PencilIcon, PlusIcon, Popover, PopoverPrimitive, Portal, QuoteIcon, RadioGroup, RadioGroupItem, RefreshCwIcon, RepeatIcon, ResizablePanels, Rows3Icon, SearchBar, SearchIcon, Select, SelectItem, Separator, SettingsIcon, Sheet, Skeleton, Tab, Table, TableBody, TableCell, TableHeader, TableIcon, TableRow, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, Unlink2Icon, UploadIcon, UserIcon, VirtualTable, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lazyChunk, paperMixin, useDebouncedCallback, useOutsideAlerter, usePortalContainer } from "@rebasepro/ui";
|
|
6
6
|
import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityChildViews, getLabelOrConfigFrom, getReferenceFrom, getRelationFrom, getRelationTargetPath, getSubcollections, getTableName, isPropertyBuilder, normalizeOrderBy, normalizeToEntityRelation, resolveArrayProperties, resolveEnumValues, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, serializeOrderBy } from "@rebasepro/common";
|
|
7
|
-
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE,
|
|
7
|
+
import { CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, IconForView, PluginProviderStack, SIDE_PANEL_DEFAULT_WIDTH, StorageSourceContext, UnsavedChangesDialog, UserDisplay, addInitialSlash, apiBaseOf, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, entityDisplayKey, flattenKeys, getAdminEntityChildViews, getCollectionBySlugWithin, getCollectionPathsCombinations, getColorScheme, getColumnKeysForProperty, getDisplayPropertyKey, getDisplayResolver, getEntityFromCache, getEntityFromMemoryCache, getEntityImagePreviewPropertyKey, getEntityPreviewKeys, getFormFieldKeys, getIcon, getLastSegment as getLastSegment$1, getLeadingRelationTitleKey, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getPropertyInPath, getRedundantChildViewColumnIds, getRelationIncludeParams, getRowHeight, getSharedEntityDisplayCache, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, hasDeclaredDisplay, iconsSearch, isDisabled, isFilterableRelation, isHidden, isReadOnly, looksLikeIdentifierValue, removeEntityFromCache, removeEntityFromMemoryCache, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveComponentRef, resolveDefaultSelectedView, resolveFilterOperators, resolveFormLayout, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiBase, useApiConfig, useAuthController, useBridgeRegistration, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
|
|
8
8
|
import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
import { EntityReference, EntityRelation, GeoPoint, MAX_LIST_LIMIT, Vector, getCollectionDataPath, getDataSourceCapabilities, isRelationalCollectionConfig, resourceKeyOf } from "@rebasepro/types";
|
|
11
|
-
import { defaultDateFormat, formatRelativeTime, getHashValue, getValueInPath, hydrateRegExp, isArrayValue,
|
|
11
|
+
import { defaultDateFormat, formatRelativeTime, getArrayValuesCount, getHashValue, getValueInPath, hydrateRegExp, isArrayValue, isPlainObject, isPrototypePollutingKey, mergeDeep, pathTraversesPrototype, prettifyIdentifier, randomString, readStoredJson, toArray, writeStoredJson } from "@rebasepro/utils";
|
|
12
12
|
import { resolveAdminCollection } from "@rebasepro/cms-types";
|
|
13
13
|
import { format } from "date-fns";
|
|
14
14
|
import { Command } from "cmdk";
|
|
@@ -1353,25 +1353,14 @@ function getUserLabel(user) {
|
|
|
1353
1353
|
//#endregion
|
|
1354
1354
|
//#region src/hooks/useEntityDisplay.tsx
|
|
1355
1355
|
/**
|
|
1356
|
-
* One shared store, so every surface agrees on a record and resolves it once.
|
|
1357
|
-
*
|
|
1358
|
-
* Module-level rather than a provider: two app instances on one page share a
|
|
1359
|
-
* cache keyed by collection path and record id, which *is* the same record.
|
|
1360
|
-
* Nothing here is user-specific — a resolver that reads per-user data must
|
|
1361
|
-
* invalidate on sign-out, which {@link useEntityDisplayCache} exposes.
|
|
1362
|
-
*/
|
|
1363
|
-
var sharedCache;
|
|
1364
|
-
/**
|
|
1365
1356
|
* The store behind {@link useEntityDisplay}, for invalidating after a write.
|
|
1366
1357
|
*
|
|
1367
|
-
*
|
|
1368
|
-
*
|
|
1369
|
-
*
|
|
1370
|
-
* which several do, and which is a legitimate thing for them to do.
|
|
1358
|
+
* The app's one shared store, so every surface agrees on a record and resolves
|
|
1359
|
+
* it once, and so it is emptied with the rest of the session's caches when the
|
|
1360
|
+
* user signs out.
|
|
1371
1361
|
*/
|
|
1372
1362
|
function useEntityDisplayCache() {
|
|
1373
|
-
|
|
1374
|
-
return sharedCache;
|
|
1363
|
+
return getSharedEntityDisplayCache();
|
|
1375
1364
|
}
|
|
1376
1365
|
/**
|
|
1377
1366
|
* What fills one display role of one record.
|
|
@@ -1936,13 +1925,24 @@ function NestedEntityPreviewBoundary({ children }) {
|
|
|
1936
1925
|
children
|
|
1937
1926
|
});
|
|
1938
1927
|
}
|
|
1928
|
+
var EntityPreviewLayoutContext = React$1.createContext("card");
|
|
1929
|
+
function useEntityPreviewLayout() {
|
|
1930
|
+
return React$1.useContext(EntityPreviewLayoutContext);
|
|
1931
|
+
}
|
|
1932
|
+
/** Lays out the previews below it as rows. See {@link EntityPreviewLayout}. */
|
|
1933
|
+
function RowEntityPreviewLayout({ children }) {
|
|
1934
|
+
return /* @__PURE__ */ jsx(EntityPreviewLayoutContext.Provider, {
|
|
1935
|
+
value: "row",
|
|
1936
|
+
children
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
1939
|
//#endregion
|
|
1940
1940
|
//#region src/components/EntityPreviewBinding.tsx
|
|
1941
1941
|
/**
|
|
1942
1942
|
* This component contains the main logic and content for displaying a entity preview,
|
|
1943
1943
|
* without any container wrapper. Used internally by EntityPreviewBinding.
|
|
1944
1944
|
*/
|
|
1945
|
-
function EntityPreviewBindingData({ actions, collection: collectionProp, previewKeys, size = "medium", includeId = true, onSidePanelClick, includeTitle = true, includeEntityLink = true, includeImage = true, entity }) {
|
|
1945
|
+
function EntityPreviewBindingData({ actions, collection: collectionProp, previewKeys, size = "medium", includeId = true, onSidePanelClick, includeTitle = true, includeEntityLink = true, includeImage = true, layout = "card", entity }) {
|
|
1946
1946
|
const authController = useAuthController();
|
|
1947
1947
|
const analyticsController = useAnalyticsController();
|
|
1948
1948
|
const sidePanelController = useSidePanel();
|
|
@@ -1972,8 +1972,32 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
1972
1972
|
const ofProp = imageProperty && "of" in imageProperty ? imageProperty.of : void 0;
|
|
1973
1973
|
const usedImageProperty = ofProp ? Array.isArray(ofProp) ? ofProp[0] : ofProp : imageProperty;
|
|
1974
1974
|
const restProperties = listProperties.filter((p) => p !== titleProperty && p !== imagePropertyKey);
|
|
1975
|
+
const openInSidePanel = () => {
|
|
1976
|
+
onSidePanelClick?.(entity);
|
|
1977
|
+
analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
|
|
1978
|
+
path: entity.path,
|
|
1979
|
+
entityId: entity.id
|
|
1980
|
+
});
|
|
1981
|
+
sidePanelController.open({
|
|
1982
|
+
entityId: entity.id,
|
|
1983
|
+
path: entity.path,
|
|
1984
|
+
collection,
|
|
1985
|
+
updateUrl: true
|
|
1986
|
+
});
|
|
1987
|
+
};
|
|
1975
1988
|
const imageValue = imagePropertyKey ? getValueInPath(entity.values, imagePropertyKey) : void 0;
|
|
1976
1989
|
const usedImageValue = imageProperty !== void 0 ? "of" in imageProperty ? (imageValue ?? []).length > 0 ? imageValue[0] : void 0 : imageValue : void 0;
|
|
1990
|
+
if (layout === "row") return /* @__PURE__ */ jsx(EntityRowPreviewContent, {
|
|
1991
|
+
size,
|
|
1992
|
+
entity,
|
|
1993
|
+
collection,
|
|
1994
|
+
title: title.value ?? String(entity.id),
|
|
1995
|
+
imageProperty: usedImageProperty,
|
|
1996
|
+
imagePropertyKey,
|
|
1997
|
+
imageValue: usedImageValue,
|
|
1998
|
+
supportingKeys: restProperties,
|
|
1999
|
+
onOpen: includeEntityLink ? openInSidePanel : void 0
|
|
2000
|
+
});
|
|
1977
2001
|
return /* @__PURE__ */ jsxs(NestedEntityPreviewBoundary, { children: [
|
|
1978
2002
|
/* @__PURE__ */ jsxs("div", {
|
|
1979
2003
|
className: cls("flex shrink-0 overflow-hidden", {
|
|
@@ -2048,17 +2072,7 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
2048
2072
|
onClick: (e) => {
|
|
2049
2073
|
e.stopPropagation();
|
|
2050
2074
|
e.preventDefault();
|
|
2051
|
-
|
|
2052
|
-
analyticsController.onAnalyticsEvent?.("entity_click_from_reference", {
|
|
2053
|
-
path: entity.path,
|
|
2054
|
-
entityId: entity.id
|
|
2055
|
-
});
|
|
2056
|
-
sidePanelController.open({
|
|
2057
|
-
entityId: entity.id,
|
|
2058
|
-
path: entity.path,
|
|
2059
|
-
collection,
|
|
2060
|
-
updateUrl: true
|
|
2061
|
-
});
|
|
2075
|
+
openInSidePanel();
|
|
2062
2076
|
},
|
|
2063
2077
|
children: /* @__PURE__ */ jsx(ArrowRightToLineIcon, {})
|
|
2064
2078
|
})
|
|
@@ -2071,6 +2085,75 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
|
|
|
2071
2085
|
] });
|
|
2072
2086
|
}
|
|
2073
2087
|
/**
|
|
2088
|
+
* A record as a line of a table row: its image, a title that opens it, and the
|
|
2089
|
+
* supporting lines that fit — one at `medium`, two at `large`. Supporting
|
|
2090
|
+
* values that are empty are skipped rather than drawn as an empty bar, so the
|
|
2091
|
+
* line under a name is the first thing the record actually has to say.
|
|
2092
|
+
*/
|
|
2093
|
+
function EntityRowPreviewContent({ size, entity, collection, title, imageProperty, imagePropertyKey, imageValue, supportingKeys, onOpen }) {
|
|
2094
|
+
const maxLines = size === "large" ? 2 : 1;
|
|
2095
|
+
const lines = supportingKeys.map((key) => ({
|
|
2096
|
+
key,
|
|
2097
|
+
property: getPropertyInPath$1(collection.properties, key),
|
|
2098
|
+
value: getValueInPath(entity.values, key)
|
|
2099
|
+
})).filter((line) => Boolean(line.property) && !isEmptyPreviewValue(line.value)).slice(0, maxLines);
|
|
2100
|
+
const titleNode = /* @__PURE__ */ jsx("span", {
|
|
2101
|
+
className: "truncate",
|
|
2102
|
+
children: title
|
|
2103
|
+
});
|
|
2104
|
+
return /* @__PURE__ */ jsx(NestedEntityPreviewBoundary, { children: /* @__PURE__ */ jsxs("div", {
|
|
2105
|
+
className: "flex items-center gap-2.5 min-w-0 w-full text-left",
|
|
2106
|
+
children: [imagePropertyKey && /* @__PURE__ */ jsx("div", {
|
|
2107
|
+
className: cls("flex shrink-0 items-center justify-center overflow-hidden rounded-md", size === "large" ? "w-10 h-10" : "w-8 h-8"),
|
|
2108
|
+
children: imageProperty && imageValue ? /* @__PURE__ */ jsx(PropertyPreview, {
|
|
2109
|
+
property: imageProperty,
|
|
2110
|
+
propertyKey: imagePropertyKey,
|
|
2111
|
+
size: "small",
|
|
2112
|
+
value: imageValue
|
|
2113
|
+
}) : /* @__PURE__ */ jsx(IconForView, {
|
|
2114
|
+
collectionOrView: collection,
|
|
2115
|
+
color: "primary",
|
|
2116
|
+
size: "small"
|
|
2117
|
+
})
|
|
2118
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
2119
|
+
className: "flex flex-col min-w-0 flex-1",
|
|
2120
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2121
|
+
className: "flex min-w-0 text-sm font-medium text-text-primary dark:text-text-primary-dark",
|
|
2122
|
+
children: onOpen ? /* @__PURE__ */ jsx(Tooltip, {
|
|
2123
|
+
title: `See details for ${title}`,
|
|
2124
|
+
asChild: true,
|
|
2125
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
2126
|
+
role: "button",
|
|
2127
|
+
tabIndex: 0,
|
|
2128
|
+
className: "inline-flex min-w-0 max-w-full cursor-pointer hover:underline underline-offset-2",
|
|
2129
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
2130
|
+
onClick: (event) => {
|
|
2131
|
+
event.preventDefault();
|
|
2132
|
+
event.stopPropagation();
|
|
2133
|
+
onOpen();
|
|
2134
|
+
},
|
|
2135
|
+
onKeyDown: (event) => {
|
|
2136
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
2137
|
+
event.preventDefault();
|
|
2138
|
+
event.stopPropagation();
|
|
2139
|
+
onOpen();
|
|
2140
|
+
},
|
|
2141
|
+
children: titleNode
|
|
2142
|
+
})
|
|
2143
|
+
}) : titleNode
|
|
2144
|
+
}), lines.map(({ key, property, value }) => /* @__PURE__ */ jsx("div", {
|
|
2145
|
+
className: "truncate min-w-0 max-h-5 overflow-hidden text-xs text-text-secondary dark:text-text-secondary-dark",
|
|
2146
|
+
children: /* @__PURE__ */ jsx(PropertyPreview, {
|
|
2147
|
+
propertyKey: key,
|
|
2148
|
+
value,
|
|
2149
|
+
property,
|
|
2150
|
+
size: "small"
|
|
2151
|
+
})
|
|
2152
|
+
}, key))]
|
|
2153
|
+
})]
|
|
2154
|
+
}) });
|
|
2155
|
+
}
|
|
2156
|
+
/**
|
|
2074
2157
|
* This view is used to display a preview of a entity.
|
|
2075
2158
|
* It is used by default in reference fields and whenever a reference is displayed.
|
|
2076
2159
|
*/
|
|
@@ -2167,6 +2250,7 @@ function InlineEntityPreview({ entity, collection: collectionProp, label, disabl
|
|
|
2167
2250
|
const content = /* @__PURE__ */ jsxs("span", {
|
|
2168
2251
|
tabIndex: interactive ? 0 : void 0,
|
|
2169
2252
|
role: interactive ? "button" : void 0,
|
|
2253
|
+
onMouseDown: interactive ? (event) => event.preventDefault() : void 0,
|
|
2170
2254
|
onClick: interactive ? handleClick : void 0,
|
|
2171
2255
|
onKeyDown: interactive ? (event) => {
|
|
2172
2256
|
if (event.key === "Enter" || event.key === " ") handleClick(event);
|
|
@@ -2292,7 +2376,9 @@ function ReferencePreviewInternal(props) {
|
|
|
2292
2376
|
function ReferencePreviewExisting({ reference, collection, previewProperties, size, disabled, includeEntityLink, includeId, onClick, hover, textOnly }) {
|
|
2293
2377
|
const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
|
|
2294
2378
|
useCustomizationController();
|
|
2295
|
-
const
|
|
2379
|
+
const nested = useIsNestedEntityPreview();
|
|
2380
|
+
const layout = useEntityPreviewLayout();
|
|
2381
|
+
const inline = nested || Boolean(textOnly) || size === "small";
|
|
2296
2382
|
const { entity, dataLoading, dataLoadingError } = useFetch({
|
|
2297
2383
|
path: reference.path,
|
|
2298
2384
|
entityId: reference.id,
|
|
@@ -2346,6 +2432,14 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
|
|
|
2346
2432
|
onClick,
|
|
2347
2433
|
includeEntityLink: !textOnly && includeEntityLink !== false
|
|
2348
2434
|
});
|
|
2435
|
+
if (layout === "row" && ResolvedEntityPreview === EntityPreviewBinding) return /* @__PURE__ */ jsx(EntityPreviewBindingData, {
|
|
2436
|
+
layout: "row",
|
|
2437
|
+
size,
|
|
2438
|
+
entity: usedEntity,
|
|
2439
|
+
collection,
|
|
2440
|
+
previewKeys: previewProperties,
|
|
2441
|
+
includeEntityLink: !disabled && includeEntityLink !== false
|
|
2442
|
+
});
|
|
2349
2443
|
return /* @__PURE__ */ jsx(ResolvedEntityPreview, {
|
|
2350
2444
|
size,
|
|
2351
2445
|
previewKeys: previewProperties,
|
|
@@ -2394,7 +2488,7 @@ function ArrayOfReferencesPreview({ propertyKey, value, property, size, textOnly
|
|
|
2394
2488
|
if (property?.type !== "array" || !property.of || property.of.type !== "reference") throw Error("Picked wrong preview component ArrayOfReferencesPreview");
|
|
2395
2489
|
const childSize = size === "medium" ? "medium" : "small";
|
|
2396
2490
|
const ofProperty = property.of;
|
|
2397
|
-
if (nested || textOnly) return /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
2491
|
+
if (nested || textOnly || size === "small") return /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
2398
2492
|
items: value ?? [],
|
|
2399
2493
|
renderItem: (reference, index) => /* @__PURE__ */ jsx(ReferencePreview, {
|
|
2400
2494
|
disabled: !ofProperty.path,
|
|
@@ -2533,7 +2627,9 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
2533
2627
|
const passedEntity = relation.data;
|
|
2534
2628
|
const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
|
|
2535
2629
|
useCustomizationController();
|
|
2536
|
-
const
|
|
2630
|
+
const nested = useIsNestedEntityPreview();
|
|
2631
|
+
const layout = useEntityPreviewLayout();
|
|
2632
|
+
const inline = nested || Boolean(textOnly) || size === "small";
|
|
2537
2633
|
const { entity, dataLoading, dataLoadingError } = useFetch({
|
|
2538
2634
|
path: relation.path,
|
|
2539
2635
|
entityId: passedEntity ? void 0 : relation.id,
|
|
@@ -2587,6 +2683,14 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
|
|
|
2587
2683
|
onClick,
|
|
2588
2684
|
includeEntityLink: !textOnly && includeEntityLink !== false
|
|
2589
2685
|
});
|
|
2686
|
+
if (layout === "row" && ResolvedEntityPreview === EntityPreviewBinding) return /* @__PURE__ */ jsx(EntityPreviewBindingData, {
|
|
2687
|
+
layout: "row",
|
|
2688
|
+
size,
|
|
2689
|
+
entity: usedEntity,
|
|
2690
|
+
collection,
|
|
2691
|
+
previewKeys: previewProperties,
|
|
2692
|
+
includeEntityLink: !disabled && includeEntityLink !== false
|
|
2693
|
+
});
|
|
2590
2694
|
return /* @__PURE__ */ jsx(ResolvedEntityPreview, {
|
|
2591
2695
|
size,
|
|
2592
2696
|
previewKeys: previewProperties,
|
|
@@ -2610,7 +2714,7 @@ function ArrayOfRelationsPreview({ propertyKey, value, property, size, textOnly
|
|
|
2610
2714
|
if (property?.type !== "array" || !property.of || property.of.type !== "relation") throw Error("Picked wrong preview component ArrayOfRelationsPreview");
|
|
2611
2715
|
const ofProperty = property.of;
|
|
2612
2716
|
const relationTargetPath = getRelationTargetPath(ofProperty);
|
|
2613
|
-
if (nested || textOnly) return /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
2717
|
+
if (nested || textOnly || size === "small") return /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
2614
2718
|
items: (value ?? []).map((relation) => normalizeToEntityRelation(relation, "relation", relationTargetPath)).filter((relation) => Boolean(relation)),
|
|
2615
2719
|
renderItem: (relation, index) => /* @__PURE__ */ jsx(RelationPreview, {
|
|
2616
2720
|
disabled: !ofProperty.relation,
|
|
@@ -3053,10 +3157,11 @@ function DatePreview({ date, mode = "date_time", timezone }) {
|
|
|
3053
3157
|
options.minute = "2-digit";
|
|
3054
3158
|
}
|
|
3055
3159
|
const formattedDate = new Intl.DateTimeFormat(customizationController?.locale ?? "en-US", options).format(date);
|
|
3056
|
-
const
|
|
3160
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
3057
3161
|
timeZone: timezone,
|
|
3058
3162
|
timeZoneName: "short"
|
|
3059
|
-
}).formatToParts(date)
|
|
3163
|
+
}).formatToParts(date);
|
|
3164
|
+
const tzAbbrev = mode === "date_time" ? parts.find((p) => p.type === "timeZoneName")?.value ?? "" : "";
|
|
3060
3165
|
return /* @__PURE__ */ jsxs("span", {
|
|
3061
3166
|
className: "flex items-center gap-1 font-mono tabular-nums",
|
|
3062
3167
|
children: [formattedDate, tzAbbrev && /* @__PURE__ */ jsxs("span", {
|
|
@@ -3316,7 +3421,8 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
|
3316
3421
|
const date = asDate(value);
|
|
3317
3422
|
if (date) content = /* @__PURE__ */ jsx(DatePreview, {
|
|
3318
3423
|
date,
|
|
3319
|
-
mode: property
|
|
3424
|
+
mode: getDatePropertyMode(property),
|
|
3425
|
+
timezone: getDatePropertyTimezone(property)
|
|
3320
3426
|
});
|
|
3321
3427
|
else content = buildWrongValueType(propertyKey, property.type, value);
|
|
3322
3428
|
} else if (property.type === "reference") if (typeof property.path === "string") if (typeof value === "object" && value !== null && "isEntityReference" in value && value.isEntityReference()) content = /* @__PURE__ */ jsx(ReferencePreview, {
|
|
@@ -3344,7 +3450,7 @@ var PropertyPreview = React.memo(function PropertyPreview(props) {
|
|
|
3344
3450
|
relation: entityRelation,
|
|
3345
3451
|
textOnly: props.textOnly
|
|
3346
3452
|
}, `preview_rel_${propertyKey}_${index}`);
|
|
3347
|
-
content = nested || props.textOnly ? /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
3453
|
+
content = nested || props.textOnly || size === "small" ? /* @__PURE__ */ jsx(InlineEntityListPreview, {
|
|
3348
3454
|
items: relations,
|
|
3349
3455
|
renderItem: renderRelation
|
|
3350
3456
|
}) : /* @__PURE__ */ jsx("div", {
|
|
@@ -3562,16 +3668,16 @@ function ReadOnlyFieldBinding({ propertyKey, value, error, showError, minimalist
|
|
|
3562
3668
|
* @ignore
|
|
3563
3669
|
*/
|
|
3564
3670
|
function useClearRestoreValue({ property, value, setValue }) {
|
|
3565
|
-
const
|
|
3671
|
+
const clearedRef = useRef(null);
|
|
3566
3672
|
useEffect(() => {
|
|
3567
3673
|
if (typeof property.admin?.disabled === "object" && Boolean(property.admin?.disabled.clearOnDisabled)) {
|
|
3568
3674
|
if (value != null) {
|
|
3569
|
-
|
|
3675
|
+
clearedRef.current = { value };
|
|
3570
3676
|
setValue(null);
|
|
3571
3677
|
}
|
|
3572
|
-
} else if (
|
|
3573
|
-
setValue(
|
|
3574
|
-
|
|
3678
|
+
} else if (clearedRef.current) {
|
|
3679
|
+
setValue(clearedRef.current.value);
|
|
3680
|
+
clearedRef.current = null;
|
|
3575
3681
|
}
|
|
3576
3682
|
}, [property]);
|
|
3577
3683
|
}
|
|
@@ -3679,7 +3785,7 @@ function useCellSelected(store, propertyKey, entityPath, entityId) {
|
|
|
3679
3785
|
//#endregion
|
|
3680
3786
|
//#region src/components/CollectionTableBinding/fields/VirtualTableSelect.tsx
|
|
3681
3787
|
function VirtualTableSelect(props) {
|
|
3682
|
-
const { enumValues, internalValue, disabled, small, focused, updateValue, multiple, valueType } = props;
|
|
3788
|
+
const { enumValues, internalValue, disabled, small, focused, updateValue, multiple, valueType, open, onOpenChange, preview, anchorRef } = props;
|
|
3683
3789
|
const validValue = Array.isArray(internalValue) && multiple || !Array.isArray(internalValue) && !multiple;
|
|
3684
3790
|
const ref = React.useRef(null);
|
|
3685
3791
|
useEffect(() => {
|
|
@@ -3700,25 +3806,33 @@ function VirtualTableSelect(props) {
|
|
|
3700
3806
|
]);
|
|
3701
3807
|
const resolvedEnumValues = resolveEnumValues(enumValues);
|
|
3702
3808
|
const renderValue = (enumKey) => {
|
|
3809
|
+
if (preview !== void 0) return preview;
|
|
3703
3810
|
return /* @__PURE__ */ jsx(EnumValuesChip, {
|
|
3704
3811
|
enumKey: String(enumKey),
|
|
3705
3812
|
enumValues,
|
|
3706
3813
|
size: small ? "small" : "medium"
|
|
3707
3814
|
}, `${enumKey}`);
|
|
3708
3815
|
};
|
|
3709
|
-
const handleOpenChange = useCallback((
|
|
3710
|
-
|
|
3816
|
+
const handleOpenChange = useCallback((nextOpen) => {
|
|
3817
|
+
onOpenChange?.(nextOpen);
|
|
3818
|
+
if (!nextOpen && ref.current) setTimeout(() => {
|
|
3711
3819
|
ref.current?.focus({ preventScroll: true });
|
|
3712
3820
|
}, 0);
|
|
3713
|
-
}, []);
|
|
3821
|
+
}, [onOpenChange]);
|
|
3714
3822
|
return multiple ? /* @__PURE__ */ jsx(MultiSelect, {
|
|
3715
3823
|
invisible: true,
|
|
3716
3824
|
inputRef: ref,
|
|
3825
|
+
size: "smallest",
|
|
3717
3826
|
className: "w-full h-full p-0 bg-transparent outline-none",
|
|
3718
3827
|
position: "item-aligned",
|
|
3719
3828
|
disabled,
|
|
3720
3829
|
includeClear: false,
|
|
3830
|
+
chevron: preview === void 0,
|
|
3831
|
+
anchorRef,
|
|
3721
3832
|
useChips: false,
|
|
3833
|
+
open,
|
|
3834
|
+
renderValues: preview !== void 0 ? () => preview : void 0,
|
|
3835
|
+
placeholder: preview,
|
|
3722
3836
|
value: validValue ? internalValue.map((v) => v.toString()) : [],
|
|
3723
3837
|
onValueChange: onChange,
|
|
3724
3838
|
onOpenChange: handleOpenChange,
|
|
@@ -3739,7 +3853,11 @@ function VirtualTableSelect(props) {
|
|
|
3739
3853
|
position: "item-aligned",
|
|
3740
3854
|
disabled,
|
|
3741
3855
|
padding: false,
|
|
3856
|
+
size: "smallest",
|
|
3857
|
+
chevron: preview === void 0,
|
|
3858
|
+
open,
|
|
3742
3859
|
value: validValue ? internalValue?.toString() : "",
|
|
3860
|
+
placeholder: preview,
|
|
3743
3861
|
onValueChange: onChange,
|
|
3744
3862
|
onOpenChange: handleOpenChange,
|
|
3745
3863
|
renderValue,
|
|
@@ -3754,6 +3872,27 @@ function VirtualTableSelect(props) {
|
|
|
3754
3872
|
});
|
|
3755
3873
|
}
|
|
3756
3874
|
//#endregion
|
|
3875
|
+
//#region src/components/PickerTrigger.tsx
|
|
3876
|
+
/**
|
|
3877
|
+
* The trigger of a picker's popover, or — given an anchor — a plain child
|
|
3878
|
+
* beside a `Popover.Anchor` on that element.
|
|
3879
|
+
*
|
|
3880
|
+
* Both at once does not work: Radix's `Trigger` registers itself as the
|
|
3881
|
+
* anchor through a ref callback, and a custom anchor through an effect that
|
|
3882
|
+
* only reports a change. Under StrictMode the ref callback runs again after
|
|
3883
|
+
* the effect, the effect sees nothing new, and the popover stays anchored to
|
|
3884
|
+
* a trigger that the custom anchor then unmounts — it opened at the window's
|
|
3885
|
+
* top-left corner. The pickers open and close themselves, so they lose
|
|
3886
|
+
* nothing by leaving the `Trigger` out.
|
|
3887
|
+
*/
|
|
3888
|
+
function PickerTrigger({ anchorRef, children }) {
|
|
3889
|
+
if (!anchorRef) return /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, {
|
|
3890
|
+
asChild: true,
|
|
3891
|
+
children
|
|
3892
|
+
});
|
|
3893
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PopoverPrimitive.Anchor, { virtualRef: anchorRef }), children] });
|
|
3894
|
+
}
|
|
3895
|
+
//#endregion
|
|
3757
3896
|
//#region src/components/UserSelector.tsx
|
|
3758
3897
|
var DEFAULT_PAGE_SIZE$2 = 10;
|
|
3759
3898
|
/**
|
|
@@ -3849,9 +3988,30 @@ function useUserSelector({ pageSize = DEFAULT_PAGE_SIZE$2 } = {}) {
|
|
|
3849
3988
|
}, [])
|
|
3850
3989
|
};
|
|
3851
3990
|
}
|
|
3852
|
-
var UserSelector = React$1.forwardRef(({ value, size = "medium", onValueChange, invisible, disabled, placeholder, clearable = true, className, pageSize, searchPlaceholder = "Search users...", noResultsText = "No users found.", loadingText = "Loading..." }, ref) => {
|
|
3991
|
+
var UserSelector = React$1.forwardRef(({ value, size = "medium", onValueChange, invisible, disabled, placeholder, clearable = true, className, pageSize, searchPlaceholder = "Search users...", noResultsText = "No users found.", loadingText = "Loading...", open: openProp, onOpenChange, triggerContent, anchorRef }, ref) => {
|
|
3853
3992
|
const { items: availableItems, isLoading, hasMore, search, loadMore, getUser } = useUserSelector({ pageSize });
|
|
3854
|
-
const [isPopoverOpen,
|
|
3993
|
+
const [isPopoverOpen, setIsPopoverOpenState] = useState(false);
|
|
3994
|
+
const isPopoverOpenRef = useRef(false);
|
|
3995
|
+
const onOpenChangeRef = useRef(onOpenChange);
|
|
3996
|
+
onOpenChangeRef.current = onOpenChange;
|
|
3997
|
+
const setIsPopoverOpen = useCallback((next, notify = true) => {
|
|
3998
|
+
const changed = isPopoverOpenRef.current !== next;
|
|
3999
|
+
isPopoverOpenRef.current = next;
|
|
4000
|
+
setIsPopoverOpenState(next);
|
|
4001
|
+
if (changed && notify) onOpenChangeRef.current?.(next);
|
|
4002
|
+
}, []);
|
|
4003
|
+
useEffect(() => {
|
|
4004
|
+
if (openProp === void 0 || openProp === isPopoverOpenRef.current) return;
|
|
4005
|
+
if (openProp && disabled) {
|
|
4006
|
+
onOpenChangeRef.current?.(false);
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
setIsPopoverOpen(openProp, false);
|
|
4010
|
+
}, [
|
|
4011
|
+
openProp,
|
|
4012
|
+
disabled,
|
|
4013
|
+
setIsPopoverOpen
|
|
4014
|
+
]);
|
|
3855
4015
|
const [searchString, setSearchString] = useState("");
|
|
3856
4016
|
const contextPortalContainer = usePortalContainer();
|
|
3857
4017
|
const scrollContainerRef = useRef(null);
|
|
@@ -3917,16 +4077,24 @@ var UserSelector = React$1.forwardRef(({ value, size = "medium", onValueChange,
|
|
|
3917
4077
|
if (value === item.uid) onValueChange?.(null);
|
|
3918
4078
|
else onValueChange?.(item.uid);
|
|
3919
4079
|
setIsPopoverOpen(false);
|
|
3920
|
-
}, [
|
|
4080
|
+
}, [
|
|
4081
|
+
value,
|
|
4082
|
+
onValueChange,
|
|
4083
|
+
setIsPopoverOpen
|
|
4084
|
+
]);
|
|
3921
4085
|
const handleClear = useCallback((e) => {
|
|
3922
4086
|
e.stopPropagation();
|
|
3923
4087
|
e.preventDefault();
|
|
3924
4088
|
onValueChange?.(null);
|
|
3925
4089
|
}, [onValueChange]);
|
|
4090
|
+
const toggleOpen = useCallback(() => {
|
|
4091
|
+
if (disabled) return;
|
|
4092
|
+
setIsPopoverOpen(!isPopoverOpenRef.current);
|
|
4093
|
+
}, [disabled, setIsPopoverOpen]);
|
|
3926
4094
|
const handleRootOpenChange = useCallback((next) => {
|
|
3927
4095
|
if (disabled) return;
|
|
3928
4096
|
if (next) setIsPopoverOpen(true);
|
|
3929
|
-
}, [disabled]);
|
|
4097
|
+
}, [disabled, setIsPopoverOpen]);
|
|
3930
4098
|
useEffect(() => {
|
|
3931
4099
|
if (!isPopoverOpen) return;
|
|
3932
4100
|
function handlePointerDown(ev) {
|
|
@@ -3946,26 +4114,39 @@ var UserSelector = React$1.forwardRef(({ value, size = "medium", onValueChange,
|
|
|
3946
4114
|
document.removeEventListener("mousedown", handlePointerDown, true);
|
|
3947
4115
|
document.removeEventListener("keydown", handleKey, true);
|
|
3948
4116
|
};
|
|
3949
|
-
}, [isPopoverOpen]);
|
|
4117
|
+
}, [isPopoverOpen, setIsPopoverOpen]);
|
|
3950
4118
|
const resolvedPlaceholder = placeholder || /* @__PURE__ */ jsx(EmptyValue, { className: "ml-2" });
|
|
3951
4119
|
const portalContainer = contextPortalContainer ?? (typeof document !== "undefined" ? document.body : void 0) ?? void 0;
|
|
3952
4120
|
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(PopoverPrimitive.Root, {
|
|
3953
4121
|
open: isPopoverOpen,
|
|
3954
4122
|
onOpenChange: handleRootOpenChange,
|
|
3955
4123
|
modal: false,
|
|
3956
|
-
children: [/* @__PURE__ */ jsx(
|
|
3957
|
-
|
|
3958
|
-
children: /* @__PURE__ */ jsx("
|
|
4124
|
+
children: [/* @__PURE__ */ jsx(PickerTrigger, {
|
|
4125
|
+
anchorRef,
|
|
4126
|
+
children: triggerContent !== void 0 ? /* @__PURE__ */ jsx("div", {
|
|
4127
|
+
ref: handleButtonRef,
|
|
4128
|
+
role: "button",
|
|
4129
|
+
tabIndex: disabled ? -1 : 0,
|
|
4130
|
+
"aria-disabled": disabled || void 0,
|
|
4131
|
+
"aria-haspopup": "listbox",
|
|
4132
|
+
"aria-expanded": isPopoverOpen,
|
|
4133
|
+
"data-user-selector-trigger": true,
|
|
4134
|
+
onClick: toggleOpen,
|
|
4135
|
+
onKeyDown: (event) => {
|
|
4136
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
4137
|
+
event.preventDefault();
|
|
4138
|
+
toggleOpen();
|
|
4139
|
+
},
|
|
4140
|
+
className: cls("w-full select-none text-left text-sm relative flex items-center outline-none", className),
|
|
4141
|
+
children: triggerContent
|
|
4142
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
3959
4143
|
ref: handleButtonRef,
|
|
3960
4144
|
type: "button",
|
|
3961
4145
|
"aria-haspopup": "listbox",
|
|
3962
4146
|
"aria-expanded": isPopoverOpen,
|
|
3963
4147
|
"data-user-selector-trigger": true,
|
|
3964
4148
|
disabled,
|
|
3965
|
-
onClick:
|
|
3966
|
-
if (disabled) return;
|
|
3967
|
-
setIsPopoverOpen((o) => !o);
|
|
3968
|
-
},
|
|
4149
|
+
onClick: toggleOpen,
|
|
3969
4150
|
className: cls({
|
|
3970
4151
|
"min-h-[42px] py-1 px-2": size === "small",
|
|
3971
4152
|
"min-h-[56px] py-2 px-4": size === "medium",
|
|
@@ -4094,8 +4275,19 @@ UserSelector.displayName = "UserSelector";
|
|
|
4094
4275
|
//#endregion
|
|
4095
4276
|
//#region src/components/CollectionTableBinding/fields/VirtualTableUserSelect.tsx
|
|
4096
4277
|
function VirtualTableUserSelect(props) {
|
|
4097
|
-
const { internalValue, disabled,
|
|
4278
|
+
const { internalValue, disabled, focused, updateValue, open, onOpenChange, preview, anchorRef } = props;
|
|
4279
|
+
const ref = useRef(null);
|
|
4280
|
+
useEffect(() => {
|
|
4281
|
+
if (focused) ref.current?.focus({ preventScroll: true });
|
|
4282
|
+
}, [focused]);
|
|
4283
|
+
const handleOpenChange = (nextOpen) => {
|
|
4284
|
+
onOpenChange?.(nextOpen);
|
|
4285
|
+
if (!nextOpen) setTimeout(() => {
|
|
4286
|
+
if (!document.activeElement || document.activeElement === document.body) ref.current?.focus({ preventScroll: true });
|
|
4287
|
+
}, 0);
|
|
4288
|
+
};
|
|
4098
4289
|
return /* @__PURE__ */ jsx(UserSelector, {
|
|
4290
|
+
ref,
|
|
4099
4291
|
value: Array.isArray(internalValue) ? internalValue[0] ?? null : internalValue ?? null,
|
|
4100
4292
|
onValueChange: (userId) => {
|
|
4101
4293
|
updateValue(userId);
|
|
@@ -4103,7 +4295,11 @@ function VirtualTableUserSelect(props) {
|
|
|
4103
4295
|
disabled,
|
|
4104
4296
|
size: "small",
|
|
4105
4297
|
clearable: true,
|
|
4106
|
-
invisible: true
|
|
4298
|
+
invisible: true,
|
|
4299
|
+
open,
|
|
4300
|
+
onOpenChange: handleOpenChange,
|
|
4301
|
+
triggerContent: preview,
|
|
4302
|
+
anchorRef
|
|
4107
4303
|
});
|
|
4108
4304
|
}
|
|
4109
4305
|
//#endregion
|
|
@@ -4388,55 +4584,38 @@ function TableStorageItemPreview({ propertyKey, property, value, size, entity })
|
|
|
4388
4584
|
//#endregion
|
|
4389
4585
|
//#region src/components/CollectionTableBinding/fields/CompactEntityCellField.tsx
|
|
4390
4586
|
/**
|
|
4391
|
-
*
|
|
4392
|
-
*
|
|
4587
|
+
* A reference or relation cell, at the row sizes that carry small previews
|
|
4588
|
+
* (`xs`, `s`, `m`), for the pickers that open a selection dialog.
|
|
4393
4589
|
*
|
|
4394
|
-
* The
|
|
4395
|
-
*
|
|
4396
|
-
*
|
|
4397
|
-
* the
|
|
4398
|
-
*
|
|
4399
|
-
*
|
|
4590
|
+
* The value is one line of inline previews, the same at rest and selected:
|
|
4591
|
+
* each title opens its record in the side panel. The cell's opener — the
|
|
4592
|
+
* chevron it reveals on hover — opens the dialog, and a cross beside it clears
|
|
4593
|
+
* the value. The cross follows the opener: hidden at rest, shown on hover and
|
|
4594
|
+
* while the cell is selected. Unlike the opener it saves at once, so hidden it
|
|
4595
|
+
* cannot be hit either: a tap where nothing is hovered selects the cell.
|
|
4400
4596
|
*/
|
|
4401
|
-
function CompactEntityCellField({ children, empty, disabled,
|
|
4597
|
+
function CompactEntityCellField({ children, empty, disabled, selected, onClear }) {
|
|
4402
4598
|
const { t } = useTranslation();
|
|
4403
4599
|
return /* @__PURE__ */ jsxs("div", {
|
|
4404
4600
|
className: "w-full min-w-0 flex items-center gap-0.5 min-h-8",
|
|
4405
4601
|
children: [/* @__PURE__ */ jsx("div", {
|
|
4406
4602
|
className: "grow min-w-0 flex items-center gap-x-3 overflow-hidden text-sm",
|
|
4407
|
-
children: empty ? /* @__PURE__ */ jsx(
|
|
4408
|
-
|
|
4409
|
-
disabled,
|
|
4410
|
-
onClick: (e) => {
|
|
4411
|
-
e.stopPropagation();
|
|
4412
|
-
onEdit();
|
|
4413
|
-
},
|
|
4414
|
-
className: cls("truncate text-left text-text-secondary dark:text-text-secondary-dark", !disabled && "cursor-pointer hover:text-text-primary dark:hover:text-text-primary-dark"),
|
|
4415
|
-
children: emptyLabel ?? t("select_reference")
|
|
4416
|
-
}) : children
|
|
4417
|
-
}), !disabled && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
4418
|
-
title: t("edit"),
|
|
4419
|
-
children: /* @__PURE__ */ jsx(IconButton, {
|
|
4420
|
-
size: "small",
|
|
4421
|
-
"aria-label": t("edit"),
|
|
4422
|
-
onClick: (e) => {
|
|
4423
|
-
e.stopPropagation();
|
|
4424
|
-
onEdit();
|
|
4425
|
-
},
|
|
4426
|
-
children: /* @__PURE__ */ jsx(PencilIcon, {})
|
|
4427
|
-
})
|
|
4428
|
-
}), !empty && /* @__PURE__ */ jsx(Tooltip, {
|
|
4603
|
+
children: empty ? /* @__PURE__ */ jsx(EmptyValue, {}) : children
|
|
4604
|
+
}), !disabled && !empty && /* @__PURE__ */ jsx(Tooltip, {
|
|
4429
4605
|
title: t("clear"),
|
|
4430
4606
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
4431
|
-
size: "
|
|
4607
|
+
size: "smallest",
|
|
4608
|
+
shape: "square",
|
|
4432
4609
|
"aria-label": t("clear"),
|
|
4610
|
+
className: cls("w-6 !h-6 min-w-6 min-h-6 p-0 hover:scale-100 transition-opacity duration-100", selected ? "opacity-100" : "opacity-0 pointer-events-none group-hover/cell:opacity-100 group-hover/cell:pointer-events-auto focus-visible:opacity-100"),
|
|
4611
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
4433
4612
|
onClick: (e) => {
|
|
4434
4613
|
e.stopPropagation();
|
|
4435
4614
|
onClear();
|
|
4436
4615
|
},
|
|
4437
|
-
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
4616
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 14 })
|
|
4438
4617
|
})
|
|
4439
|
-
})]
|
|
4618
|
+
})]
|
|
4440
4619
|
});
|
|
4441
4620
|
}
|
|
4442
4621
|
//#endregion
|
|
@@ -4467,7 +4646,7 @@ function TableReferenceField(props) {
|
|
|
4467
4646
|
return content;
|
|
4468
4647
|
}
|
|
4469
4648
|
var TableReferenceFieldInternal = React.memo(function TableReferenceFieldInternal(props) {
|
|
4470
|
-
const { name, internalValue, updateValue, multiselect, path, size, previewProperties,
|
|
4649
|
+
const { name, internalValue, updateValue, multiselect, path, size, previewProperties, disabled, fixedFilter, collection, includeId, includeEntityLink, selected, open, onOpenChange } = props;
|
|
4471
4650
|
const onSingleEntitySelected = useCallback((entity) => {
|
|
4472
4651
|
updateValue(entity ? getReferenceFrom(entity) : null);
|
|
4473
4652
|
}, [updateValue]);
|
|
@@ -4486,72 +4665,27 @@ var TableReferenceFieldInternal = React.memo(function TableReferenceFieldInterna
|
|
|
4486
4665
|
if (disabled) return;
|
|
4487
4666
|
referenceDialogController.open();
|
|
4488
4667
|
};
|
|
4668
|
+
useEffect(() => {
|
|
4669
|
+
if (!open) return;
|
|
4670
|
+
onOpenChange?.(false);
|
|
4671
|
+
handleOpen();
|
|
4672
|
+
}, [open]);
|
|
4489
4673
|
const valueNotSet = !internalValue || Array.isArray(internalValue) && internalValue.length === 0;
|
|
4490
|
-
const buildSingleReferenceField = () => {
|
|
4491
|
-
if (internalValue && !Array.isArray(internalValue) && internalValue.isEntityReference && internalValue.isEntityReference()) return /* @__PURE__ */ jsx(ReferencePreview, {
|
|
4492
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4493
|
-
size: getPreviewSizeFrom(size),
|
|
4494
|
-
reference: internalValue,
|
|
4495
|
-
hover: !disabled,
|
|
4496
|
-
disabled: !path,
|
|
4497
|
-
previewProperties,
|
|
4498
|
-
includeId,
|
|
4499
|
-
includeEntityLink
|
|
4500
|
-
});
|
|
4501
|
-
else return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
4502
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4503
|
-
size: getPreviewSizeFrom(size),
|
|
4504
|
-
children: /* @__PURE__ */ jsx(ErrorView, {
|
|
4505
|
-
title: "Value is not a reference.",
|
|
4506
|
-
error: "Click to edit"
|
|
4507
|
-
})
|
|
4508
|
-
});
|
|
4509
|
-
};
|
|
4510
|
-
const buildMultipleReferenceField = () => {
|
|
4511
|
-
if (Array.isArray(internalValue)) return /* @__PURE__ */ jsx(Fragment, { children: internalValue.map((reference, index) => /* @__PURE__ */ jsx("div", {
|
|
4512
|
-
className: "w-full my-0.5",
|
|
4513
|
-
children: /* @__PURE__ */ jsx(ReferencePreview, {
|
|
4514
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4515
|
-
size: "small",
|
|
4516
|
-
reference,
|
|
4517
|
-
hover: !disabled,
|
|
4518
|
-
disabled: !path,
|
|
4519
|
-
previewProperties,
|
|
4520
|
-
includeId,
|
|
4521
|
-
includeEntityLink
|
|
4522
|
-
})
|
|
4523
|
-
}, `preview_array_ref_${name}_${index}`)) });
|
|
4524
|
-
else return /* @__PURE__ */ jsx(ErrorView, { error: "Data is not an array of references" });
|
|
4525
|
-
};
|
|
4526
4674
|
if (!collection) return /* @__PURE__ */ jsx(ErrorView, { error: "The specified collection does not exist" });
|
|
4527
|
-
|
|
4675
|
+
return /* @__PURE__ */ jsx(CompactEntityCellField, {
|
|
4528
4676
|
empty: valueNotSet,
|
|
4529
4677
|
disabled,
|
|
4530
|
-
|
|
4678
|
+
selected,
|
|
4531
4679
|
onClear: () => updateValue(multiselect ? [] : null),
|
|
4532
|
-
emptyLabel: title,
|
|
4533
4680
|
children: (!internalValue ? [] : Array.isArray(internalValue) ? internalValue : [internalValue]).map((reference, index) => reference && reference.isEntityReference && reference.isEntityReference() ? /* @__PURE__ */ jsx(ReferencePreview, {
|
|
4534
|
-
size: "small",
|
|
4681
|
+
size: multiselect ? "small" : getPreviewSizeFrom(size),
|
|
4535
4682
|
reference,
|
|
4536
4683
|
hover: false,
|
|
4537
4684
|
disabled: !path,
|
|
4538
4685
|
previewProperties,
|
|
4539
4686
|
includeId,
|
|
4540
4687
|
includeEntityLink
|
|
4541
|
-
}, `
|
|
4542
|
-
});
|
|
4543
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4544
|
-
className: "w-full group",
|
|
4545
|
-
children: [
|
|
4546
|
-
internalValue && !multiselect && buildSingleReferenceField(),
|
|
4547
|
-
internalValue && multiselect && buildMultipleReferenceField(),
|
|
4548
|
-
valueNotSet && /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
4549
|
-
className: cls("px-3 py-2 text-sm font-medium flex items-center", multiselect ? "gap-4" : "gap-6", disabled ? "text-surface-accent-500" : "cursor-pointer text-text-secondary dark:text-text-secondary-dark hover:bg-surface-hover group-hover:bg-surface-hover"),
|
|
4550
|
-
onClick: handleOpen,
|
|
4551
|
-
size: "medium",
|
|
4552
|
-
children: [/* @__PURE__ */ jsx(PencilIcon, { className: "ml-2 mr-1 text-surface-300 dark:text-surface-600" }), title]
|
|
4553
|
-
})
|
|
4554
|
-
]
|
|
4688
|
+
}, `compact_ref_${name}_${index}`) : /* @__PURE__ */ jsx(ErrorView, { error: "Value is not a reference" }, `compact_ref_${name}_${index}`))
|
|
4555
4689
|
});
|
|
4556
4690
|
}, deepEqual);
|
|
4557
4691
|
//#endregion
|
|
@@ -4564,7 +4698,7 @@ function TableMultipleRelationField(props) {
|
|
|
4564
4698
|
});
|
|
4565
4699
|
}
|
|
4566
4700
|
var TableMultipleRelationFieldInternal = React.memo(function TableMultipleRelationFieldInternal(props) {
|
|
4567
|
-
const { name, internalValue, updateValue, previewProperties,
|
|
4701
|
+
const { name, internalValue, updateValue, previewProperties, disabled, fixedFilter, collection, includeId, includeEntityLink, selected, open, onOpenChange } = props;
|
|
4568
4702
|
const value = Array.isArray(internalValue) ? internalValue : [];
|
|
4569
4703
|
const onMultipleEntitiesSelected = useCallback((entities) => {
|
|
4570
4704
|
updateValue(entities.map((e) => getRelationFrom(e)));
|
|
@@ -4582,33 +4716,18 @@ var TableMultipleRelationFieldInternal = React.memo(function TableMultipleRelati
|
|
|
4582
4716
|
if (disabled) return;
|
|
4583
4717
|
relationDialogController.open();
|
|
4584
4718
|
};
|
|
4719
|
+
useEffect(() => {
|
|
4720
|
+
if (!open) return;
|
|
4721
|
+
onOpenChange?.(false);
|
|
4722
|
+
handleOpen();
|
|
4723
|
+
}, [open]);
|
|
4585
4724
|
const valueNotSet = !internalValue || Array.isArray(internalValue) && internalValue.length === 0;
|
|
4586
|
-
const buildMultipleRelationField = () => {
|
|
4587
|
-
if (Array.isArray(internalValue)) return /* @__PURE__ */ jsx(Fragment, { children: internalValue.map((item, index) => {
|
|
4588
|
-
const relationItem = normalizeToEntityRelation(item);
|
|
4589
|
-
if (!relationItem) return null;
|
|
4590
|
-
return /* @__PURE__ */ jsx("div", {
|
|
4591
|
-
className: "w-full my-0.5",
|
|
4592
|
-
children: /* @__PURE__ */ jsx(RelationPreview, {
|
|
4593
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4594
|
-
size: "small",
|
|
4595
|
-
relation: relationItem,
|
|
4596
|
-
hover: !disabled,
|
|
4597
|
-
previewProperties,
|
|
4598
|
-
includeId,
|
|
4599
|
-
includeEntityLink
|
|
4600
|
-
})
|
|
4601
|
-
}, `preview_array_ref_${name}_${index}`);
|
|
4602
|
-
}) });
|
|
4603
|
-
else return /* @__PURE__ */ jsx(ErrorView, { error: "Data is not an array of relations" });
|
|
4604
|
-
};
|
|
4605
4725
|
if (!collection) return /* @__PURE__ */ jsx(ErrorView, { error: "The specified collection does not exist" });
|
|
4606
|
-
|
|
4726
|
+
return /* @__PURE__ */ jsx(CompactEntityCellField, {
|
|
4607
4727
|
empty: valueNotSet,
|
|
4608
4728
|
disabled,
|
|
4609
|
-
|
|
4729
|
+
selected,
|
|
4610
4730
|
onClear: () => updateValue([]),
|
|
4611
|
-
emptyLabel: title,
|
|
4612
4731
|
children: value.map((item, index) => {
|
|
4613
4732
|
const relationItem = normalizeToEntityRelation(item);
|
|
4614
4733
|
if (!relationItem) return null;
|
|
@@ -4619,18 +4738,9 @@ var TableMultipleRelationFieldInternal = React.memo(function TableMultipleRelati
|
|
|
4619
4738
|
previewProperties,
|
|
4620
4739
|
includeId,
|
|
4621
4740
|
includeEntityLink
|
|
4622
|
-
}, `
|
|
4741
|
+
}, `compact_rel_${name}_${index}`);
|
|
4623
4742
|
})
|
|
4624
4743
|
});
|
|
4625
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4626
|
-
className: "w-full group",
|
|
4627
|
-
children: [internalValue && buildMultipleRelationField(), valueNotSet && /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
4628
|
-
className: cls("px-3 py-2 text-sm font-medium flex items-center gap-4", disabled ? "text-surface-accent-500" : "cursor-pointer text-text-secondary dark:text-text-secondary-dark hover:bg-surface-hover group-hover:bg-surface-hover"),
|
|
4629
|
-
onClick: handleOpen,
|
|
4630
|
-
size: "medium",
|
|
4631
|
-
children: [/* @__PURE__ */ jsx(PencilIcon, { className: "ml-2 mr-1 text-surface-300 dark:text-surface-600" }), title]
|
|
4632
|
-
})]
|
|
4633
|
-
});
|
|
4634
4744
|
}, deepEqual);
|
|
4635
4745
|
//#endregion
|
|
4636
4746
|
//#region src/components/CollectionTableBinding/fields/TableRelationField.tsx
|
|
@@ -4649,13 +4759,14 @@ function TableRelationField(props) {
|
|
|
4649
4759
|
disabled: props.disabled,
|
|
4650
4760
|
internalValue: Array.isArray(props.internalValue) ? props.internalValue : [],
|
|
4651
4761
|
updateValue: (newValue) => props.updateValue(newValue),
|
|
4652
|
-
size: props.size,
|
|
4653
4762
|
previewProperties: props.previewProperties,
|
|
4654
|
-
title: props.title,
|
|
4655
4763
|
relation: props.relation,
|
|
4656
4764
|
fixedFilter: props.fixedFilter,
|
|
4657
4765
|
includeId: props.includeId,
|
|
4658
|
-
includeEntityLink: props.includeEntityLink
|
|
4766
|
+
includeEntityLink: props.includeEntityLink,
|
|
4767
|
+
selected: props.selected,
|
|
4768
|
+
open: props.open,
|
|
4769
|
+
onOpenChange: props.onOpenChange
|
|
4659
4770
|
});
|
|
4660
4771
|
return /* @__PURE__ */ jsx(TableRelationFieldInternal, {
|
|
4661
4772
|
...props,
|
|
@@ -4663,7 +4774,7 @@ function TableRelationField(props) {
|
|
|
4663
4774
|
});
|
|
4664
4775
|
}
|
|
4665
4776
|
var TableRelationFieldInternal = React.memo(function TableRelationFieldInternal(props) {
|
|
4666
|
-
const { name, internalValue, updateValue, multiselect, relation, size, previewProperties,
|
|
4777
|
+
const { name, internalValue, updateValue, multiselect, relation, size, previewProperties, disabled, fixedFilter, collection, includeId, includeEntityLink, selected, open, onOpenChange } = props;
|
|
4667
4778
|
const onSingleEntitySelected = useCallback((entity) => {
|
|
4668
4779
|
updateValue(entity ? getRelationFrom(entity) : null);
|
|
4669
4780
|
}, [updateValue]);
|
|
@@ -4684,75 +4795,31 @@ var TableRelationFieldInternal = React.memo(function TableRelationFieldInternal(
|
|
|
4684
4795
|
if (disabled) return;
|
|
4685
4796
|
relationDialogController.open();
|
|
4686
4797
|
};
|
|
4798
|
+
useEffect(() => {
|
|
4799
|
+
if (!open) return;
|
|
4800
|
+
onOpenChange?.(false);
|
|
4801
|
+
handleOpen();
|
|
4802
|
+
}, [open]);
|
|
4687
4803
|
const valueNotSet = !internalValue || Array.isArray(internalValue) && internalValue.length === 0;
|
|
4688
|
-
const buildSingleRelationField = () => {
|
|
4689
|
-
const normalizedRelation = normalizeToEntityRelation(internalValue);
|
|
4690
|
-
if (normalizedRelation) return /* @__PURE__ */ jsx(RelationPreview, {
|
|
4691
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4692
|
-
size: getPreviewSizeFrom(size),
|
|
4693
|
-
relation: normalizedRelation,
|
|
4694
|
-
hover: !disabled,
|
|
4695
|
-
previewProperties,
|
|
4696
|
-
includeId,
|
|
4697
|
-
includeEntityLink
|
|
4698
|
-
});
|
|
4699
|
-
else return /* @__PURE__ */ jsx(EntityPreviewContainer, {
|
|
4700
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4701
|
-
size: getPreviewSizeFrom(size),
|
|
4702
|
-
children: /* @__PURE__ */ jsx(ErrorView, {
|
|
4703
|
-
title: "Value is not a relation.",
|
|
4704
|
-
error: "Click to edit"
|
|
4705
|
-
})
|
|
4706
|
-
});
|
|
4707
|
-
};
|
|
4708
|
-
const buildMultipleRelationField = () => {
|
|
4709
|
-
if (Array.isArray(internalValue)) return /* @__PURE__ */ jsx(Fragment, { children: internalValue.map((relationItem, index) => /* @__PURE__ */ jsx("div", {
|
|
4710
|
-
className: "w-full my-0.5",
|
|
4711
|
-
children: /* @__PURE__ */ jsx(RelationPreview, {
|
|
4712
|
-
onClick: disabled ? void 0 : handleOpen,
|
|
4713
|
-
size: "small",
|
|
4714
|
-
relation: relationItem,
|
|
4715
|
-
hover: !disabled,
|
|
4716
|
-
previewProperties,
|
|
4717
|
-
includeId,
|
|
4718
|
-
includeEntityLink
|
|
4719
|
-
})
|
|
4720
|
-
}, `preview_array_ref_${name}_${index}`)) });
|
|
4721
|
-
else return /* @__PURE__ */ jsx(ErrorView, { error: "Data is not an array of relations" });
|
|
4722
|
-
};
|
|
4723
4804
|
if (!collection) return /* @__PURE__ */ jsx(ErrorView, { error: "The specified collection does not exist" });
|
|
4724
|
-
|
|
4805
|
+
return /* @__PURE__ */ jsx(CompactEntityCellField, {
|
|
4725
4806
|
empty: valueNotSet,
|
|
4726
4807
|
disabled,
|
|
4727
|
-
|
|
4808
|
+
selected,
|
|
4728
4809
|
onClear: () => updateValue(multiselect ? [] : null),
|
|
4729
|
-
emptyLabel: title,
|
|
4730
4810
|
children: (!internalValue ? [] : Array.isArray(internalValue) ? internalValue : [internalValue]).map((item, index) => {
|
|
4731
4811
|
const relationItem = normalizeToEntityRelation(item);
|
|
4732
4812
|
if (!relationItem) return null;
|
|
4733
4813
|
return /* @__PURE__ */ jsx(RelationPreview, {
|
|
4734
|
-
size: "small",
|
|
4814
|
+
size: multiselect ? "small" : getPreviewSizeFrom(size),
|
|
4735
4815
|
relation: relationItem,
|
|
4736
4816
|
hover: false,
|
|
4737
4817
|
previewProperties,
|
|
4738
4818
|
includeId,
|
|
4739
4819
|
includeEntityLink
|
|
4740
|
-
}, `
|
|
4820
|
+
}, `compact_rel_${name}_${index}`);
|
|
4741
4821
|
})
|
|
4742
4822
|
});
|
|
4743
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
4744
|
-
className: "w-full group",
|
|
4745
|
-
children: [
|
|
4746
|
-
internalValue && !multiselect && buildSingleRelationField(),
|
|
4747
|
-
internalValue && multiselect && buildMultipleRelationField(),
|
|
4748
|
-
valueNotSet && /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
4749
|
-
className: cls("px-3 py-2 text-sm font-medium flex items-center", multiselect ? "gap-4" : "gap-6", disabled ? "text-surface-accent-500" : "cursor-pointer text-text-secondary dark:text-text-secondary-dark hover:bg-surface-hover group-hover:bg-surface-hover"),
|
|
4750
|
-
onClick: handleOpen,
|
|
4751
|
-
size: "medium",
|
|
4752
|
-
children: [/* @__PURE__ */ jsx(PencilIcon, { className: "ml-2 mr-1 text-surface-300 dark:text-surface-600" }), title]
|
|
4753
|
-
})
|
|
4754
|
-
]
|
|
4755
|
-
});
|
|
4756
4823
|
}, deepEqual);
|
|
4757
4824
|
//#endregion
|
|
4758
4825
|
//#region src/components/RelationSelector.tsx
|
|
@@ -4764,7 +4831,7 @@ function relationCardinality$2(relation) {
|
|
|
4764
4831
|
if (relation.kind === "belongsTo" || relation.kind === "hasOne") return "one";
|
|
4765
4832
|
return relation.cardinality;
|
|
4766
4833
|
}
|
|
4767
|
-
var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChange, invisible, disabled, placeholder, useChips = true, className, relation, multiple: multipleOverride, fixedFilter, pageSize, emptyPlaceholder, searchPlaceholder = "Search...", noResultsText = "No matches.", emptyText = "Select…", emptyCollectionText, loadingText = "Loading...", allowCreate = true }, ref) => {
|
|
4834
|
+
var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChange, invisible, disabled, placeholder, useChips = true, className, relation, multiple: multipleOverride, fixedFilter, pageSize, emptyPlaceholder, searchPlaceholder = "Search...", noResultsText = "No matches.", emptyText = "Select…", emptyCollectionText, loadingText = "Loading...", allowCreate = true, open: openProp, onOpenChange, triggerContent, anchorRef }, ref) => {
|
|
4768
4835
|
const collection = relation.target();
|
|
4769
4836
|
const dataPath = getCollectionDataPath(collection);
|
|
4770
4837
|
const dataClient = useData();
|
|
@@ -4773,7 +4840,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4773
4840
|
const { t } = useTranslation();
|
|
4774
4841
|
const contextPortalContainer = usePortalContainer();
|
|
4775
4842
|
const multiple = multipleOverride ?? relationCardinality$2(relation) === "many";
|
|
4776
|
-
const [isPopoverOpen,
|
|
4843
|
+
const [isPopoverOpen, setIsPopoverOpenState] = useState(false);
|
|
4777
4844
|
const isPopoverOpenRef = useRef(false);
|
|
4778
4845
|
const [listRequested, setListRequested] = useState(false);
|
|
4779
4846
|
const [selectedItems, setSelectedItems] = useState([]);
|
|
@@ -4781,6 +4848,32 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4781
4848
|
const [searchString, setSearchString] = useState("");
|
|
4782
4849
|
const localSelectionIdsRef = useRef(null);
|
|
4783
4850
|
const pinnedIdsRef = useRef(null);
|
|
4851
|
+
const selectedItemsForPinRef = useRef([]);
|
|
4852
|
+
selectedItemsForPinRef.current = selectedItems;
|
|
4853
|
+
const onOpenChangeRef = useRef(onOpenChange);
|
|
4854
|
+
onOpenChangeRef.current = onOpenChange;
|
|
4855
|
+
const setPopoverOpen = useCallback((next, notify = true) => {
|
|
4856
|
+
if (next) {
|
|
4857
|
+
pinnedIdsRef.current = /* @__PURE__ */ new Set([...selectedItemsForPinRef.current.map((i) => String(i.id)), ...relationsArrayRef.current.map((rel) => String(typeof rel === "string" || typeof rel === "number" ? rel : rel.id))]);
|
|
4858
|
+
setListRequested(true);
|
|
4859
|
+
} else pinnedIdsRef.current = null;
|
|
4860
|
+
const changed = isPopoverOpenRef.current !== next;
|
|
4861
|
+
isPopoverOpenRef.current = next;
|
|
4862
|
+
setIsPopoverOpenState(next);
|
|
4863
|
+
if (changed && notify) onOpenChangeRef.current?.(next);
|
|
4864
|
+
}, []);
|
|
4865
|
+
useEffect(() => {
|
|
4866
|
+
if (openProp === void 0 || openProp === isPopoverOpenRef.current) return;
|
|
4867
|
+
if (openProp && disabled) {
|
|
4868
|
+
onOpenChangeRef.current?.(false);
|
|
4869
|
+
return;
|
|
4870
|
+
}
|
|
4871
|
+
setPopoverOpen(openProp, false);
|
|
4872
|
+
}, [
|
|
4873
|
+
openProp,
|
|
4874
|
+
disabled,
|
|
4875
|
+
setPopoverOpen
|
|
4876
|
+
]);
|
|
4784
4877
|
const { items: availableItems, isLoading, hasMore, search, loadMore, entityToRelationItem } = useRelationSelector({
|
|
4785
4878
|
path: dataPath,
|
|
4786
4879
|
collection,
|
|
@@ -4826,7 +4919,9 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4826
4919
|
let active = true;
|
|
4827
4920
|
const currentRelationsArray = relationsArrayRef.current;
|
|
4828
4921
|
if (incomingIds === resolvedIdsRef.current && (allHaveData || hasResolvedItemsRef.current)) return;
|
|
4829
|
-
|
|
4922
|
+
const localSelectionIds = localSelectionIdsRef.current;
|
|
4923
|
+
localSelectionIdsRef.current = null;
|
|
4924
|
+
if (localSelectionIds !== null && incomingIds === localSelectionIds) {
|
|
4830
4925
|
resolvedIdsRef.current = incomingIds;
|
|
4831
4926
|
hasResolvedItemsRef.current = true;
|
|
4832
4927
|
return;
|
|
@@ -4949,9 +5044,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4949
5044
|
if (multiple) newSelected = selectedItems.some((v) => String(v.id) === String(item.id)) ? selectedItems.filter((v) => String(v.id) !== String(item.id)) : [...selectedItems, item];
|
|
4950
5045
|
else {
|
|
4951
5046
|
newSelected = [item];
|
|
4952
|
-
|
|
4953
|
-
isPopoverOpenRef.current = false;
|
|
4954
|
-
pinnedIdsRef.current = null;
|
|
5047
|
+
setPopoverOpen(false);
|
|
4955
5048
|
}
|
|
4956
5049
|
setSelectedItems(newSelected);
|
|
4957
5050
|
localSelectionIdsRef.current = computeSelectionFingerprint(newSelected);
|
|
@@ -4960,7 +5053,8 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4960
5053
|
multiple,
|
|
4961
5054
|
selectedItems,
|
|
4962
5055
|
emitValueChange,
|
|
4963
|
-
computeSelectionFingerprint
|
|
5056
|
+
computeSelectionFingerprint,
|
|
5057
|
+
setPopoverOpen
|
|
4964
5058
|
]);
|
|
4965
5059
|
const handleClear = useCallback(() => {
|
|
4966
5060
|
setSelectedItems([]);
|
|
@@ -4979,13 +5073,8 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4979
5073
|
]);
|
|
4980
5074
|
const handleRootOpenChange = useCallback((next) => {
|
|
4981
5075
|
if (disabled) return;
|
|
4982
|
-
if (next)
|
|
4983
|
-
|
|
4984
|
-
setListRequested(true);
|
|
4985
|
-
setIsPopoverOpen(true);
|
|
4986
|
-
isPopoverOpenRef.current = true;
|
|
4987
|
-
}
|
|
4988
|
-
}, [disabled, selectedItems]);
|
|
5076
|
+
if (next) setPopoverOpen(true);
|
|
5077
|
+
}, [disabled, setPopoverOpen]);
|
|
4989
5078
|
useEffect(() => {
|
|
4990
5079
|
if (!isPopoverOpen) return;
|
|
4991
5080
|
function handlePointerDown(ev) {
|
|
@@ -4994,16 +5083,10 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
4994
5083
|
const contentEl = contentRef.current;
|
|
4995
5084
|
if (triggerEl?.contains(target)) return;
|
|
4996
5085
|
if (contentEl?.contains(target)) return;
|
|
4997
|
-
|
|
4998
|
-
isPopoverOpenRef.current = false;
|
|
4999
|
-
pinnedIdsRef.current = null;
|
|
5086
|
+
setPopoverOpen(false);
|
|
5000
5087
|
}
|
|
5001
5088
|
function handleKey(ev) {
|
|
5002
|
-
if (ev.key === "Escape")
|
|
5003
|
-
setIsPopoverOpen(false);
|
|
5004
|
-
isPopoverOpenRef.current = false;
|
|
5005
|
-
pinnedIdsRef.current = null;
|
|
5006
|
-
}
|
|
5089
|
+
if (ev.key === "Escape") setPopoverOpen(false);
|
|
5007
5090
|
}
|
|
5008
5091
|
document.addEventListener("mousedown", handlePointerDown, true);
|
|
5009
5092
|
document.addEventListener("keydown", handleKey, true);
|
|
@@ -5011,12 +5094,12 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
5011
5094
|
document.removeEventListener("mousedown", handlePointerDown, true);
|
|
5012
5095
|
document.removeEventListener("keydown", handleKey, true);
|
|
5013
5096
|
};
|
|
5014
|
-
}, [isPopoverOpen]);
|
|
5015
|
-
const closePopover = useCallback(() =>
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
}, []);
|
|
5097
|
+
}, [isPopoverOpen, setPopoverOpen]);
|
|
5098
|
+
const closePopover = useCallback(() => setPopoverOpen(false), [setPopoverOpen]);
|
|
5099
|
+
const toggleOpen = useCallback(() => {
|
|
5100
|
+
if (disabled) return;
|
|
5101
|
+
setPopoverOpen(!isPopoverOpenRef.current);
|
|
5102
|
+
}, [disabled, setPopoverOpen]);
|
|
5020
5103
|
const canCreateTarget = allowCreate && canCreate(collection, dataPath);
|
|
5021
5104
|
const buildDefaultValues = useCallback((searchText) => {
|
|
5022
5105
|
const trimmed = searchText.trim();
|
|
@@ -5062,28 +5145,39 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
5062
5145
|
open: isPopoverOpen,
|
|
5063
5146
|
onOpenChange: handleRootOpenChange,
|
|
5064
5147
|
modal: false,
|
|
5065
|
-
children: [/* @__PURE__ */ jsx(
|
|
5066
|
-
|
|
5067
|
-
children: /* @__PURE__ */ jsx("
|
|
5148
|
+
children: [/* @__PURE__ */ jsx(PickerTrigger, {
|
|
5149
|
+
anchorRef,
|
|
5150
|
+
children: triggerContent !== void 0 ? /* @__PURE__ */ jsx("div", {
|
|
5151
|
+
ref: handleButtonRef,
|
|
5152
|
+
role: "button",
|
|
5153
|
+
tabIndex: disabled ? -1 : 0,
|
|
5154
|
+
"aria-disabled": disabled || void 0,
|
|
5155
|
+
"aria-haspopup": "listbox",
|
|
5156
|
+
"aria-expanded": isPopoverOpen,
|
|
5157
|
+
"data-relation-selector-trigger": true,
|
|
5158
|
+
onClick: toggleOpen,
|
|
5159
|
+
onClickCapture: (event) => {
|
|
5160
|
+
const target = event.target;
|
|
5161
|
+
if (!(target instanceof Element) || !isPopoverOpenRef.current) return;
|
|
5162
|
+
const interactive = target.closest("a, button, [role=\"button\"]");
|
|
5163
|
+
if (interactive && interactive !== event.currentTarget) setPopoverOpen(false);
|
|
5164
|
+
},
|
|
5165
|
+
onKeyDown: (event) => {
|
|
5166
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
5167
|
+
event.preventDefault();
|
|
5168
|
+
toggleOpen();
|
|
5169
|
+
},
|
|
5170
|
+
className: cls("w-full select-none text-left text-sm relative flex items-center outline-none", className),
|
|
5171
|
+
children: triggerContent
|
|
5172
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
5068
5173
|
ref: handleButtonRef,
|
|
5069
5174
|
type: "button",
|
|
5070
5175
|
"aria-haspopup": "listbox",
|
|
5071
5176
|
"aria-expanded": isPopoverOpen,
|
|
5072
5177
|
"data-relation-selector-trigger": true,
|
|
5073
5178
|
disabled,
|
|
5074
|
-
onClick:
|
|
5075
|
-
if (disabled) return;
|
|
5076
|
-
setListRequested(true);
|
|
5077
|
-
setIsPopoverOpen((o) => {
|
|
5078
|
-
const next = !o;
|
|
5079
|
-
isPopoverOpenRef.current = next;
|
|
5080
|
-
if (next) pinnedIdsRef.current = new Set(selectedItems.map((i) => String(i.id)));
|
|
5081
|
-
else pinnedIdsRef.current = null;
|
|
5082
|
-
return next;
|
|
5083
|
-
});
|
|
5084
|
-
},
|
|
5179
|
+
onClick: toggleOpen,
|
|
5085
5180
|
className: cls({
|
|
5086
|
-
"min-h-8 py-0.5 px-2": size === "compact",
|
|
5087
5181
|
"min-h-[42px] py-1 px-2": size === "small",
|
|
5088
5182
|
"min-h-[56px] py-2 px-4": size === "medium",
|
|
5089
5183
|
"min-h-[48px] py-1 px-4": size === "large"
|
|
@@ -5099,36 +5193,19 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
5099
5193
|
}) : selectedItems.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
5100
5194
|
className: "flex flex-wrap items-center gap-1.5 text-start flex-1 min-w-0 mr-2",
|
|
5101
5195
|
children: selectedItems.map((item) => {
|
|
5102
|
-
if (!useChips || !multiple) {
|
|
5103
|
-
|
|
5196
|
+
if (!useChips || !multiple) return /* @__PURE__ */ jsx("div", {
|
|
5197
|
+
className: "flex flex-row items-center gap-1 truncate",
|
|
5198
|
+
children: item.data ? /* @__PURE__ */ jsx(EntityPreviewBindingData, {
|
|
5199
|
+
size: "medium",
|
|
5104
5200
|
entity: item.data,
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
path: item.data.path,
|
|
5110
|
-
collection,
|
|
5111
|
-
updateUrl: true
|
|
5112
|
-
});
|
|
5113
|
-
}
|
|
5114
|
-
}, String(item.id)) : /* @__PURE__ */ jsx("span", {
|
|
5201
|
+
includeEntityLink: false,
|
|
5202
|
+
includeId: false,
|
|
5203
|
+
onSidePanelClick: closePopover
|
|
5204
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
5115
5205
|
className: "text-sm truncate",
|
|
5116
5206
|
children: item.label
|
|
5117
|
-
}
|
|
5118
|
-
|
|
5119
|
-
className: "flex flex-row items-center gap-1 truncate",
|
|
5120
|
-
children: item.data ? /* @__PURE__ */ jsx(EntityPreviewBindingData, {
|
|
5121
|
-
size: "medium",
|
|
5122
|
-
entity: item.data,
|
|
5123
|
-
includeEntityLink: false,
|
|
5124
|
-
includeId: false,
|
|
5125
|
-
onSidePanelClick: closePopover
|
|
5126
|
-
}) : /* @__PURE__ */ jsx("span", {
|
|
5127
|
-
className: "text-sm truncate",
|
|
5128
|
-
children: item.label
|
|
5129
|
-
})
|
|
5130
|
-
}, String(item.id));
|
|
5131
|
-
}
|
|
5207
|
+
})
|
|
5208
|
+
}, String(item.id));
|
|
5132
5209
|
return /* @__PURE__ */ jsxs(Chip, {
|
|
5133
5210
|
size: "small",
|
|
5134
5211
|
className: cls("flex flex-row items-center gap-1 truncate"),
|
|
@@ -5154,7 +5231,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
5154
5231
|
children: resolvedPlaceholder
|
|
5155
5232
|
}), /* @__PURE__ */ jsxs("div", {
|
|
5156
5233
|
className: "flex items-center flex-shrink-0",
|
|
5157
|
-
children: [
|
|
5234
|
+
children: [!multiple && selectedItems.length === 1 && selectedItems[0]?.data && /* @__PURE__ */ jsx(Tooltip, {
|
|
5158
5235
|
title: `Open ${selectedItems[0].label}`,
|
|
5159
5236
|
children: /* @__PURE__ */ jsx(IconButton, {
|
|
5160
5237
|
component: "div",
|
|
@@ -5165,7 +5242,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
5165
5242
|
e.stopPropagation();
|
|
5166
5243
|
e.preventDefault();
|
|
5167
5244
|
const entity = selectedItems[0].data;
|
|
5168
|
-
|
|
5245
|
+
setPopoverOpen(false);
|
|
5169
5246
|
sidePanelController.open({
|
|
5170
5247
|
entityId: entity.id,
|
|
5171
5248
|
path: entity.path,
|
|
@@ -5176,7 +5253,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
|
|
|
5176
5253
|
children: /* @__PURE__ */ jsx(ArrowRightToLineIcon, { size: iconSize.small })
|
|
5177
5254
|
})
|
|
5178
5255
|
}), /* @__PURE__ */ jsx(ChevronDownIcon, {
|
|
5179
|
-
size: size === "small"
|
|
5256
|
+
size: size === "small" ? iconSize.small : iconSize.medium,
|
|
5180
5257
|
className: cls("transition", isPopoverOpen ? "rotate-180" : "")
|
|
5181
5258
|
})]
|
|
5182
5259
|
})]
|
|
@@ -5337,14 +5414,28 @@ function InnerCheckBox({ checked }) {
|
|
|
5337
5414
|
//#endregion
|
|
5338
5415
|
//#region src/components/CollectionTableBinding/fields/TableRelationSelectorField.tsx
|
|
5339
5416
|
/** Thin wrapper around RelationSelector for table cells */
|
|
5340
|
-
function TableRelationSelectorField({ disabled = false, internalValue, updateValue, relation, fixedFilter,
|
|
5417
|
+
function TableRelationSelectorField({ disabled = false, internalValue, updateValue, relation, fixedFilter, focused, open, onOpenChange, preview, anchorRef }) {
|
|
5418
|
+
const ref = useRef(null);
|
|
5419
|
+
useEffect(() => {
|
|
5420
|
+
if (focused) ref.current?.focus({ preventScroll: true });
|
|
5421
|
+
}, [focused]);
|
|
5422
|
+
const handleOpenChange = (nextOpen) => {
|
|
5423
|
+
onOpenChange?.(nextOpen);
|
|
5424
|
+
if (!nextOpen) setTimeout(() => {
|
|
5425
|
+
if (!document.activeElement || document.activeElement === document.body) ref.current?.focus({ preventScroll: true });
|
|
5426
|
+
}, 0);
|
|
5427
|
+
};
|
|
5341
5428
|
return /* @__PURE__ */ jsx(RelationSelector, {
|
|
5429
|
+
ref,
|
|
5342
5430
|
disabled,
|
|
5343
|
-
size,
|
|
5344
5431
|
value: internalValue || null,
|
|
5345
5432
|
onValueChange: (newVal) => updateValue(newVal ?? null),
|
|
5346
5433
|
relation,
|
|
5347
|
-
fixedFilter
|
|
5434
|
+
fixedFilter,
|
|
5435
|
+
open,
|
|
5436
|
+
onOpenChange: handleOpenChange,
|
|
5437
|
+
triggerContent: preview,
|
|
5438
|
+
anchorRef
|
|
5348
5439
|
});
|
|
5349
5440
|
}
|
|
5350
5441
|
//#endregion
|
|
@@ -5380,7 +5471,7 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5380
5471
|
else if (selected && property.type === "number") {
|
|
5381
5472
|
const numberProperty = property;
|
|
5382
5473
|
if (numberProperty.enum) return {
|
|
5383
|
-
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableSelect, {
|
|
5474
|
+
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue, open, onOpenChange, preview, anchorRef }) => /* @__PURE__ */ jsx(VirtualTableSelect, {
|
|
5384
5475
|
name: propertyKey,
|
|
5385
5476
|
multiple: false,
|
|
5386
5477
|
disabled,
|
|
@@ -5390,9 +5481,13 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5390
5481
|
enumValues: numberProperty.enum,
|
|
5391
5482
|
error: validationError ?? error,
|
|
5392
5483
|
internalValue,
|
|
5393
|
-
updateValue
|
|
5484
|
+
updateValue,
|
|
5485
|
+
open,
|
|
5486
|
+
onOpenChange,
|
|
5487
|
+
preview,
|
|
5488
|
+
anchorRef
|
|
5394
5489
|
}),
|
|
5395
|
-
|
|
5490
|
+
opener: "dropdown"
|
|
5396
5491
|
};
|
|
5397
5492
|
else return {
|
|
5398
5493
|
Component: ({ align, error, validationError, selected, disabled, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableNumberInput, {
|
|
@@ -5408,7 +5503,7 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5408
5503
|
} else if (selected && property.type === "string") {
|
|
5409
5504
|
const stringProperty = property;
|
|
5410
5505
|
if (stringProperty.enum) return {
|
|
5411
|
-
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableSelect, {
|
|
5506
|
+
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue, open, onOpenChange, preview, anchorRef }) => /* @__PURE__ */ jsx(VirtualTableSelect, {
|
|
5412
5507
|
name: propertyKey,
|
|
5413
5508
|
multiple: false,
|
|
5414
5509
|
focused: selected,
|
|
@@ -5418,12 +5513,16 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5418
5513
|
enumValues: stringProperty.enum,
|
|
5419
5514
|
error: validationError ?? error,
|
|
5420
5515
|
internalValue,
|
|
5421
|
-
updateValue
|
|
5516
|
+
updateValue,
|
|
5517
|
+
open,
|
|
5518
|
+
onOpenChange,
|
|
5519
|
+
preview,
|
|
5520
|
+
anchorRef
|
|
5422
5521
|
}),
|
|
5423
|
-
|
|
5522
|
+
opener: "dropdown"
|
|
5424
5523
|
};
|
|
5425
5524
|
else if (stringProperty.userSelect) return {
|
|
5426
|
-
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableUserSelect, {
|
|
5525
|
+
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue, open, onOpenChange, preview, anchorRef }) => /* @__PURE__ */ jsx(VirtualTableUserSelect, {
|
|
5427
5526
|
name: propertyKey,
|
|
5428
5527
|
multiple: false,
|
|
5429
5528
|
focused: selected,
|
|
@@ -5431,9 +5530,13 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5431
5530
|
small: getPreviewSizeFrom(size) !== "medium",
|
|
5432
5531
|
error: validationError ?? error,
|
|
5433
5532
|
internalValue,
|
|
5434
|
-
updateValue
|
|
5533
|
+
updateValue,
|
|
5534
|
+
open,
|
|
5535
|
+
onOpenChange,
|
|
5536
|
+
preview,
|
|
5537
|
+
anchorRef
|
|
5435
5538
|
}),
|
|
5436
|
-
|
|
5539
|
+
opener: "dropdown"
|
|
5437
5540
|
};
|
|
5438
5541
|
else if (stringProperty.admin?.markdown || !stringProperty.storage) {
|
|
5439
5542
|
const multiline = Boolean(stringProperty.admin?.multiline) || Boolean(stringProperty.admin?.markdown);
|
|
@@ -5457,29 +5560,20 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5457
5560
|
updateValue
|
|
5458
5561
|
}) };
|
|
5459
5562
|
else if (property.type === "date") return {
|
|
5460
|
-
Component:
|
|
5461
|
-
|
|
5462
|
-
return /* @__PURE__ */ jsx(VirtualTableDateField, {
|
|
5463
|
-
name: propertyKey,
|
|
5464
|
-
error: validationError ?? error,
|
|
5465
|
-
disabled,
|
|
5466
|
-
small: getPreviewSizeFrom(size) !== "medium",
|
|
5467
|
-
mode: property.mode,
|
|
5468
|
-
focused: selected,
|
|
5469
|
-
internalValue,
|
|
5470
|
-
updateValue,
|
|
5471
|
-
locale
|
|
5472
|
-
});
|
|
5473
|
-
},
|
|
5563
|
+
Component: DateBindingComponent,
|
|
5564
|
+
opener: "calendar",
|
|
5474
5565
|
fullHeight: true,
|
|
5475
5566
|
hideOverflow: false,
|
|
5476
5567
|
allowScroll: false
|
|
5477
5568
|
};
|
|
5478
5569
|
else if (property.type === "reference") {
|
|
5479
5570
|
if (property.path) return {
|
|
5480
|
-
Component: ({ propertyKey, internalValue, updateValue, disabled, size, property }) => {
|
|
5571
|
+
Component: ({ propertyKey, internalValue, updateValue, disabled, size, property, selected, open, onOpenChange }) => {
|
|
5481
5572
|
return /* @__PURE__ */ jsx(TableReferenceField, {
|
|
5482
5573
|
name: propertyKey,
|
|
5574
|
+
selected,
|
|
5575
|
+
open,
|
|
5576
|
+
onOpenChange,
|
|
5483
5577
|
internalValue,
|
|
5484
5578
|
updateValue,
|
|
5485
5579
|
disabled,
|
|
@@ -5489,20 +5583,22 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5489
5583
|
previewProperties: property.admin?.previewProperties,
|
|
5490
5584
|
includeId: property.admin?.includeId,
|
|
5491
5585
|
includeEntityLink: property.admin?.includeEntityLink,
|
|
5492
|
-
title: property.name ?? propertyKey,
|
|
5493
5586
|
fixedFilter: property.admin?.fixedFilter
|
|
5494
5587
|
});
|
|
5495
5588
|
},
|
|
5589
|
+
opener: "dialog",
|
|
5496
5590
|
allowScroll: false
|
|
5497
5591
|
};
|
|
5498
5592
|
} else if (property.type === "relation") {
|
|
5499
5593
|
if (property.relation) {
|
|
5500
5594
|
if (property.admin?.widget === "dialog") return {
|
|
5501
5595
|
Component: RelationDialogBindingComponent,
|
|
5596
|
+
opener: "dialog",
|
|
5502
5597
|
allowScroll: false
|
|
5503
5598
|
};
|
|
5504
5599
|
else if (selected) return {
|
|
5505
5600
|
Component: RelationSelectorBindingComponent,
|
|
5601
|
+
opener: "dropdown",
|
|
5506
5602
|
allowScroll: false
|
|
5507
5603
|
};
|
|
5508
5604
|
}
|
|
@@ -5513,7 +5609,7 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5513
5609
|
const ofProp = arrayProperty.of;
|
|
5514
5610
|
if (ofProp.type === "string" || ofProp.type === "number") {
|
|
5515
5611
|
if (selected && ofProp.enum) return {
|
|
5516
|
-
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue }) => /* @__PURE__ */ jsx(VirtualTableSelect, {
|
|
5612
|
+
Component: ({ propertyKey, disabled, selected, size, error, validationError, internalValue, updateValue, open, onOpenChange, preview, anchorRef }) => /* @__PURE__ */ jsx(VirtualTableSelect, {
|
|
5517
5613
|
name: propertyKey,
|
|
5518
5614
|
multiple: true,
|
|
5519
5615
|
disabled,
|
|
@@ -5523,17 +5619,22 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5523
5619
|
enumValues: ofProp.enum,
|
|
5524
5620
|
error: validationError ?? error,
|
|
5525
5621
|
internalValue,
|
|
5526
|
-
updateValue
|
|
5622
|
+
updateValue,
|
|
5623
|
+
open,
|
|
5624
|
+
onOpenChange,
|
|
5625
|
+
preview,
|
|
5626
|
+
anchorRef
|
|
5527
5627
|
}),
|
|
5528
|
-
|
|
5529
|
-
fullHeight: true,
|
|
5530
|
-
hideOverflow: false
|
|
5628
|
+
opener: "dropdown"
|
|
5531
5629
|
};
|
|
5532
5630
|
} else if (ofProp.type === "reference") {
|
|
5533
5631
|
const refOfProp = ofProp;
|
|
5534
5632
|
if (refOfProp.path) return {
|
|
5535
|
-
Component: ({ propertyKey, disabled, internalValue, updateValue, size }) => /* @__PURE__ */ jsx(TableReferenceField, {
|
|
5633
|
+
Component: ({ propertyKey, disabled, internalValue, updateValue, size, selected, open, onOpenChange }) => /* @__PURE__ */ jsx(TableReferenceField, {
|
|
5536
5634
|
name: propertyKey,
|
|
5635
|
+
selected,
|
|
5636
|
+
open,
|
|
5637
|
+
onOpenChange,
|
|
5537
5638
|
disabled,
|
|
5538
5639
|
internalValue,
|
|
5539
5640
|
updateValue,
|
|
@@ -5541,22 +5642,58 @@ function getTableBindingForProperty(property, selected) {
|
|
|
5541
5642
|
multiselect: true,
|
|
5542
5643
|
path: refOfProp.path,
|
|
5543
5644
|
previewProperties: refOfProp.admin?.previewProperties,
|
|
5544
|
-
title: arrayProperty.name,
|
|
5545
5645
|
fixedFilter: refOfProp.admin?.fixedFilter,
|
|
5546
5646
|
includeId: refOfProp.admin?.includeId,
|
|
5547
5647
|
includeEntityLink: refOfProp.admin?.includeEntityLink
|
|
5548
5648
|
}),
|
|
5649
|
+
opener: "dialog",
|
|
5549
5650
|
allowScroll: false
|
|
5550
5651
|
};
|
|
5551
5652
|
}
|
|
5552
5653
|
}
|
|
5553
5654
|
}
|
|
5554
5655
|
}
|
|
5656
|
+
/**
|
|
5657
|
+
* Stable component for date cells. The input is always there, at rest too, so
|
|
5658
|
+
* a date can be typed into; the cell's opener shows the picker through it.
|
|
5659
|
+
*/
|
|
5660
|
+
function DateBindingComponent({ propertyKey, error, validationError, disabled, selected, size, property, internalValue, updateValue, open, onOpenChange }) {
|
|
5661
|
+
const { locale } = useCustomizationController();
|
|
5662
|
+
const inputRef = useRef(null);
|
|
5663
|
+
useEffect(() => {
|
|
5664
|
+
if (!open) return;
|
|
5665
|
+
onOpenChange?.(false);
|
|
5666
|
+
const input = inputRef.current;
|
|
5667
|
+
if (!input) return;
|
|
5668
|
+
try {
|
|
5669
|
+
input.showPicker();
|
|
5670
|
+
} catch {
|
|
5671
|
+
input.focus();
|
|
5672
|
+
}
|
|
5673
|
+
}, [open]);
|
|
5674
|
+
return /* @__PURE__ */ jsx(VirtualTableDateField, {
|
|
5675
|
+
name: propertyKey,
|
|
5676
|
+
error: validationError ?? error,
|
|
5677
|
+
disabled,
|
|
5678
|
+
small: getPreviewSizeFrom(size) !== "medium",
|
|
5679
|
+
mode: getDatePropertyMode(property),
|
|
5680
|
+
timezone: getDatePropertyTimezone(property),
|
|
5681
|
+
focused: selected,
|
|
5682
|
+
internalValue,
|
|
5683
|
+
updateValue,
|
|
5684
|
+
locale,
|
|
5685
|
+
pickerButton: onOpenChange === void 0,
|
|
5686
|
+
inputRef
|
|
5687
|
+
});
|
|
5688
|
+
}
|
|
5555
5689
|
/** Stable component for relation fields rendered with the dialog widget */
|
|
5556
|
-
function RelationDialogBindingComponent({ propertyKey, internalValue, updateValue, disabled, size, property }) {
|
|
5690
|
+
function RelationDialogBindingComponent({ propertyKey, internalValue, updateValue, disabled, size, property, selected, open, onOpenChange }) {
|
|
5557
5691
|
const relProp = property;
|
|
5558
5692
|
return /* @__PURE__ */ jsx(TableRelationField, {
|
|
5559
5693
|
name: propertyKey,
|
|
5694
|
+
selected,
|
|
5695
|
+
open,
|
|
5696
|
+
onOpenChange,
|
|
5560
5697
|
internalValue,
|
|
5561
5698
|
updateValue,
|
|
5562
5699
|
disabled,
|
|
@@ -5566,21 +5703,24 @@ function RelationDialogBindingComponent({ propertyKey, internalValue, updateValu
|
|
|
5566
5703
|
previewProperties: relProp.admin?.previewProperties,
|
|
5567
5704
|
includeId: relProp.admin?.includeId,
|
|
5568
5705
|
includeEntityLink: relProp.admin?.includeEntityLink,
|
|
5569
|
-
title: relProp.name ?? propertyKey,
|
|
5570
5706
|
fixedFilter: relProp.admin?.fixedFilter
|
|
5571
5707
|
});
|
|
5572
5708
|
}
|
|
5573
5709
|
/** Stable component for relation fields rendered with the inline selector */
|
|
5574
|
-
function RelationSelectorBindingComponent({ propertyKey, internalValue, updateValue, disabled, property,
|
|
5710
|
+
function RelationSelectorBindingComponent({ propertyKey, internalValue, updateValue, disabled, property, selected, open, onOpenChange, preview, anchorRef }) {
|
|
5575
5711
|
const relProp = property;
|
|
5576
5712
|
return /* @__PURE__ */ jsx(TableRelationSelectorField, {
|
|
5577
|
-
size: getPreviewSizeFrom(size) === "small" ? "compact" : "medium",
|
|
5578
5713
|
name: propertyKey,
|
|
5579
5714
|
internalValue,
|
|
5580
5715
|
updateValue,
|
|
5581
5716
|
disabled,
|
|
5582
5717
|
relation: relProp.relation,
|
|
5583
|
-
fixedFilter: relProp.admin?.fixedFilter
|
|
5718
|
+
fixedFilter: relProp.admin?.fixedFilter,
|
|
5719
|
+
focused: selected,
|
|
5720
|
+
open,
|
|
5721
|
+
onOpenChange,
|
|
5722
|
+
preview,
|
|
5723
|
+
anchorRef
|
|
5584
5724
|
});
|
|
5585
5725
|
}
|
|
5586
5726
|
//#endregion
|
|
@@ -5683,23 +5823,72 @@ function getZodStringSchema({ property, parentProperty, customFieldValidator, na
|
|
|
5683
5823
|
const regExp = typeof validation.matches === "string" ? hydrateRegExp(validation.matches) : validation.matches;
|
|
5684
5824
|
if (regExp) schema = schema.refine((value) => value == null || typeof value === "string" && regExp.test(value), { message: validation.matchesMessage ?? "Invalid format" });
|
|
5685
5825
|
}
|
|
5686
|
-
if (validation.
|
|
5687
|
-
if (validation.lowercase) schema = z.preprocess((v) => typeof v === "string" ? v
|
|
5688
|
-
if (validation.uppercase) schema = z.preprocess((v) => typeof v === "string" ? v.toUpperCase() : v, schema);
|
|
5689
|
-
if (property.email) schema = schema.refine((value) => value == null || typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), { message: `${fieldLabel$1(property, name)} must be an email` });
|
|
5690
|
-
if (property.url) if (!property.storage || property.storage?.storeUrl) schema = schema.refine((value) => {
|
|
5691
|
-
if (value == null) return true;
|
|
5692
|
-
try {
|
|
5693
|
-
new URL(value);
|
|
5694
|
-
return true;
|
|
5695
|
-
} catch {
|
|
5696
|
-
return false;
|
|
5697
|
-
}
|
|
5698
|
-
}, { message: `${fieldLabel$1(property, name)} must be a url` });
|
|
5699
|
-
else console.warn(`Property ${fieldLabel$1(property, name)} has a url validation but its storage configuration is not set to store urls`);
|
|
5826
|
+
if (validation.length !== void 0) schema = schema.refine((value) => value == null || typeof value === "string" && value.length === validation.length, { message: `${fieldLabel$1(property, name)} must be exactly ${validation.length} characters long` });
|
|
5827
|
+
if (validation.trim || validation.lowercase || validation.uppercase) schema = z.preprocess((v) => typeof v === "string" ? transformString(v, validation) : v, schema);
|
|
5700
5828
|
}
|
|
5829
|
+
if (property.email) schema = schema.refine((value) => value == null || value === "" || typeof value === "string" && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value), { message: `${fieldLabel$1(property, name)} must be an email` });
|
|
5830
|
+
if (property.url) if (!property.storage || property.storage?.storeUrl) schema = schema.refine((value) => {
|
|
5831
|
+
if (value == null || value === "") return true;
|
|
5832
|
+
try {
|
|
5833
|
+
new URL(value);
|
|
5834
|
+
return true;
|
|
5835
|
+
} catch {
|
|
5836
|
+
return false;
|
|
5837
|
+
}
|
|
5838
|
+
}, { message: `${fieldLabel$1(property, name)} must be a url` });
|
|
5839
|
+
else console.warn(`Property ${fieldLabel$1(property, name)} has a url validation but its storage configuration is not set to store urls`);
|
|
5701
5840
|
return schema;
|
|
5702
5841
|
}
|
|
5842
|
+
/**
|
|
5843
|
+
* A string's declared transforms — `trim`, `lowercase`, `uppercase` — applied.
|
|
5844
|
+
* The one definition of them: validation judges its result, and
|
|
5845
|
+
* {@link applyValueTransforms} writes it.
|
|
5846
|
+
*/
|
|
5847
|
+
function transformString(value, validation) {
|
|
5848
|
+
let result = value;
|
|
5849
|
+
if (validation.trim) result = result.trim();
|
|
5850
|
+
if (validation.lowercase) result = result.toLowerCase();
|
|
5851
|
+
if (validation.uppercase) result = result.toUpperCase();
|
|
5852
|
+
return result;
|
|
5853
|
+
}
|
|
5854
|
+
/**
|
|
5855
|
+
* The values as they are to be written: every `trim`, `lowercase` and
|
|
5856
|
+
* `uppercase` a string property declares applied to its value, inside maps and
|
|
5857
|
+
* arrays too.
|
|
5858
|
+
*
|
|
5859
|
+
* They are documented as changing the value that is written, and only the
|
|
5860
|
+
* panel applies them — the server checks what it receives. Validation judges
|
|
5861
|
+
* the transformed value, so without this the form passed "my-slug" and sent
|
|
5862
|
+
* " My-Slug ", which a `matches` on the server then refused.
|
|
5863
|
+
*
|
|
5864
|
+
* Returns `values` itself when nothing changes.
|
|
5865
|
+
*/
|
|
5866
|
+
function applyValueTransforms(values, properties) {
|
|
5867
|
+
let result = values;
|
|
5868
|
+
for (const [key, property] of Object.entries(properties)) {
|
|
5869
|
+
if (!(key in values)) continue;
|
|
5870
|
+
const transformed = transformValue(property, values[key]);
|
|
5871
|
+
if (transformed !== values[key]) result = {
|
|
5872
|
+
...result,
|
|
5873
|
+
[key]: transformed
|
|
5874
|
+
};
|
|
5875
|
+
}
|
|
5876
|
+
return result;
|
|
5877
|
+
}
|
|
5878
|
+
function transformValue(property, value) {
|
|
5879
|
+
if (!property || typeof property !== "object") return value;
|
|
5880
|
+
if (property.type === "string") return typeof value === "string" && property.validation ? transformString(value, property.validation) : value;
|
|
5881
|
+
if (property.type === "map" && property.properties && isPlainObject(value)) return applyValueTransforms(value, property.properties);
|
|
5882
|
+
if (property.type === "array" && property.of && Array.isArray(value)) {
|
|
5883
|
+
const of = property.of;
|
|
5884
|
+
const items = value.map((item, index) => {
|
|
5885
|
+
const itemProperty = Array.isArray(of) ? of[index] : of;
|
|
5886
|
+
return itemProperty ? transformValue(itemProperty, item) : item;
|
|
5887
|
+
});
|
|
5888
|
+
return items.some((item, index) => item !== value[index]) ? items : value;
|
|
5889
|
+
}
|
|
5890
|
+
return value;
|
|
5891
|
+
}
|
|
5703
5892
|
function getZodNumberSchema({ property, parentProperty, customFieldValidator, name, entityId }) {
|
|
5704
5893
|
const validation = property.validation;
|
|
5705
5894
|
let schema = z.preprocess((val) => {
|
|
@@ -5712,8 +5901,8 @@ function getZodNumberSchema({ property, parentProperty, customFieldValidator, na
|
|
|
5712
5901
|
if (validation) {
|
|
5713
5902
|
if (validation.min || validation.min === 0) schema = schema.refine((value) => value == null || typeof value === "number" && value >= validation.min, { message: `${fieldLabel$1(property, name)} must be higher or equal to ${validation.min}` });
|
|
5714
5903
|
if (validation.max || validation.max === 0) schema = schema.refine((value) => value == null || typeof value === "number" && value <= validation.max, { message: `${fieldLabel$1(property, name)} must be lower or equal to ${validation.max}` });
|
|
5715
|
-
if (validation.lessThan || validation.lessThan === 0) schema = schema.refine((value) => value == null || typeof value === "number" && value < validation.lessThan, { message: `${fieldLabel$1(property, name)} must be
|
|
5716
|
-
if (validation.moreThan || validation.moreThan === 0) schema = schema.refine((value) => value == null || typeof value === "number" && value > validation.moreThan, { message: `${fieldLabel$1(property, name)} must be
|
|
5904
|
+
if (validation.lessThan || validation.lessThan === 0) schema = schema.refine((value) => value == null || typeof value === "number" && value < validation.lessThan, { message: `${fieldLabel$1(property, name)} must be lower than ${validation.lessThan}` });
|
|
5905
|
+
if (validation.moreThan || validation.moreThan === 0) schema = schema.refine((value) => value == null || typeof value === "number" && value > validation.moreThan, { message: `${fieldLabel$1(property, name)} must be higher than ${validation.moreThan}` });
|
|
5717
5906
|
if (validation.positive) schema = schema.refine((value) => value == null || typeof value === "number" && value > 0, { message: `${fieldLabel$1(property, name)} must be positive` });
|
|
5718
5907
|
if (validation.negative) schema = schema.refine((value) => value == null || typeof value === "number" && value < 0, { message: `${fieldLabel$1(property, name)} must be negative` });
|
|
5719
5908
|
if (validation.integer) schema = schema.refine((value) => value == null || typeof value === "number" && Number.isInteger(value), { message: `${fieldLabel$1(property, name)} must be an integer` });
|
|
@@ -5733,7 +5922,7 @@ function getZodDateSchema({ property, parentProperty, customFieldValidator, name
|
|
|
5733
5922
|
if (validation) {
|
|
5734
5923
|
if (validation.required) schema = schema.refine((value) => value !== void 0 && value !== null, { message: validation?.requiredMessage ? validation.requiredMessage : "Required" });
|
|
5735
5924
|
if (validation.min) schema = schema.refine((value) => value == null || value instanceof Date && value >= validation.min, { message: `${fieldLabel$1(property, name)} must be after ${validation.min}` });
|
|
5736
|
-
if (validation.max) schema = schema.refine((value) => value == null || value instanceof Date && value <= validation.max, { message: `${fieldLabel$1(property, name)} must be before ${validation.
|
|
5925
|
+
if (validation.max) schema = schema.refine((value) => value == null || value instanceof Date && value <= validation.max, { message: `${fieldLabel$1(property, name)} must be before ${validation.max}` });
|
|
5737
5926
|
}
|
|
5738
5927
|
return schema;
|
|
5739
5928
|
}
|
|
@@ -5773,30 +5962,30 @@ function hasUniqueInArrayModifier(property) {
|
|
|
5773
5962
|
function getZodArraySchema({ property, parentProperty, customFieldValidator, name, entityId }) {
|
|
5774
5963
|
let arraySchema = z.array(z.any()).nullable().optional();
|
|
5775
5964
|
if (property.of) if (Array.isArray(property.of)) {
|
|
5776
|
-
const
|
|
5777
|
-
property.of.forEach((p, index) => {
|
|
5965
|
+
const positionSchemas = property.of.map((p, index) => {
|
|
5778
5966
|
try {
|
|
5779
|
-
|
|
5967
|
+
return mapPropertyToZod({
|
|
5780
5968
|
property: p,
|
|
5781
5969
|
parentProperty: property,
|
|
5782
5970
|
entityId
|
|
5783
5971
|
});
|
|
5784
5972
|
} catch (e) {
|
|
5785
5973
|
console.error(`Error creating validation schema for array item ${index}:`, e);
|
|
5786
|
-
|
|
5974
|
+
return z.any().refine(() => false, { message: `Validation error: ${e instanceof Error ? e.message : "Unknown error"}` });
|
|
5787
5975
|
}
|
|
5788
5976
|
});
|
|
5789
|
-
arraySchema = z.array(z.any().superRefine(async (
|
|
5790
|
-
for (
|
|
5791
|
-
const result = await
|
|
5977
|
+
arraySchema = z.array(z.any()).superRefine(async (items, ctx) => {
|
|
5978
|
+
for (let index = 0; index < items.length && index < positionSchemas.length; index++) {
|
|
5979
|
+
const result = await positionSchemas[index].safeParseAsync(items[index]);
|
|
5792
5980
|
if (!result.success) result.error.issues.forEach((issue) => {
|
|
5793
5981
|
ctx.addIssue({
|
|
5794
5982
|
code: "custom",
|
|
5795
|
-
message: issue.message
|
|
5983
|
+
message: issue.message,
|
|
5984
|
+
path: [index, ...issue.path]
|
|
5796
5985
|
});
|
|
5797
5986
|
});
|
|
5798
5987
|
}
|
|
5799
|
-
})
|
|
5988
|
+
}).nullable().optional();
|
|
5800
5989
|
} else {
|
|
5801
5990
|
try {
|
|
5802
5991
|
const ofSchema = mapPropertyToZod({
|
|
@@ -5843,6 +6032,53 @@ function getZodBinarySchema({ property }) {
|
|
|
5843
6032
|
return schema;
|
|
5844
6033
|
}
|
|
5845
6034
|
//#endregion
|
|
6035
|
+
//#region src/components/CollectionTableBinding/internal/TableCellOpener.tsx
|
|
6036
|
+
/**
|
|
6037
|
+
* The one control a cell reveals to open its editor.
|
|
6038
|
+
*
|
|
6039
|
+
* A cell looks the same at rest, hovered and selected: the value, in the same
|
|
6040
|
+
* place. This is the only thing that appears — on hover, and for as long as
|
|
6041
|
+
* the cell is selected — in a slot the cell keeps for it whether it is shown
|
|
6042
|
+
* or not, so revealing it moves nothing.
|
|
6043
|
+
*
|
|
6044
|
+
* One click on it opens the editor, from rest: it selects the cell on the way.
|
|
6045
|
+
* It never takes the focus on a mouse press, because the cell selects itself
|
|
6046
|
+
* on focus and doing so first would re-render the cell under the pointer.
|
|
6047
|
+
*/
|
|
6048
|
+
function TableCellOpener({ kind, open, selected, focusOnSelect, label, onToggle }) {
|
|
6049
|
+
const ref = useRef(null);
|
|
6050
|
+
const openAtPressRef = useRef(void 0);
|
|
6051
|
+
useEffect(() => {
|
|
6052
|
+
if (selected && focusOnSelect) ref.current?.focus({ preventScroll: true });
|
|
6053
|
+
}, [selected, focusOnSelect]);
|
|
6054
|
+
const Icon = kind === "calendar" ? CalendarIcon : kind === "expand" ? Maximize2Icon : ChevronDownIcon;
|
|
6055
|
+
return /* @__PURE__ */ jsx(IconButton, {
|
|
6056
|
+
ref,
|
|
6057
|
+
size: "smallest",
|
|
6058
|
+
shape: "square",
|
|
6059
|
+
"aria-label": label,
|
|
6060
|
+
"aria-haspopup": kind === "calendar" || kind === "expand" ? "dialog" : "listbox",
|
|
6061
|
+
"aria-expanded": kind === "dropdown" ? open : void 0,
|
|
6062
|
+
tabIndex: -1,
|
|
6063
|
+
"data-table-cell-opener": kind,
|
|
6064
|
+
className: cls("w-6 !h-6 min-w-6 min-h-6 p-0 hover:scale-100", "transition-opacity duration-100", selected || open ? "opacity-100" : "opacity-0 group-hover/cell:opacity-100 focus-visible:opacity-100"),
|
|
6065
|
+
onPointerDown: () => {
|
|
6066
|
+
openAtPressRef.current = open;
|
|
6067
|
+
},
|
|
6068
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
6069
|
+
onClick: (event) => {
|
|
6070
|
+
event.stopPropagation();
|
|
6071
|
+
const wasOpen = openAtPressRef.current ?? open;
|
|
6072
|
+
openAtPressRef.current = void 0;
|
|
6073
|
+
onToggle(!wasOpen);
|
|
6074
|
+
},
|
|
6075
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
6076
|
+
size: iconSize.smallest,
|
|
6077
|
+
className: cls(kind === "dropdown" || kind === "dialog" ? "transition-transform duration-150" : "", open && (kind === "dropdown" || kind === "dialog") ? "rotate-180" : "")
|
|
6078
|
+
})
|
|
6079
|
+
});
|
|
6080
|
+
}
|
|
6081
|
+
//#endregion
|
|
5846
6082
|
//#region src/components/CollectionTableBinding/internal/EntityTableCell.tsx
|
|
5847
6083
|
var TableCellInner = ({ justifyContent, scrollable, faded, fullHeight, children }) => {
|
|
5848
6084
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -5857,9 +6093,10 @@ var TableCellInner = ({ justifyContent, scrollable, faded, fullHeight, children
|
|
|
5857
6093
|
children
|
|
5858
6094
|
});
|
|
5859
6095
|
};
|
|
5860
|
-
var EntityTableCell = React.memo(function EntityTableCell({ children, actions, size, selected, disabled, disabledTooltip, savedTimestamp, error, align, allowScroll, removePadding, fullHeight, onSelect, width, hideOverflow = true, showExpandIcon = true, sortableNodeRef, sortableStyle, sortableAttributes, isDragging, isDraggable, frozen }) {
|
|
6096
|
+
var EntityTableCell = React.memo(function EntityTableCell({ children, actions, size, selected, disabled, disabledTooltip, savedTimestamp, error, align, allowScroll, removePadding, fullHeight, opener, cellRef, onSelect, width, hideOverflow = true, showExpandIcon = true, sortableNodeRef, sortableStyle, sortableAttributes, isDragging, isDraggable, frozen }) {
|
|
5861
6097
|
const [measureRef, bounds] = useMeasure();
|
|
5862
|
-
const
|
|
6098
|
+
const internalRef = useRef(null);
|
|
6099
|
+
const ref = cellRef ?? internalRef;
|
|
5863
6100
|
const maxHeight = useMemo(() => getRowHeight(size), [size]);
|
|
5864
6101
|
const [onHover, setOnHover] = useState(false);
|
|
5865
6102
|
const [showSaved, setShowSaved] = useState(false);
|
|
@@ -5909,6 +6146,16 @@ var EntityTableCell = React.memo(function EntityTableCell({ children, actions, s
|
|
|
5909
6146
|
selected,
|
|
5910
6147
|
disabled
|
|
5911
6148
|
]);
|
|
6149
|
+
const onOpenerToggle = useCallback((open) => {
|
|
6150
|
+
if (!opener) return;
|
|
6151
|
+
const cellRect = ref.current?.getBoundingClientRect();
|
|
6152
|
+
if (open) onSelectCallback();
|
|
6153
|
+
opener.onToggle(open, cellRect);
|
|
6154
|
+
}, [
|
|
6155
|
+
opener,
|
|
6156
|
+
onSelectCallback,
|
|
6157
|
+
ref
|
|
6158
|
+
]);
|
|
5912
6159
|
const onFocus = useCallback((event) => {
|
|
5913
6160
|
event.stopPropagation();
|
|
5914
6161
|
event.preventDefault();
|
|
@@ -5924,8 +6171,24 @@ var EntityTableCell = React.memo(function EntityTableCell({ children, actions, s
|
|
|
5924
6171
|
const setOnHoverTrue = useCallback(() => setOnHover(true), []);
|
|
5925
6172
|
const setOnHoverFalse = useCallback(() => setOnHover(false), []);
|
|
5926
6173
|
const borderClass = showError ? "border-red-500" : isSelected ? "border-primary" : "border-transparent";
|
|
6174
|
+
const content = /* @__PURE__ */ jsx(RowEntityPreviewLayout, { children: /* @__PURE__ */ jsxs(ErrorBoundary, { children: [fullHeight && !faded && children, (!fullHeight || faded) && /* @__PURE__ */ jsx(TableCellInner, {
|
|
6175
|
+
fullHeight: fullHeight ?? false,
|
|
6176
|
+
justifyContent,
|
|
6177
|
+
scrollable: scrollable ?? false,
|
|
6178
|
+
faded,
|
|
6179
|
+
children: !fullHeight && /* @__PURE__ */ jsx("div", {
|
|
6180
|
+
ref: measureRef,
|
|
6181
|
+
style: {
|
|
6182
|
+
display: "flex",
|
|
6183
|
+
width: "100%",
|
|
6184
|
+
justifyContent,
|
|
6185
|
+
height: fullHeight ? "100%" : void 0
|
|
6186
|
+
},
|
|
6187
|
+
children
|
|
6188
|
+
})
|
|
6189
|
+
})] }) });
|
|
5927
6190
|
const result = /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", {
|
|
5928
|
-
className: cls("transition-colors duration-500", `flex relative h-full rounded-md p-${p} border-4`, showSaved ? "bg-primary/20 dark:bg-primary/20" : onHover && !disabled ? "bg-surface-hover" : "", hideOverflow ? "overflow-hidden" : "", isSelected && !showSaved ? "bg-surface-raised" : "", borderClass),
|
|
6191
|
+
className: cls("group/cell transition-colors duration-500", `flex relative h-full rounded-md p-${p} border-4`, showSaved ? "bg-primary/20 dark:bg-primary/20" : onHover && !disabled ? "bg-surface-hover" : "", hideOverflow ? "overflow-hidden" : "", isSelected && !showSaved ? "bg-surface-raised" : "", borderClass),
|
|
5929
6192
|
ref,
|
|
5930
6193
|
style: {
|
|
5931
6194
|
justifyContent,
|
|
@@ -5938,22 +6201,28 @@ var EntityTableCell = React.memo(function EntityTableCell({ children, actions, s
|
|
|
5938
6201
|
onMouseEnter: setOnHoverTrue,
|
|
5939
6202
|
onMouseMove: setOnHoverTrue,
|
|
5940
6203
|
onMouseLeave: setOnHoverFalse,
|
|
5941
|
-
children: [
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
scrollable: scrollable ?? false,
|
|
5945
|
-
faded,
|
|
5946
|
-
children: !fullHeight && /* @__PURE__ */ jsx("div", {
|
|
5947
|
-
ref: measureRef,
|
|
6204
|
+
children: [
|
|
6205
|
+
opener ? /* @__PURE__ */ jsx("div", {
|
|
6206
|
+
className: "flex flex-1 min-w-0 self-stretch",
|
|
5948
6207
|
style: {
|
|
5949
|
-
display: "flex",
|
|
5950
|
-
width: "100%",
|
|
5951
6208
|
justifyContent,
|
|
5952
|
-
|
|
6209
|
+
alignItems: disabled || !isOverflowing ? "center" : void 0
|
|
5953
6210
|
},
|
|
5954
|
-
children
|
|
5955
|
-
})
|
|
5956
|
-
|
|
6211
|
+
children: content
|
|
6212
|
+
}) : content,
|
|
6213
|
+
opener && /* @__PURE__ */ jsx("div", {
|
|
6214
|
+
className: "shrink-0 self-center flex items-center pl-1",
|
|
6215
|
+
children: /* @__PURE__ */ jsx(TableCellOpener, {
|
|
6216
|
+
kind: opener.kind,
|
|
6217
|
+
open: opener.open,
|
|
6218
|
+
selected: Boolean(selected),
|
|
6219
|
+
focusOnSelect: opener.focusOnSelect,
|
|
6220
|
+
label: opener.label,
|
|
6221
|
+
onToggle: onOpenerToggle
|
|
6222
|
+
})
|
|
6223
|
+
}),
|
|
6224
|
+
actions
|
|
6225
|
+
]
|
|
5957
6226
|
}) });
|
|
5958
6227
|
const { tabIndex: _tabIndex, ...sortableAttrsWithoutTabIndex } = sortableAttributes ?? {};
|
|
5959
6228
|
return sortableNodeRef ? /* @__PURE__ */ jsx("div", {
|
|
@@ -5974,7 +6243,7 @@ var EntityTableCell = React.memo(function EntityTableCell({ children, actions, s
|
|
|
5974
6243
|
children: result
|
|
5975
6244
|
}) : result;
|
|
5976
6245
|
}, (a, b) => {
|
|
5977
|
-
return a.error === b.error && a.value === b.value && a.disabled === b.disabled && a.savedTimestamp === b.savedTimestamp && a.allowScroll === b.allowScroll && a.align === b.align && a.size === b.size && a.disabledTooltip === b.disabledTooltip && a.width === b.width && a.showExpandIcon === b.showExpandIcon && a.removePadding === b.removePadding && a.fullHeight === b.fullHeight && a.selected === b.selected && a.isDragging === b.isDragging && a.isDraggable === b.isDraggable && a.frozen === b.frozen;
|
|
6246
|
+
return a.error === b.error && a.value === b.value && a.disabled === b.disabled && a.savedTimestamp === b.savedTimestamp && a.allowScroll === b.allowScroll && a.align === b.align && a.size === b.size && a.disabledTooltip === b.disabledTooltip && a.width === b.width && a.showExpandIcon === b.showExpandIcon && a.removePadding === b.removePadding && a.fullHeight === b.fullHeight && a.selected === b.selected && a.opener?.kind === b.opener?.kind && a.opener?.open === b.opener?.open && a.opener?.focusOnSelect === b.opener?.focusOnSelect && a.opener?.label === b.opener?.label && a.isDragging === b.isDragging && a.isDraggable === b.isDraggable && a.frozen === b.frozen;
|
|
5978
6247
|
});
|
|
5979
6248
|
//#endregion
|
|
5980
6249
|
//#region src/components/SelectableTable/SelectableTableContext.tsx
|
|
@@ -5984,6 +6253,7 @@ var useSelectableTableController = () => useContext(SelectableTableContext);
|
|
|
5984
6253
|
//#region src/components/CollectionTableBinding/PropertyTableCell.tsx
|
|
5985
6254
|
var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, customFieldValidator, value, property, align, width, height, path, entity, readonly, disabled: disabledProp, enablePopupIcon = true, sortableNodeRef, sortableStyle, sortableAttributes, isDragging, isDraggable, frozen }) {
|
|
5986
6255
|
const { onValueChange, size, selectionStore, select, setPopupCell } = useSelectableTableController();
|
|
6256
|
+
const { t } = useTranslation();
|
|
5987
6257
|
const dummySelectionStore = useMemo(() => createSelectionStore(), []);
|
|
5988
6258
|
const selected = useCellSelected(selectionStore || dummySelectionStore, propertyKey, entity.path, entity.id);
|
|
5989
6259
|
const [internalValue, setInternalValue] = useState(value);
|
|
@@ -6017,15 +6287,29 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6017
6287
|
onValueUpdated();
|
|
6018
6288
|
}
|
|
6019
6289
|
}, [onValueUpdated, value]);
|
|
6290
|
+
const toWrittenValue = useCallback((typed) => applyValueTransforms({ value: typed }, { value: property }).value, [property]);
|
|
6291
|
+
const [transformedWrite, setTransformedWrite] = useState();
|
|
6292
|
+
useEffect(() => {
|
|
6293
|
+
if (selected || !transformedWrite) return;
|
|
6294
|
+
if (deepEqual(toWrittenValue(internalValue), transformedWrite.value)) setInternalValue(transformedWrite.value);
|
|
6295
|
+
setTransformedWrite(void 0);
|
|
6296
|
+
}, [
|
|
6297
|
+
selected,
|
|
6298
|
+
transformedWrite,
|
|
6299
|
+
internalValue,
|
|
6300
|
+
toWrittenValue
|
|
6301
|
+
]);
|
|
6020
6302
|
const saveValues = async (value) => {
|
|
6021
|
-
|
|
6303
|
+
const writtenValue = toWrittenValue(value);
|
|
6304
|
+
if (deepEqual(writtenValue, internalValueRef.current)) return;
|
|
6022
6305
|
const result = await validation.safeParseAsync(value);
|
|
6023
6306
|
if (result.success) {
|
|
6024
6307
|
setValidationError(void 0);
|
|
6025
|
-
internalValueRef.current =
|
|
6308
|
+
internalValueRef.current = writtenValue;
|
|
6309
|
+
if (!deepEqual(writtenValue, value)) setTransformedWrite({ value: writtenValue });
|
|
6026
6310
|
if (onValueChange) try {
|
|
6027
6311
|
onValueChange({
|
|
6028
|
-
value,
|
|
6312
|
+
value: writtenValue,
|
|
6029
6313
|
propertyKey,
|
|
6030
6314
|
setError,
|
|
6031
6315
|
onValueUpdated,
|
|
@@ -6056,11 +6340,6 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6056
6340
|
setInternalValue(updatedValue);
|
|
6057
6341
|
saveValues(updatedValue);
|
|
6058
6342
|
};
|
|
6059
|
-
useClearRestoreValue({
|
|
6060
|
-
property,
|
|
6061
|
-
value: internalValue,
|
|
6062
|
-
setValue: updateValue
|
|
6063
|
-
});
|
|
6064
6343
|
const onSelect = useCallback((cellRect) => {
|
|
6065
6344
|
if (!cellRect) select(void 0);
|
|
6066
6345
|
else select({
|
|
@@ -6091,6 +6370,39 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6091
6370
|
});
|
|
6092
6371
|
};
|
|
6093
6372
|
const tableBinding = useMemo(() => getTableBindingForProperty(property, selected), [property, selected]);
|
|
6373
|
+
const editorBinding = useMemo(() => getTableBindingForProperty(property, true), [property]);
|
|
6374
|
+
const [editorOpen, setEditorOpen] = useState(false);
|
|
6375
|
+
useEffect(() => {
|
|
6376
|
+
if (!selected) setEditorOpen(false);
|
|
6377
|
+
}, [selected]);
|
|
6378
|
+
const cellRef = useRef(null);
|
|
6379
|
+
const preview = /* @__PURE__ */ jsx(PropertyPreview, {
|
|
6380
|
+
width,
|
|
6381
|
+
height,
|
|
6382
|
+
propertyKey,
|
|
6383
|
+
value: internalValue,
|
|
6384
|
+
property,
|
|
6385
|
+
size: getPreviewSizeFrom(size)
|
|
6386
|
+
});
|
|
6387
|
+
let openerKind;
|
|
6388
|
+
if (!disabled && !readOnlyProperty) {
|
|
6389
|
+
if (!customField && editorBinding) openerKind = editorBinding.opener;
|
|
6390
|
+
else if (enablePopupIcon && setPopupCell) openerKind = "expand";
|
|
6391
|
+
}
|
|
6392
|
+
const onOpenerToggleRef = useRef(() => void 0);
|
|
6393
|
+
onOpenerToggleRef.current = (open, cellRect) => {
|
|
6394
|
+
if (openerKind === "expand") {
|
|
6395
|
+
if (open) openPopup(cellRect);
|
|
6396
|
+
} else setEditorOpen(open);
|
|
6397
|
+
};
|
|
6398
|
+
const onOpenerToggle = useCallback((open, cellRect) => onOpenerToggleRef.current(open, cellRect), []);
|
|
6399
|
+
const opener = openerKind ? {
|
|
6400
|
+
kind: openerKind,
|
|
6401
|
+
open: editorOpen,
|
|
6402
|
+
focusOnSelect: openerKind !== "dropdown",
|
|
6403
|
+
label: openerKind === "expand" ? t("expand") : t("edit_name", { name: property.name ?? propertyKey }),
|
|
6404
|
+
onToggle: onOpenerToggle
|
|
6405
|
+
} : void 0;
|
|
6094
6406
|
let innerComponent;
|
|
6095
6407
|
let allowScroll = false;
|
|
6096
6408
|
let showExpandIcon = false;
|
|
@@ -6139,7 +6451,11 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6139
6451
|
align,
|
|
6140
6452
|
entity,
|
|
6141
6453
|
path,
|
|
6142
|
-
openPopup: setPopupCell ? openPopup : void 0
|
|
6454
|
+
openPopup: setPopupCell ? openPopup : void 0,
|
|
6455
|
+
open: editorOpen,
|
|
6456
|
+
onOpenChange: setEditorOpen,
|
|
6457
|
+
preview,
|
|
6458
|
+
anchorRef: cellRef
|
|
6143
6459
|
});
|
|
6144
6460
|
allowScroll = tableBinding.allowScroll ?? false;
|
|
6145
6461
|
includeActions = tableBinding.includeActions ?? true;
|
|
@@ -6150,16 +6466,8 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6150
6466
|
}
|
|
6151
6467
|
}
|
|
6152
6468
|
if (!innerComponent) {
|
|
6153
|
-
allowScroll = false;
|
|
6154
|
-
|
|
6155
|
-
innerComponent = /* @__PURE__ */ jsx(PropertyPreview, {
|
|
6156
|
-
width,
|
|
6157
|
-
height,
|
|
6158
|
-
propertyKey,
|
|
6159
|
-
value: internalValue,
|
|
6160
|
-
property,
|
|
6161
|
-
size: getPreviewSizeFrom(size)
|
|
6162
|
-
});
|
|
6469
|
+
allowScroll = false;
|
|
6470
|
+
innerComponent = preview;
|
|
6163
6471
|
}
|
|
6164
6472
|
return /* @__PURE__ */ jsx(EntityTableCell, {
|
|
6165
6473
|
size,
|
|
@@ -6177,6 +6485,8 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6177
6485
|
showExpandIcon,
|
|
6178
6486
|
value: internalValue,
|
|
6179
6487
|
hideOverflow,
|
|
6488
|
+
opener,
|
|
6489
|
+
cellRef,
|
|
6180
6490
|
sortableNodeRef,
|
|
6181
6491
|
sortableStyle,
|
|
6182
6492
|
sortableAttributes,
|
|
@@ -6194,7 +6504,7 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6194
6504
|
}, `cell_${propertyKey}_${entity.path}_${entity.id}`);
|
|
6195
6505
|
}, areEqual);
|
|
6196
6506
|
function areEqual(prevProps, nextProps) {
|
|
6197
|
-
return prevProps.height === nextProps.height && prevProps.propertyKey === nextProps.propertyKey && prevProps.align === nextProps.align && prevProps.width === nextProps.width && deepEqual(prevProps.property, nextProps.property) && deepEqual(prevProps.value, nextProps.value) && prevProps.entity.id === nextProps.entity.id && prevProps.entity.path === nextProps.entity.path && prevProps.isDragging === nextProps.isDragging && prevProps.isDraggable === nextProps.isDraggable && prevProps.frozen === nextProps.frozen;
|
|
6507
|
+
return prevProps.height === nextProps.height && prevProps.propertyKey === nextProps.propertyKey && prevProps.align === nextProps.align && prevProps.width === nextProps.width && deepEqual(prevProps.property, nextProps.property) && deepEqual(prevProps.value, nextProps.value) && prevProps.entity.id === nextProps.entity.id && prevProps.entity.path === nextProps.entity.path && (prevProps.entity === nextProps.entity || deepEqual(prevProps.entity.values, nextProps.entity.values)) && prevProps.isDragging === nextProps.isDragging && prevProps.isDraggable === nextProps.isDraggable && prevProps.frozen === nextProps.frozen;
|
|
6198
6508
|
}
|
|
6199
6509
|
//#endregion
|
|
6200
6510
|
//#region src/contexts/SideDialogsControllerContext.tsx
|
|
@@ -6308,6 +6618,20 @@ var CollectionRowActions = function CollectionRowActions({ entity, collection, p
|
|
|
6308
6618
|
const uncollapsedActions = actions.filter((a) => a.collapsed === false);
|
|
6309
6619
|
const hasDraft = (collection ? getLocalChangesBackup(collection) : false) ? getEntityFromCache(path + "/" + entity.id) : false;
|
|
6310
6620
|
const iconSize = "small";
|
|
6621
|
+
const clickProps = {
|
|
6622
|
+
view: "collection",
|
|
6623
|
+
entity,
|
|
6624
|
+
path,
|
|
6625
|
+
collection,
|
|
6626
|
+
context,
|
|
6627
|
+
sidePanelController: sidePanelCtrl,
|
|
6628
|
+
selectionController,
|
|
6629
|
+
highlightEntity,
|
|
6630
|
+
unhighlightEntity,
|
|
6631
|
+
onCollectionChange,
|
|
6632
|
+
openEntityMode: openEntityMode ?? collection?.openEntityMode
|
|
6633
|
+
};
|
|
6634
|
+
const isActionEnabled = (action) => !action.isEnabled || action.isEnabled(clickProps);
|
|
6311
6635
|
const content = /* @__PURE__ */ jsxs("div", {
|
|
6312
6636
|
className: cls("h-full flex items-center justify-center flex-col z-10", isSelected ? "bg-surface-raised" : "bg-surface-card group-hover:bg-surface-card-hover", frozen ? "sticky left-0" : ""),
|
|
6313
6637
|
onClick: useCallback((event) => {
|
|
@@ -6326,21 +6650,10 @@ var CollectionRowActions = function CollectionRowActions({ entity, collection, p
|
|
|
6326
6650
|
const isEditAction = action.key === "edit";
|
|
6327
6651
|
const tooltip = isEditAction && hasDraft ? t("unsaved_local_changes") : action.name;
|
|
6328
6652
|
let iconButton = /* @__PURE__ */ jsx(IconButton, {
|
|
6653
|
+
disabled: !isActionEnabled(action),
|
|
6329
6654
|
onClick: (event) => {
|
|
6330
6655
|
event.stopPropagation();
|
|
6331
|
-
action.onClick(
|
|
6332
|
-
view: "collection",
|
|
6333
|
-
entity,
|
|
6334
|
-
path,
|
|
6335
|
-
collection,
|
|
6336
|
-
context,
|
|
6337
|
-
sidePanelController: sidePanelCtrl,
|
|
6338
|
-
selectionController,
|
|
6339
|
-
highlightEntity,
|
|
6340
|
-
unhighlightEntity,
|
|
6341
|
-
onCollectionChange,
|
|
6342
|
-
openEntityMode: openEntityMode ?? collection?.openEntityMode
|
|
6343
|
-
});
|
|
6656
|
+
action.onClick(clickProps);
|
|
6344
6657
|
},
|
|
6345
6658
|
size: iconSize,
|
|
6346
6659
|
children: getIcon(action.icon, void 0, void 0, "smallest")
|
|
@@ -6362,21 +6675,10 @@ var CollectionRowActions = function CollectionRowActions({ entity, collection, p
|
|
|
6362
6675
|
children: /* @__PURE__ */ jsx(MoreVerticalIcon, {})
|
|
6363
6676
|
}),
|
|
6364
6677
|
children: collapsedActions.map((action, index) => /* @__PURE__ */ jsxs(MenuItem, {
|
|
6678
|
+
disabled: !isActionEnabled(action),
|
|
6365
6679
|
onClick: (e) => {
|
|
6366
6680
|
e.stopPropagation();
|
|
6367
|
-
action.onClick(
|
|
6368
|
-
view: "collection",
|
|
6369
|
-
entity,
|
|
6370
|
-
path,
|
|
6371
|
-
collection,
|
|
6372
|
-
context,
|
|
6373
|
-
sidePanelController: sidePanelCtrl,
|
|
6374
|
-
selectionController,
|
|
6375
|
-
highlightEntity,
|
|
6376
|
-
unhighlightEntity,
|
|
6377
|
-
onCollectionChange,
|
|
6378
|
-
openEntityMode: openEntityMode ?? collection?.openEntityMode
|
|
6379
|
-
});
|
|
6681
|
+
action.onClick(clickProps);
|
|
6380
6682
|
},
|
|
6381
6683
|
children: [getIcon(action.icon, void 0, void 0, "smallest"), action.name]
|
|
6382
6684
|
}, index))
|
|
@@ -6638,6 +6940,7 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
6638
6940
|
const [operation, setOperation] = useState(fieldOperation === "==" && fieldValue === null ? "is-null" : fieldOperation === "!=" && fieldValue === null ? "is-not-null" : fieldOperation);
|
|
6639
6941
|
const [internalValue, setInternalValue] = useState(containsOperations.includes(fieldOperation) ? unwrapContains(fieldValue) : fieldValue);
|
|
6640
6942
|
const isNullOperation = operation === "is-null" || operation === "is-not-null";
|
|
6943
|
+
const hasSingleValue = internalValue !== void 0 && internalValue !== null && !Array.isArray(internalValue);
|
|
6641
6944
|
if (possibleOperations.length === 0) return null;
|
|
6642
6945
|
function updateFilter(op, val) {
|
|
6643
6946
|
if (op === "is-null" || op === "is-not-null") {
|
|
@@ -6711,11 +7014,11 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
6711
7014
|
position: "item-aligned",
|
|
6712
7015
|
fullWidth: true,
|
|
6713
7016
|
disabled: isNullOperation,
|
|
6714
|
-
value:
|
|
7017
|
+
value: hasSingleValue ? String(internalValue) : "",
|
|
6715
7018
|
onValueChange: (value) => {
|
|
6716
7019
|
if (value !== "") updateFilter(operation, type === "number" ? parseInt(value) : value);
|
|
6717
7020
|
},
|
|
6718
|
-
endAdornment:
|
|
7021
|
+
endAdornment: hasSingleValue && /* @__PURE__ */ jsx(IconButton, {
|
|
6719
7022
|
onClick: (e) => updateFilter(operation, void 0),
|
|
6720
7023
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
6721
7024
|
}),
|
|
@@ -7318,7 +7621,8 @@ function DefaultFilterField({ propertyKey, property, isArray, operators, value,
|
|
|
7318
7621
|
value,
|
|
7319
7622
|
setValue,
|
|
7320
7623
|
name: propertyKey,
|
|
7321
|
-
mode: property
|
|
7624
|
+
mode: getDatePropertyMode(property),
|
|
7625
|
+
timezone: getDatePropertyTimezone(property),
|
|
7322
7626
|
isArray,
|
|
7323
7627
|
operators,
|
|
7324
7628
|
title
|
|
@@ -7391,7 +7695,10 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
7391
7695
|
}, Boolean(localSelectedCell));
|
|
7392
7696
|
useEffect(() => {
|
|
7393
7697
|
const escFunction = (event) => {
|
|
7394
|
-
if (event.key
|
|
7698
|
+
if (event.key !== "Escape") return;
|
|
7699
|
+
const target = event.target;
|
|
7700
|
+
if (target instanceof Node && target !== document.body && ref.current && !ref.current.contains(target)) return;
|
|
7701
|
+
unselect();
|
|
7395
7702
|
};
|
|
7396
7703
|
document.addEventListener("keydown", escFunction, false);
|
|
7397
7704
|
return () => {
|
|
@@ -8023,7 +8330,9 @@ function useCollectionInlineEditor({ path, collection, dataClient, context }) {
|
|
|
8023
8330
|
}, [path, dataClient]);
|
|
8024
8331
|
const onValueChange = useCallback(({ value, propertyKey, onValueUpdated, setError, data: entity }) => {
|
|
8025
8332
|
if (!entity) return;
|
|
8026
|
-
const
|
|
8333
|
+
const column = propertyKey.split(/[.[]/)[0];
|
|
8334
|
+
const updatedRow = setIn(entity.values ?? {}, propertyKey, value);
|
|
8335
|
+
const updatedValues = { [column]: getIn(updatedRow, column) };
|
|
8027
8336
|
return saveEntityWithCallbacks({
|
|
8028
8337
|
path: entity.path ?? path,
|
|
8029
8338
|
entityId: entity.id,
|
|
@@ -8099,40 +8408,25 @@ function getViewModeFromSearch(search, customKeys) {
|
|
|
8099
8408
|
return isViewMode(value, customKeys) ? value : null;
|
|
8100
8409
|
}
|
|
8101
8410
|
/**
|
|
8102
|
-
* Append the active view mode to a URL so the target route keeps rendering the
|
|
8103
|
-
* view the user was last looking at.
|
|
8104
|
-
*
|
|
8105
|
-
* Every entity/collection navigation must go through this — a missed call site
|
|
8106
|
-
* silently resets the user's view mode mid-session.
|
|
8107
|
-
*
|
|
8108
|
-
* @param url the URL to decorate. May already contain a query string and/or hash.
|
|
8109
|
-
* @param search query string to read the view mode from. Defaults to the
|
|
8110
|
-
* current browser location.
|
|
8111
|
-
*/
|
|
8112
|
-
function withViewMode(url, search) {
|
|
8113
|
-
const source = search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
8114
|
-
const viewMode = new URLSearchParams(source).get(VIEW_MODE_PARAM);
|
|
8115
|
-
if (!viewMode) return url;
|
|
8116
|
-
const hashIndex = url.indexOf("#");
|
|
8117
|
-
const hash = hashIndex >= 0 ? url.substring(hashIndex) : "";
|
|
8118
|
-
const base = hashIndex >= 0 ? url.substring(0, hashIndex) : url;
|
|
8119
|
-
if (new URLSearchParams(base.split("?")[1] ?? "").has("__view")) return url;
|
|
8120
|
-
return `${base}${base.includes("?") ? "&" : "?"}${VIEW_MODE_PARAM}=${viewMode}${hash}`;
|
|
8121
|
-
}
|
|
8122
|
-
/**
|
|
8123
8411
|
* Carry the whole of the collection list's URL state onto a navigation target.
|
|
8124
8412
|
*
|
|
8125
|
-
*
|
|
8126
|
-
*
|
|
8127
|
-
*
|
|
8128
|
-
*
|
|
8129
|
-
* unfiltered list with an empty search box,
|
|
8130
|
-
* for every record
|
|
8413
|
+
* Every navigation to the collection or one of its records goes through this —
|
|
8414
|
+
* a missed call site silently resets the list mid-session. The list reads its
|
|
8415
|
+
* state back from the URL on each navigation, so the view mode, the search
|
|
8416
|
+
* string, the filters and the sort all have to travel: a URL without them
|
|
8417
|
+
* returned an unfiltered list with an empty search box, and the search had to
|
|
8418
|
+
* be retyped for every record the user wanted to look at.
|
|
8131
8419
|
*
|
|
8132
8420
|
* Copies every param the list currently has rather than an allowlist, because
|
|
8133
8421
|
* filters are encoded under the field's own name — there is no fixed set to
|
|
8134
8422
|
* list. Params already on the target win: the caller is more specific.
|
|
8135
8423
|
*
|
|
8424
|
+
* Carries whatever `__view` holds rather than validating it. These call sites
|
|
8425
|
+
* are navigations with no collection in hand, so they cannot know whether
|
|
8426
|
+
* `map` is one of *this* collection's custom views; checking against the
|
|
8427
|
+
* built-ins here would drop every custom view on the first record click. The
|
|
8428
|
+
* binding validates on read, which is where the answer is knowable.
|
|
8429
|
+
*
|
|
8136
8430
|
* @param url the URL to decorate. May carry a query string and/or a hash.
|
|
8137
8431
|
* @param search query string to read from. Defaults to the browser location.
|
|
8138
8432
|
*/
|
|
@@ -8185,7 +8479,7 @@ function resolveOpenEntityMode({ collection, viewMode, customView }) {
|
|
|
8185
8479
|
* Navigate to a entity using either a side panel or full-screen mode.
|
|
8186
8480
|
* This is an admin-specific UI concern and lives here (not in common).
|
|
8187
8481
|
*/
|
|
8188
|
-
function navigateToEntity({ openEntityMode, collection, entityId, copy, path, selectedTab, defaultValues, sidePanelController, onClose, navigation, replace }) {
|
|
8482
|
+
function navigateToEntity({ openEntityMode, collection, entityId, copy, path, selectedTab, defaultValues, sidePanelController, onClose, onUpdate, navigation, replace }) {
|
|
8189
8483
|
if (openEntityMode === "side_panel" || openEntityMode === "dialog") sidePanelController.open({
|
|
8190
8484
|
entityId,
|
|
8191
8485
|
path,
|
|
@@ -8194,6 +8488,7 @@ function navigateToEntity({ openEntityMode, collection, entityId, copy, path, se
|
|
|
8194
8488
|
collection,
|
|
8195
8489
|
updateUrl: openEntityMode !== "dialog",
|
|
8196
8490
|
onClose,
|
|
8491
|
+
onUpdate,
|
|
8197
8492
|
defaultValues
|
|
8198
8493
|
});
|
|
8199
8494
|
else {
|
|
@@ -8209,6 +8504,14 @@ function navigateToEntity({ openEntityMode, collection, entityId, copy, path, se
|
|
|
8209
8504
|
navigation.navigate(to, hasOptions ? navigateOptions : void 0);
|
|
8210
8505
|
}
|
|
8211
8506
|
}
|
|
8507
|
+
/**
|
|
8508
|
+
* Whether `pathname` is the record at `entityPath` or somewhere below it — one
|
|
8509
|
+
* of its tabs, its `/edit`. A path segment, not a string prefix:
|
|
8510
|
+
* `/c/products/12` is another record, not a place inside `/c/products/1`.
|
|
8511
|
+
*/
|
|
8512
|
+
function isPathWithinRecord(pathname, entityPath) {
|
|
8513
|
+
return pathname === entityPath || pathname.startsWith(`${entityPath}/`);
|
|
8514
|
+
}
|
|
8212
8515
|
//#endregion
|
|
8213
8516
|
//#region src/util/entity_actions.ts
|
|
8214
8517
|
var reservedKeys = [
|
|
@@ -8759,21 +9062,234 @@ function unflattenObject(flatObj) {
|
|
|
8759
9062
|
return nestedObj;
|
|
8760
9063
|
}, {});
|
|
8761
9064
|
}
|
|
9065
|
+
/**
|
|
9066
|
+
* Read a text cell as the number, boolean or array it spells, when it spells
|
|
9067
|
+
* one exactly — so the type inference sees `12`, `true` and the export's
|
|
9068
|
+
* `["a","b"]` for what they are.
|
|
9069
|
+
*
|
|
9070
|
+
* Only a cell that is the canonical JSON of its value is read: `JSON.stringify`
|
|
9071
|
+
* of the result must give back the cell's text. That makes the read lossless,
|
|
9072
|
+
* and the import coerces each value against the property it lands in only
|
|
9073
|
+
* later — a string property turns it back into exactly the text in the file.
|
|
9074
|
+
* Anything else stays the text it was: `1.10`, `00123`, `1E3`, a twenty-digit
|
|
9075
|
+
* SKU that a double cannot hold, the word `null`, and a cell with quotes in it.
|
|
9076
|
+
* So does an object: the import flattens objects into `a.b` columns, which
|
|
9077
|
+
* would scatter a string property's JSON text into columns that do not exist.
|
|
9078
|
+
*/
|
|
9079
|
+
function parseCanonicalJsonCell(cell) {
|
|
9080
|
+
if (typeof cell !== "string") return cell;
|
|
9081
|
+
let parsed;
|
|
9082
|
+
try {
|
|
9083
|
+
parsed = JSON.parse(cell);
|
|
9084
|
+
} catch (e) {
|
|
9085
|
+
return cell;
|
|
9086
|
+
}
|
|
9087
|
+
return (typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed)) && JSON.stringify(parsed) === cell ? parsed : cell;
|
|
9088
|
+
}
|
|
8762
9089
|
function mapJsonParse(obj) {
|
|
8763
9090
|
return Object.keys(obj).reduce((acc, key) => {
|
|
8764
9091
|
if (isPrototypePollutingKey(key)) {
|
|
8765
9092
|
console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
|
|
8766
9093
|
return acc;
|
|
8767
9094
|
}
|
|
8768
|
-
|
|
8769
|
-
acc[key] = JSON.parse(obj[key]);
|
|
8770
|
-
} catch (e) {
|
|
8771
|
-
acc[key] = obj[key];
|
|
8772
|
-
}
|
|
9095
|
+
acc[key] = parseCanonicalJsonCell(obj[key]);
|
|
8773
9096
|
return acc;
|
|
8774
9097
|
}, {});
|
|
8775
9098
|
}
|
|
8776
9099
|
//#endregion
|
|
9100
|
+
//#region src/data_export/export/export.ts
|
|
9101
|
+
function downloadEntitiesExport({ data, additionalData, properties, propertiesOrder, name, flattenArrays, additionalHeaders, exportType, dateExportType }) {
|
|
9102
|
+
console.debug("Downloading export", {
|
|
9103
|
+
dataLength: data.length,
|
|
9104
|
+
properties,
|
|
9105
|
+
exportType,
|
|
9106
|
+
dateExportType
|
|
9107
|
+
});
|
|
9108
|
+
if (exportType === "csv") {
|
|
9109
|
+
const headers = getExportHeaders(properties, propertiesOrder, additionalHeaders, flattenArrays ? getArrayValuesCount(data.map((d) => d.values)) : {});
|
|
9110
|
+
const exportableData = getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType);
|
|
9111
|
+
downloadBlob([entryToCSVRow(headers.map((h) => h.label)), ...exportableData.map((entry) => entryToCSVRow(entry))], `${name}.csv`, "text/csv");
|
|
9112
|
+
} else {
|
|
9113
|
+
const exportableData = getEntityJsonExportableData(data, additionalData, properties, dateExportType);
|
|
9114
|
+
downloadBlob([JSON.stringify(exportableData, null, 2)], `${name}.json`, "application/json");
|
|
9115
|
+
}
|
|
9116
|
+
}
|
|
9117
|
+
function getEntityCSVExportableData(data, additionalData, properties, headers, dateExportType) {
|
|
9118
|
+
const mergedData = data.map((e) => ({
|
|
9119
|
+
id: e.id,
|
|
9120
|
+
...processValuesForExport(e.values, properties, "csv", dateExportType)
|
|
9121
|
+
}));
|
|
9122
|
+
if (additionalData) additionalData.forEach((additional, index) => {
|
|
9123
|
+
mergedData[index] = {
|
|
9124
|
+
...mergedData[index],
|
|
9125
|
+
...additional
|
|
9126
|
+
};
|
|
9127
|
+
});
|
|
9128
|
+
return mergedData && mergedData.map((entry) => {
|
|
9129
|
+
return headers.map((header) => getValueInPath(entry, header.key));
|
|
9130
|
+
});
|
|
9131
|
+
}
|
|
9132
|
+
function getEntityJsonExportableData(data, additionalData, properties, dateExportType) {
|
|
9133
|
+
const mergedData = data.map((e) => ({
|
|
9134
|
+
id: e.id,
|
|
9135
|
+
...processValuesForExport(e.values, properties, "json", dateExportType)
|
|
9136
|
+
}));
|
|
9137
|
+
if (additionalData) additionalData.forEach((additional, index) => {
|
|
9138
|
+
mergedData[index] = {
|
|
9139
|
+
...mergedData[index],
|
|
9140
|
+
...additional
|
|
9141
|
+
};
|
|
9142
|
+
});
|
|
9143
|
+
return mergedData;
|
|
9144
|
+
}
|
|
9145
|
+
function getExportHeaders(properties, propertiesOrder, additionalHeaders, arrayValuesCount) {
|
|
9146
|
+
const headers = [{
|
|
9147
|
+
label: "id",
|
|
9148
|
+
key: "id"
|
|
9149
|
+
}, ...(propertiesOrder ?? Object.keys(properties)).flatMap((childKey) => {
|
|
9150
|
+
const property = properties[childKey];
|
|
9151
|
+
if (!property) {
|
|
9152
|
+
console.warn("Property not found", childKey, properties);
|
|
9153
|
+
return [];
|
|
9154
|
+
}
|
|
9155
|
+
if (arrayValuesCount && arrayValuesCount[childKey] > 1) return Array.from({ length: arrayValuesCount[childKey] }, (_, i) => getHeaders(property, `${childKey}[${i}]`, "")).flat();
|
|
9156
|
+
else return getHeaders(property, childKey, "");
|
|
9157
|
+
})];
|
|
9158
|
+
if (additionalHeaders) headers.push(...additionalHeaders.map((h) => ({
|
|
9159
|
+
label: h,
|
|
9160
|
+
key: h
|
|
9161
|
+
})));
|
|
9162
|
+
return headers;
|
|
9163
|
+
}
|
|
9164
|
+
/**
|
|
9165
|
+
* Get headers for property. There could be more than one header per property
|
|
9166
|
+
* @param property
|
|
9167
|
+
* @param propertyKey
|
|
9168
|
+
* @param prefix
|
|
9169
|
+
*/
|
|
9170
|
+
function getHeaders(property, propertyKey, prefix = "") {
|
|
9171
|
+
const currentKey = prefix ? `${prefix}.${propertyKey}` : propertyKey;
|
|
9172
|
+
if (property.type === "map" && property.properties) return Object.entries(property.properties).map(([childKey, p]) => getHeaders(p, childKey, currentKey)).flat();
|
|
9173
|
+
else return [{
|
|
9174
|
+
label: currentKey,
|
|
9175
|
+
key: currentKey
|
|
9176
|
+
}];
|
|
9177
|
+
}
|
|
9178
|
+
/**
|
|
9179
|
+
* Read whatever a `date` column actually arrived as into a `Date`.
|
|
9180
|
+
*
|
|
9181
|
+
* The driver/WebSocket path revives `{ __type: "date" }` into a `Date`; the REST
|
|
9182
|
+
* path — which is the one the admin is on — leaves an ISO string, and an epoch
|
|
9183
|
+
* number is what a re-imported export carries. Returns `undefined` for anything
|
|
9184
|
+
* that is not a date, so the caller can pass the raw value through.
|
|
9185
|
+
*/
|
|
9186
|
+
function toExportableDate(inputValue) {
|
|
9187
|
+
if (inputValue instanceof Date) return Number.isNaN(inputValue.getTime()) ? void 0 : inputValue;
|
|
9188
|
+
if (typeof inputValue === "number" && Number.isFinite(inputValue)) return new Date(inputValue);
|
|
9189
|
+
if (typeof inputValue === "string" && inputValue.trim() !== "") {
|
|
9190
|
+
const parsed = new Date(inputValue);
|
|
9191
|
+
return Number.isNaN(parsed.getTime()) ? void 0 : parsed;
|
|
9192
|
+
}
|
|
9193
|
+
}
|
|
9194
|
+
/**
|
|
9195
|
+
* The id a relation value points at, for a relation property's value — an
|
|
9196
|
+
* `EntityRelation`, or the same thing still in its `{ __type: "relation" }`
|
|
9197
|
+
* wire form. `undefined` for anything else, so a bare foreign key passes
|
|
9198
|
+
* through as the id it already is.
|
|
9199
|
+
*/
|
|
9200
|
+
function relationIdForExport(value) {
|
|
9201
|
+
if (value instanceof EntityRelation) return value.id;
|
|
9202
|
+
if (typeof value === "object" && value !== null && "__type" in value && value.__type === "relation" && "id" in value && (typeof value.id === "string" || typeof value.id === "number")) return value.id;
|
|
9203
|
+
}
|
|
9204
|
+
function processValueForExport(inputValue, property, exportType, dateExportType) {
|
|
9205
|
+
let value;
|
|
9206
|
+
if (property.type === "relation") {
|
|
9207
|
+
const toId = (item) => relationIdForExport(item) ?? item;
|
|
9208
|
+
value = Array.isArray(inputValue) ? inputValue.map(toId) : toId(inputValue);
|
|
9209
|
+
} else if (property.type === "map" && property.properties) value = processValuesForExport(inputValue, property.properties, exportType, dateExportType);
|
|
9210
|
+
else if (property.type === "array") if (property.of && Array.isArray(inputValue)) if (Array.isArray(property.of)) value = property.of.map((p, i) => processValueForExport(inputValue[i], p, exportType, dateExportType));
|
|
9211
|
+
else if (property.of.type === "map") value = exportType === "csv" ? inputValue.map((e) => JSON.stringify(e)) : inputValue.map((e) => processValueForExport(e, property.of, exportType, dateExportType));
|
|
9212
|
+
else value = inputValue.map((e) => processValueForExport(e, property.of, exportType, dateExportType));
|
|
9213
|
+
else value = inputValue;
|
|
9214
|
+
else if (property.type === "reference" && inputValue && typeof inputValue === "object" && "isEntityReference" in inputValue && typeof inputValue.isEntityReference === "function" && inputValue.isEntityReference()) {
|
|
9215
|
+
const ref = inputValue ? inputValue : void 0;
|
|
9216
|
+
value = ref ? ref.fullPath : null;
|
|
9217
|
+
} else if (property.type === "date") {
|
|
9218
|
+
const date = toExportableDate(inputValue);
|
|
9219
|
+
value = date ? dateExportType === "timestamp" ? date.getTime() : date.toISOString() : inputValue;
|
|
9220
|
+
} else value = inputValue;
|
|
9221
|
+
return value;
|
|
9222
|
+
}
|
|
9223
|
+
function processValuesForExport(inputValues, properties, exportType, dateExportType) {
|
|
9224
|
+
const updatedValues = Object.entries(properties).map(([key, property]) => {
|
|
9225
|
+
const updatedValue = processValueForExport(inputValues && inputValues[key], property, exportType, dateExportType);
|
|
9226
|
+
if (updatedValue === void 0) return {};
|
|
9227
|
+
return { [key]: updatedValue };
|
|
9228
|
+
}).reduce((a, b) => ({
|
|
9229
|
+
...a,
|
|
9230
|
+
...b
|
|
9231
|
+
}), {});
|
|
9232
|
+
return {
|
|
9233
|
+
...inputValues,
|
|
9234
|
+
...updatedValues
|
|
9235
|
+
};
|
|
9236
|
+
}
|
|
9237
|
+
/**
|
|
9238
|
+
* Characters that make a spreadsheet read a cell as a formula rather than text.
|
|
9239
|
+
*
|
|
9240
|
+
* Quoting is CSV *escaping* and is not enough: Excel, LibreOffice Calc and
|
|
9241
|
+
* Sheets strip the CSV quoting at parse time and then evaluate a cell whose
|
|
9242
|
+
* first character is one of these — `=HYPERLINK(…)` exfiltrates neighbouring
|
|
9243
|
+
* cells on click, `=cmd|'…'!A0` is worse.
|
|
9244
|
+
*/
|
|
9245
|
+
var FORMULA_TRIGGERS = [
|
|
9246
|
+
"=",
|
|
9247
|
+
"+",
|
|
9248
|
+
"-",
|
|
9249
|
+
"@",
|
|
9250
|
+
" ",
|
|
9251
|
+
"\r"
|
|
9252
|
+
];
|
|
9253
|
+
/**
|
|
9254
|
+
* Neutralise a cell that a spreadsheet would otherwise evaluate, by prefixing a
|
|
9255
|
+
* single quote — the standard OWASP mitigation. The apostrophe is consumed by
|
|
9256
|
+
* the spreadsheet as "treat the rest as text", so the value reads back intact.
|
|
9257
|
+
*/
|
|
9258
|
+
function escapeCsvFormula(value) {
|
|
9259
|
+
const first = value.charAt(0);
|
|
9260
|
+
if (!FORMULA_TRIGGERS.includes(first)) return value;
|
|
9261
|
+
if ((first === "-" || first === "+") && value.trim() !== "" && Number.isFinite(Number(value))) return value;
|
|
9262
|
+
return "'" + value;
|
|
9263
|
+
}
|
|
9264
|
+
/**
|
|
9265
|
+
* The inverse of {@link escapeCsvFormula}, for a file read back in: a cell that
|
|
9266
|
+
* is an apostrophe followed by a formula trigger loses the apostrophe, as it
|
|
9267
|
+
* does when a spreadsheet reads it. Any other apostrophe is text.
|
|
9268
|
+
*/
|
|
9269
|
+
function unescapeCsvFormula(value) {
|
|
9270
|
+
return value.charAt(0) === "'" && FORMULA_TRIGGERS.includes(value.charAt(1)) ? value.slice(1) : value;
|
|
9271
|
+
}
|
|
9272
|
+
function toCSVCell(v) {
|
|
9273
|
+
if (v === null || v === void 0) return "";
|
|
9274
|
+
return "\"" + escapeCsvFormula(Array.isArray(v) || typeof v === "object" && !(v instanceof Date) ? JSON.stringify(v) : String(v)).replaceAll("\"", "\"\"") + "\"";
|
|
9275
|
+
}
|
|
9276
|
+
function entryToCSVRow(entry) {
|
|
9277
|
+
return entry.map(toCSVCell).join(",") + "\r\n";
|
|
9278
|
+
}
|
|
9279
|
+
function downloadBlob(content, filename, contentType) {
|
|
9280
|
+
const blob = new Blob(content, { type: contentType });
|
|
9281
|
+
const url = URL.createObjectURL(blob);
|
|
9282
|
+
const pom = document.createElement("a");
|
|
9283
|
+
pom.href = url;
|
|
9284
|
+
pom.setAttribute("download", filename);
|
|
9285
|
+
pom.click();
|
|
9286
|
+
}
|
|
9287
|
+
function downloadDataAsCsv(data, name) {
|
|
9288
|
+
if (data.length === 0) return;
|
|
9289
|
+
const headers = Object.keys(data[0]);
|
|
9290
|
+
downloadBlob([[headers.join(","), ...data.map((row) => headers.map((header) => toCSVCell(row[header])).join(","))].join("\r\n")], `${name}.csv`, "text/csv");
|
|
9291
|
+
}
|
|
9292
|
+
//#endregion
|
|
8777
9293
|
//#region src/data_import/utils/csv.ts
|
|
8778
9294
|
/**
|
|
8779
9295
|
* Delimiters sniffed from the header row, most common first.
|
|
@@ -8791,8 +9307,9 @@ var CANDIDATE_DELIMITERS = [
|
|
|
8791
9307
|
* Split CSV text into rows of raw cells, RFC 4180 style.
|
|
8792
9308
|
*
|
|
8793
9309
|
* Quoted fields may contain the delimiter, newlines and doubled quotes; this is
|
|
8794
|
-
* the exact inverse of
|
|
8795
|
-
*
|
|
9310
|
+
* the exact inverse of the quoting the export's `entryToCSVRow` writes, and
|
|
9311
|
+
* `parseCsvToObjects` undoes its formula escape, so a file this admin produced
|
|
9312
|
+
* reads back into the cells it was built from.
|
|
8796
9313
|
*/
|
|
8797
9314
|
function parseCsvRows(text, delimiter = ",") {
|
|
8798
9315
|
const rows = [];
|
|
@@ -8890,7 +9407,7 @@ function parseCsvToObjects(text) {
|
|
|
8890
9407
|
headers: [],
|
|
8891
9408
|
data: []
|
|
8892
9409
|
};
|
|
8893
|
-
const headers = rows[0].map((header, index) => header.trim() || `Column${index + 1}`);
|
|
9410
|
+
const headers = rows[0].map((header, index) => unescapeCsvFormula(header).trim() || `Column${index + 1}`);
|
|
8894
9411
|
return {
|
|
8895
9412
|
headers,
|
|
8896
9413
|
data: rows.slice(1).map((cells) => {
|
|
@@ -8899,7 +9416,7 @@ function parseCsvToObjects(text) {
|
|
|
8899
9416
|
if (isPrototypePollutingKey(header)) return;
|
|
8900
9417
|
const cell = cells[index];
|
|
8901
9418
|
if (cell === void 0) return;
|
|
8902
|
-
obj[header] = cell;
|
|
9419
|
+
obj[header] = unescapeCsvFormula(cell);
|
|
8903
9420
|
});
|
|
8904
9421
|
return obj;
|
|
8905
9422
|
})
|
|
@@ -9302,9 +9819,11 @@ function convertDataToEntity(authController, navigation, data, idColumn, headers
|
|
|
9302
9819
|
const flatObject = flattenEntry(data);
|
|
9303
9820
|
if (idColumn) delete flatObject[idColumn];
|
|
9304
9821
|
const mappedKeysObject = Object.entries(flatObject).map(([key, value]) => {
|
|
9305
|
-
const mappedKey =
|
|
9822
|
+
const mappedKey = Object.prototype.hasOwnProperty.call(headersMapping, key) ? headersMapping[key] : key;
|
|
9823
|
+
if (mappedKey === null) return {};
|
|
9824
|
+
if (value === "") return {};
|
|
9306
9825
|
const mappedProperty = getPropertyInPath$1(properties, mappedKey);
|
|
9307
|
-
if (!mappedProperty) return {};
|
|
9826
|
+
if (!mappedProperty || typeof mappedProperty !== "object") return {};
|
|
9308
9827
|
const processedValue = processValueMapping(authController, value, navigation, mappedProperty);
|
|
9309
9828
|
return { [mappedKey]: processedValue };
|
|
9310
9829
|
}).reduce((acc, curr) => ({
|
|
@@ -9357,19 +9876,19 @@ function processValueMapping(authController, value, navigation, property) {
|
|
|
9357
9876
|
}
|
|
9358
9877
|
return value;
|
|
9359
9878
|
}
|
|
9879
|
+
if (from === "string" && to === "array" && typeof value === "string") return processValueMapping(authController, splitListCell(value), navigation, usedProperty);
|
|
9880
|
+
else if (from === "string" && to === "boolean" && typeof value === "string") return readBooleanCell(value);
|
|
9881
|
+
else if (from === "array" && to === "string") return JSON.stringify(value);
|
|
9360
9882
|
if (from === "array" && to === "array" && Array.isArray(value) && usedProperty.of && !Array.isArray(usedProperty.of) && !isPropertyBuilder(usedProperty.of)) return value.map((v) => processValueMapping(authController, v, navigation, usedProperty.of));
|
|
9361
9883
|
else if (from === "string" && to === "number" && typeof value === "string") {
|
|
9362
9884
|
const trimmed = value.trim();
|
|
9363
9885
|
if (trimmed === "") return null;
|
|
9364
9886
|
const num = Number(trimmed);
|
|
9365
9887
|
return Number.isNaN(num) ? null : num;
|
|
9366
|
-
} else if (from === "
|
|
9367
|
-
else if (from === "string" && to === "boolean") return value === "true";
|
|
9368
|
-
else if (from === "number" && to === "boolean") return value === 1;
|
|
9888
|
+
} else if (from === "number" && to === "boolean") return value === 1;
|
|
9369
9889
|
else if (from === "boolean" && to === "number") return value ? 1 : 0;
|
|
9370
9890
|
else if (from === "boolean" && to === "string") return value ? "true" : "false";
|
|
9371
9891
|
else if (from === "number" && to === "string" && typeof value === "number") return value.toString();
|
|
9372
|
-
else if (from === "string" && to === "array" && typeof value === "string") return value.split(",").map((v) => v.trim());
|
|
9373
9892
|
else if (from === "string" && to === "date" && typeof value === "string") try {
|
|
9374
9893
|
return new Date(value);
|
|
9375
9894
|
} catch (e) {
|
|
@@ -9398,9 +9917,66 @@ function processValueMapping(authController, value, navigation, property) {
|
|
|
9398
9917
|
databaseId
|
|
9399
9918
|
});
|
|
9400
9919
|
} else if (from === to) return value;
|
|
9401
|
-
else if (from === "array" && to === "string" && Array.isArray(value)) return value.join(",");
|
|
9402
9920
|
return value;
|
|
9403
9921
|
}
|
|
9922
|
+
/**
|
|
9923
|
+
* The items of a list cell: a JSON array as written (the export writes lists
|
|
9924
|
+
* that way), otherwise the text split on commas. Each item is trimmed and an
|
|
9925
|
+
* empty one dropped, so `news, sports` is two tags rather than one with a
|
|
9926
|
+
* leading space, and a blank cell is no tags rather than one empty tag.
|
|
9927
|
+
*/
|
|
9928
|
+
function splitListCell(cell) {
|
|
9929
|
+
const trimmed = cell.trim();
|
|
9930
|
+
if (trimmed.startsWith("[")) try {
|
|
9931
|
+
const parsed = JSON.parse(trimmed);
|
|
9932
|
+
if (Array.isArray(parsed)) return parsed;
|
|
9933
|
+
} catch (e) {}
|
|
9934
|
+
return trimmed.split(",").map((item) => item.trim()).filter((item) => item !== "");
|
|
9935
|
+
}
|
|
9936
|
+
/**
|
|
9937
|
+
* A boolean cell as spreadsheets and people write it: Excel and Sheets export
|
|
9938
|
+
* `TRUE`/`FALSE`, and `yes`/`no` and `1`/`0` are common by hand. Anything
|
|
9939
|
+
* unreadable is absent — `null`, as a number cell is — rather than a confident
|
|
9940
|
+
* `false`.
|
|
9941
|
+
*/
|
|
9942
|
+
function readBooleanCell(cell) {
|
|
9943
|
+
const normalised = cell.trim().toLowerCase();
|
|
9944
|
+
if ([
|
|
9945
|
+
"true",
|
|
9946
|
+
"1",
|
|
9947
|
+
"yes"
|
|
9948
|
+
].includes(normalised)) return true;
|
|
9949
|
+
if ([
|
|
9950
|
+
"false",
|
|
9951
|
+
"0",
|
|
9952
|
+
"no"
|
|
9953
|
+
].includes(normalised)) return false;
|
|
9954
|
+
return null;
|
|
9955
|
+
}
|
|
9956
|
+
//#endregion
|
|
9957
|
+
//#region src/data_import/utils/id_column.ts
|
|
9958
|
+
/**
|
|
9959
|
+
* The column an import uses as the row id before the user touches the mapping.
|
|
9960
|
+
*
|
|
9961
|
+
* The guess decides something destructive: the chosen column is taken out of
|
|
9962
|
+
* the values and sent as the id of an upsert, so every row overwrites the record
|
|
9963
|
+
* that already has that id. A header that merely *contained* "id" or "key" used
|
|
9964
|
+
* to qualify, so a file starting with `width`, `video` or `provider` wrote its
|
|
9965
|
+
* widths over the records whose ids they happened to equal.
|
|
9966
|
+
*
|
|
9967
|
+
* Only the first column is a candidate — the one the export writes the id to —
|
|
9968
|
+
* and only when its name says it is the id: the header is `id` (in any case),
|
|
9969
|
+
* or it maps onto a property the collection declares with `isId`. Anything else
|
|
9970
|
+
* is left for the user to choose in the mapping step.
|
|
9971
|
+
*/
|
|
9972
|
+
function guessIdColumn(headers, headersMapping, properties) {
|
|
9973
|
+
const firstKey = headers[0];
|
|
9974
|
+
if (firstKey === void 0) return void 0;
|
|
9975
|
+
if (firstKey.trim().toLowerCase() === "id") return firstKey;
|
|
9976
|
+
const mappedKey = headersMapping[firstKey];
|
|
9977
|
+
const mappedProperty = mappedKey ? properties?.[mappedKey] : void 0;
|
|
9978
|
+
if (mappedProperty && "isId" in mappedProperty && mappedProperty.isId) return firstKey;
|
|
9979
|
+
}
|
|
9404
9980
|
//#endregion
|
|
9405
9981
|
//#region src/data_import/components/DataNewPropertiesMapping.tsx
|
|
9406
9982
|
function DataNewPropertiesMapping({ importConfig, destinationProperties, buildPropertyView }) {
|
|
@@ -9926,8 +10502,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
|
|
|
9926
10502
|
}
|
|
9927
10503
|
//#endregion
|
|
9928
10504
|
//#region src/components/CollectionViewBinding/CollectionViewActions.tsx
|
|
9929
|
-
var ImportCollectionAction = lazyChunk(() => import("./import-
|
|
9930
|
-
var ExportCollectionAction = lazyChunk(() => import("./export-
|
|
10505
|
+
var ImportCollectionAction = lazyChunk(() => import("./import-TCgkTVlS.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
|
|
10506
|
+
var ExportCollectionAction = lazyChunk(() => import("./export-4bJWdlDK.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
|
|
9931
10507
|
function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
|
|
9932
10508
|
const context = useAdminContext();
|
|
9933
10509
|
const { canCreate, canDelete } = usePermissions();
|
|
@@ -11467,27 +12043,32 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
11467
12043
|
className: "flex items-center justify-end gap-0.5 flex-shrink-0 ml-auto",
|
|
11468
12044
|
style: { minWidth: actionsWidth },
|
|
11469
12045
|
onClick: (e) => e.stopPropagation(),
|
|
11470
|
-
children: listViewActions.map((action, index) =>
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
11476
|
-
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11480
|
-
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
|
|
11486
|
-
|
|
11487
|
-
|
|
11488
|
-
|
|
11489
|
-
|
|
11490
|
-
|
|
12046
|
+
children: listViewActions.map((action, index) => {
|
|
12047
|
+
const clickProps = {
|
|
12048
|
+
view: "collection",
|
|
12049
|
+
entity,
|
|
12050
|
+
path,
|
|
12051
|
+
collection,
|
|
12052
|
+
context,
|
|
12053
|
+
sidePanelController: context?.sidePanelController,
|
|
12054
|
+
selectionController,
|
|
12055
|
+
openEntityMode: openEntityMode ?? collection?.openEntityMode ?? "full_screen"
|
|
12056
|
+
};
|
|
12057
|
+
const enabled = !action.isEnabled || action.isEnabled(clickProps);
|
|
12058
|
+
return /* @__PURE__ */ jsx(Tooltip, {
|
|
12059
|
+
title: action.name,
|
|
12060
|
+
asChild: true,
|
|
12061
|
+
children: /* @__PURE__ */ jsx(IconButton, {
|
|
12062
|
+
size: "small",
|
|
12063
|
+
disabled: !enabled,
|
|
12064
|
+
onClick: (e) => {
|
|
12065
|
+
e.stopPropagation();
|
|
12066
|
+
action.onClick(clickProps);
|
|
12067
|
+
},
|
|
12068
|
+
children: getIcon(action.icon, void 0, void 0, "smallest")
|
|
12069
|
+
})
|
|
12070
|
+
}, action.key ?? index);
|
|
12071
|
+
})
|
|
11491
12072
|
})
|
|
11492
12073
|
]
|
|
11493
12074
|
});
|
|
@@ -12288,7 +12869,7 @@ function SplitListShowButton({ onClick }) {
|
|
|
12288
12869
|
}
|
|
12289
12870
|
//#endregion
|
|
12290
12871
|
//#region src/components/EntityInspector.tsx
|
|
12291
|
-
var EntityHistoryView = lazyChunk(() => import("./history-
|
|
12872
|
+
var EntityHistoryView = lazyChunk(() => import("./history-BElrZ5Rw.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
12292
12873
|
/**
|
|
12293
12874
|
* The JSON tab pulls `prism-react-renderer` — 85 kB, and it was EAGER.
|
|
12294
12875
|
*
|
|
@@ -12691,7 +13272,7 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
12691
13272
|
})
|
|
12692
13273
|
}, `entity_detail_collection_tab_${view.name}`);
|
|
12693
13274
|
});
|
|
12694
|
-
const collectionUrl =
|
|
13275
|
+
const collectionUrl = withListState(urlController.buildUrlCollectionPath(path));
|
|
12695
13276
|
const backToCollection = layout === "full_screen" && !onShowList ? () => navigate(collectionUrl) : void 0;
|
|
12696
13277
|
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
12697
13278
|
title: "Open full screen",
|
|
@@ -13000,7 +13581,7 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
13000
13581
|
};
|
|
13001
13582
|
}, [selectedEntityId]);
|
|
13002
13583
|
const buildUrl = useCallback((subPath) => {
|
|
13003
|
-
return
|
|
13584
|
+
return withListState(urlController.buildUrlCollectionPath(subPath));
|
|
13004
13585
|
}, [urlController]);
|
|
13005
13586
|
const handleCloseDetail = useCallback(() => {
|
|
13006
13587
|
navigate(buildUrl(path));
|
|
@@ -13450,6 +14031,8 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13450
14031
|
[currentColumnProperty]: ["==", column]
|
|
13451
14032
|
};
|
|
13452
14033
|
const orderByParam = currentOrderProperty ? [currentOrderProperty, "asc"] : void 0;
|
|
14034
|
+
let active = true;
|
|
14035
|
+
const isCurrent = () => active && !isCleaningUpRef.current;
|
|
13453
14036
|
setColumnData((prev) => ({
|
|
13454
14037
|
...prev,
|
|
13455
14038
|
[column]: {
|
|
@@ -13459,7 +14042,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13459
14042
|
}
|
|
13460
14043
|
}));
|
|
13461
14044
|
const onUpdate = async (entities) => {
|
|
13462
|
-
if (
|
|
14045
|
+
if (!isCurrent()) return;
|
|
13463
14046
|
const pendingMap = pendingItemsRef.current;
|
|
13464
14047
|
const mergedEntities = entities.map((e) => {
|
|
13465
14048
|
const pending = pendingMap[String(e.id)];
|
|
@@ -13518,6 +14101,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13518
14101
|
} catch (e) {
|
|
13519
14102
|
console.error("Error in afterRead callback:", e);
|
|
13520
14103
|
}
|
|
14104
|
+
if (!isCurrent()) return;
|
|
13521
14105
|
const newHasMore = entities.length >= itemCount;
|
|
13522
14106
|
setColumnData((prev) => {
|
|
13523
14107
|
const existing = prev[column];
|
|
@@ -13559,10 +14143,10 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13559
14143
|
searchString: currentSearchString
|
|
13560
14144
|
});
|
|
13561
14145
|
const onError = (error) => {
|
|
13562
|
-
if (
|
|
14146
|
+
if (!isCurrent()) return;
|
|
13563
14147
|
console.error(`Error loading column ${column}:`, error);
|
|
13564
14148
|
fetchOnce().then((res) => onUpdate(res.data)).catch(() => {
|
|
13565
|
-
if (
|
|
14149
|
+
if (!isCurrent()) return;
|
|
13566
14150
|
setColumnData((prev) => ({
|
|
13567
14151
|
...prev,
|
|
13568
14152
|
[column]: {
|
|
@@ -13588,29 +14172,35 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13588
14172
|
onUpdate(res.data);
|
|
13589
14173
|
}, onError);
|
|
13590
14174
|
const firstPaintFallback = setTimeout(() => {
|
|
13591
|
-
if (liveDataReceived ||
|
|
14175
|
+
if (liveDataReceived || !isCurrent()) return;
|
|
13592
14176
|
fetchOnce().then((res) => {
|
|
13593
|
-
if (liveDataReceived ||
|
|
14177
|
+
if (liveDataReceived || !isCurrent()) return;
|
|
13594
14178
|
onUpdate(res.data);
|
|
13595
14179
|
}).catch(() => void 0);
|
|
13596
14180
|
}, LIVE_FIRST_PAINT_TIMEOUT_MS);
|
|
13597
14181
|
unsubscribersRef.current[column] = () => {
|
|
14182
|
+
active = false;
|
|
13598
14183
|
clearTimeout(firstPaintFallback);
|
|
13599
14184
|
unsubscribe?.();
|
|
13600
14185
|
};
|
|
13601
|
-
} else
|
|
13602
|
-
|
|
13603
|
-
|
|
13604
|
-
|
|
13605
|
-
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
|
|
14186
|
+
} else {
|
|
14187
|
+
unsubscribersRef.current[column] = () => {
|
|
14188
|
+
active = false;
|
|
14189
|
+
};
|
|
14190
|
+
fetchOnce().then((res) => onUpdate(res.data)).catch((error) => {
|
|
14191
|
+
if (!isCurrent()) return;
|
|
14192
|
+
setColumnData((prev) => ({
|
|
14193
|
+
...prev,
|
|
14194
|
+
[column]: {
|
|
14195
|
+
...prev[column],
|
|
14196
|
+
entities: prev[column]?.entities ?? [],
|
|
14197
|
+
loading: false,
|
|
14198
|
+
hasMore: false,
|
|
14199
|
+
error
|
|
14200
|
+
}
|
|
14201
|
+
}));
|
|
14202
|
+
});
|
|
14203
|
+
}
|
|
13614
14204
|
}, []);
|
|
13615
14205
|
useEffect(() => {
|
|
13616
14206
|
isCleaningUpRef.current = false;
|
|
@@ -13629,6 +14219,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13629
14219
|
const currentResolvedPath = resolvedPathRef.current;
|
|
13630
14220
|
const currentColumns = columns;
|
|
13631
14221
|
const currentColumnItemCounts = columnItemCounts;
|
|
14222
|
+
let cancelled = false;
|
|
13632
14223
|
const timeoutId = setTimeout(() => {
|
|
13633
14224
|
if (isCleaningUpRef.current) return;
|
|
13634
14225
|
currentColumns.forEach((column) => {
|
|
@@ -13645,7 +14236,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13645
14236
|
where: whereFilter,
|
|
13646
14237
|
searchString: currentSearchString
|
|
13647
14238
|
}).then((count) => {
|
|
13648
|
-
if (isCleaningUpRef.current) return;
|
|
14239
|
+
if (cancelled || isCleaningUpRef.current) return;
|
|
13649
14240
|
setColumnData((prev) => ({
|
|
13650
14241
|
...prev,
|
|
13651
14242
|
[column]: {
|
|
@@ -13661,6 +14252,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13661
14252
|
prevColumnItemCountsRef.current = { ...currentColumnItemCounts };
|
|
13662
14253
|
}, 0);
|
|
13663
14254
|
return () => {
|
|
14255
|
+
cancelled = true;
|
|
13664
14256
|
clearTimeout(timeoutId);
|
|
13665
14257
|
isCleaningUpRef.current = true;
|
|
13666
14258
|
const unsubscribers = { ...unsubscribersRef.current };
|
|
@@ -13713,19 +14305,54 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13713
14305
|
[column]: (prev[column] ?? pageSize) + pageSize
|
|
13714
14306
|
}));
|
|
13715
14307
|
}, [pageSize]);
|
|
14308
|
+
/**
|
|
14309
|
+
* Drop the optimistic overlay for the moves matching `predicate`.
|
|
14310
|
+
*
|
|
14311
|
+
* An entry is otherwise released only when the database "catches up" to
|
|
14312
|
+
* it, which a refused write never does: the card would stay where it was
|
|
14313
|
+
* dropped, on top of whatever the database re-delivers. A refresh is a
|
|
14314
|
+
* request for what the database says, so it lets go of them.
|
|
14315
|
+
*/
|
|
14316
|
+
const dropPendingMoves = useCallback((predicate) => {
|
|
14317
|
+
const pendingMap = pendingItemsRef.current;
|
|
14318
|
+
for (const [id, pending] of Object.entries(pendingMap)) if (predicate(pending.expectedValues)) delete pendingMap[id];
|
|
14319
|
+
}, []);
|
|
13716
14320
|
const refreshColumn = useCallback((column) => {
|
|
14321
|
+
dropPendingMoves((expected) => expected[columnPropertyRef.current] === column);
|
|
13717
14322
|
setColumnItemCounts((prev) => ({
|
|
13718
14323
|
...prev,
|
|
13719
14324
|
[column]: pageSize
|
|
13720
14325
|
}));
|
|
13721
|
-
|
|
14326
|
+
prevColumnItemCountsRef.current = {
|
|
14327
|
+
...prevColumnItemCountsRef.current,
|
|
14328
|
+
[column]: pageSize
|
|
14329
|
+
};
|
|
14330
|
+
const unsubscribe = unsubscribersRef.current[column];
|
|
14331
|
+
delete unsubscribersRef.current[column];
|
|
14332
|
+
try {
|
|
14333
|
+
unsubscribe?.();
|
|
14334
|
+
} catch (e) {}
|
|
14335
|
+
setTimeout(() => {
|
|
14336
|
+
if (!isCleaningUpRef.current) subscribeToColumn(column, pageSize);
|
|
14337
|
+
}, 0);
|
|
14338
|
+
}, [
|
|
14339
|
+
pageSize,
|
|
14340
|
+
subscribeToColumn,
|
|
14341
|
+
dropPendingMoves
|
|
14342
|
+
]);
|
|
13722
14343
|
const refreshAll = useCallback(() => {
|
|
14344
|
+
dropPendingMoves(() => true);
|
|
13723
14345
|
const reset = {};
|
|
13724
14346
|
columns.forEach((col) => {
|
|
13725
14347
|
reset[col] = pageSize;
|
|
13726
14348
|
});
|
|
13727
14349
|
setColumnItemCounts(reset);
|
|
13728
|
-
|
|
14350
|
+
setSubscriptionVersion((v) => v + 1);
|
|
14351
|
+
}, [
|
|
14352
|
+
columns,
|
|
14353
|
+
pageSize,
|
|
14354
|
+
dropPendingMoves
|
|
14355
|
+
]);
|
|
13729
14356
|
const moveItemOptimistically = useCallback((itemId, sourceColumn, targetColumn, newValues, newIndex) => {
|
|
13730
14357
|
setColumnData((prev) => {
|
|
13731
14358
|
const updated = { ...prev };
|
|
@@ -13863,6 +14490,8 @@ function isValidOrderKey(value) {
|
|
|
13863
14490
|
}
|
|
13864
14491
|
}
|
|
13865
14492
|
function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
|
|
14493
|
+
const snackbarController = useSnackbarController();
|
|
14494
|
+
const { t } = useTranslation();
|
|
13866
14495
|
const handleItemsReorder = useCallback(async (items, moveInfo) => {
|
|
13867
14496
|
const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
|
|
13868
14497
|
if (!entity || !moveInfo) return;
|
|
@@ -13914,16 +14543,16 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
13914
14543
|
...saveProps,
|
|
13915
14544
|
collection,
|
|
13916
14545
|
data: dataClient,
|
|
13917
|
-
context
|
|
13918
|
-
afterSave: () => {},
|
|
13919
|
-
afterSaveError: (e) => {
|
|
13920
|
-
console.error("Failed to save entity after reorder:", e);
|
|
13921
|
-
if (boardDataController.refreshAll) boardDataController.refreshAll();
|
|
13922
|
-
}
|
|
14546
|
+
context
|
|
13923
14547
|
});
|
|
13924
14548
|
} catch (e) {
|
|
13925
|
-
console.error("
|
|
13926
|
-
|
|
14549
|
+
console.error("Failed to save entity after reorder:", e);
|
|
14550
|
+
snackbarController.open({
|
|
14551
|
+
type: "error",
|
|
14552
|
+
title: t("error_saving_entity"),
|
|
14553
|
+
message: e instanceof Error ? e.message : String(e)
|
|
14554
|
+
});
|
|
14555
|
+
boardDataController.refreshAll();
|
|
13927
14556
|
}
|
|
13928
14557
|
}, [
|
|
13929
14558
|
collection,
|
|
@@ -13933,7 +14562,9 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
13933
14562
|
dataClient,
|
|
13934
14563
|
boardDataController,
|
|
13935
14564
|
analyticsController,
|
|
13936
|
-
fullPath
|
|
14565
|
+
fullPath,
|
|
14566
|
+
snackbarController,
|
|
14567
|
+
t
|
|
13937
14568
|
]);
|
|
13938
14569
|
return useMemo(() => ({ handleItemsReorder }), [handleItemsReorder]);
|
|
13939
14570
|
}
|
|
@@ -14749,7 +15380,9 @@ function DeleteEntityDialog({ target, collection, onClose, open, onEntityDelete,
|
|
|
14749
15380
|
maxWidth: singleEntity ? "2xl" : "lg",
|
|
14750
15381
|
"aria-labelledby": "delete-dialog",
|
|
14751
15382
|
open,
|
|
14752
|
-
onOpenChange: (open) =>
|
|
15383
|
+
onOpenChange: (open) => {
|
|
15384
|
+
if (!open) handleCancel();
|
|
15385
|
+
},
|
|
14753
15386
|
children: [
|
|
14754
15387
|
/* @__PURE__ */ jsx(DialogTitle, {
|
|
14755
15388
|
id: "delete-dialog-title",
|
|
@@ -14975,7 +15608,7 @@ var copyEntityAction = {
|
|
|
14975
15608
|
name: "Copy",
|
|
14976
15609
|
key: "copy",
|
|
14977
15610
|
isEnabled: ({ entity }) => Boolean(entity),
|
|
14978
|
-
onClick({ entity, collection, context, path, sidePanelController, highlightEntity, unhighlightEntity, openEntityMode }) {
|
|
15611
|
+
onClick({ entity, collection, context, path, sidePanelController, highlightEntity, unhighlightEntity, onCollectionChange, openEntityMode }) {
|
|
14979
15612
|
if (!entity) throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
|
|
14980
15613
|
if (!sidePanelController) throw new Error("INTERNAL: copyEntityAction: sidePanelController is undefined");
|
|
14981
15614
|
highlightEntity?.(entity);
|
|
@@ -14993,6 +15626,7 @@ var copyEntityAction = {
|
|
|
14993
15626
|
copy: true,
|
|
14994
15627
|
sidePanelController,
|
|
14995
15628
|
onClose: () => unhighlightEntity?.(entity),
|
|
15629
|
+
onUpdate: onCollectionChange,
|
|
14996
15630
|
navigation: context.urlController
|
|
14997
15631
|
});
|
|
14998
15632
|
return Promise.resolve(void 0);
|
|
@@ -15332,6 +15966,29 @@ function useWindowSize() {
|
|
|
15332
15966
|
}
|
|
15333
15967
|
//#endregion
|
|
15334
15968
|
//#region src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx
|
|
15969
|
+
/**
|
|
15970
|
+
* The properties that judge one cell: the property at `propertyKey`, under the
|
|
15971
|
+
* maps its key goes through, and nothing beside it.
|
|
15972
|
+
*
|
|
15973
|
+
* A spread map's child is `address.street`, a path into `properties` rather
|
|
15974
|
+
* than a key of it; looked up as a key it found nothing, and the popup
|
|
15975
|
+
* validated nothing. The maps on the way carry only the edited child and none
|
|
15976
|
+
* of their own rules: the popup shows one field, and an error anywhere else
|
|
15977
|
+
* would block the save where no one could see it.
|
|
15978
|
+
*/
|
|
15979
|
+
function propertiesOfCell(properties, propertyKey) {
|
|
15980
|
+
if (propertyKey in properties) return { [propertyKey]: properties[propertyKey] };
|
|
15981
|
+
const dot = propertyKey.indexOf(".");
|
|
15982
|
+
if (dot === -1) return {};
|
|
15983
|
+
const mapKey = propertyKey.slice(0, dot);
|
|
15984
|
+
const map = properties[mapKey];
|
|
15985
|
+
if (map?.type !== "map" || !map.properties) return {};
|
|
15986
|
+
const children = propertiesOfCell(map.properties, propertyKey.slice(dot + 1));
|
|
15987
|
+
return Object.keys(children).length > 0 ? { [mapKey]: {
|
|
15988
|
+
type: "map",
|
|
15989
|
+
properties: children
|
|
15990
|
+
} } : {};
|
|
15991
|
+
}
|
|
15335
15992
|
function PopupFormField(props) {
|
|
15336
15993
|
if (!props.open) return null;
|
|
15337
15994
|
return /* @__PURE__ */ jsx(PopupFormFieldLoading, { ...props });
|
|
@@ -15431,7 +16088,7 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
15431
16088
|
]);
|
|
15432
16089
|
const validationSchema = useMemo(() => {
|
|
15433
16090
|
if (!collection || !entityId) return;
|
|
15434
|
-
return getEntitySchema(entityId, propertyKey
|
|
16091
|
+
return getEntitySchema(entityId, propertyKey ? propertiesOfCell(collection.properties, propertyKey) : {}, customFieldValidator);
|
|
15435
16092
|
}, [
|
|
15436
16093
|
collection,
|
|
15437
16094
|
entityId,
|
|
@@ -15456,7 +16113,7 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
15456
16113
|
const saveValue = async (values) => {
|
|
15457
16114
|
setSavingError(null);
|
|
15458
16115
|
if (collection && entity && onCellValueChange && propertyKey) return onCellValueChange({
|
|
15459
|
-
value: values
|
|
16116
|
+
value: getIn(applyValueTransforms(values, collection.properties), propertyKey),
|
|
15460
16117
|
propertyKey,
|
|
15461
16118
|
data: entity,
|
|
15462
16119
|
setError: setSavingError,
|
|
@@ -16179,7 +16836,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
16179
16836
|
openNewDocument
|
|
16180
16837
|
};
|
|
16181
16838
|
const handleBackClick = useCallback(() => {
|
|
16182
|
-
navigate(
|
|
16839
|
+
navigate(withListState(urlController.buildUrlCollectionPath(path)));
|
|
16183
16840
|
}, [
|
|
16184
16841
|
navigate,
|
|
16185
16842
|
urlController,
|
|
@@ -16492,19 +17149,9 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16492
17149
|
urlController,
|
|
16493
17150
|
analyticsController
|
|
16494
17151
|
]);
|
|
16495
|
-
const
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
navigateToEntity({
|
|
16499
|
-
openEntityMode,
|
|
16500
|
-
collection,
|
|
16501
|
-
entityId: void 0,
|
|
16502
|
-
path,
|
|
16503
|
-
sidePanelController,
|
|
16504
|
-
navigation: urlController,
|
|
16505
|
-
onClose: unselectNavigatedEntity
|
|
16506
|
-
});
|
|
16507
|
-
}, [path, sidePanelController]);
|
|
17152
|
+
const onCreatedInPanel = useCallback(() => {
|
|
17153
|
+
setLastDeleteTimestamp(Date.now());
|
|
17154
|
+
}, []);
|
|
16508
17155
|
/**
|
|
16509
17156
|
* Attach rows that already exist to this parent.
|
|
16510
17157
|
*
|
|
@@ -16544,7 +17191,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16544
17191
|
const selectionDialog = useSelectionDialog({
|
|
16545
17192
|
path: linkedTargetSlug ?? false,
|
|
16546
17193
|
multiselect: true,
|
|
16547
|
-
|
|
17194
|
+
onSelectionDone: linkExistingEntities
|
|
16548
17195
|
});
|
|
16549
17196
|
const openNewDocument = useCallback((defaultValues) => {
|
|
16550
17197
|
const collection = collectionRef.current;
|
|
@@ -16557,15 +17204,18 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16557
17204
|
path,
|
|
16558
17205
|
sidePanelController,
|
|
16559
17206
|
navigation: urlController,
|
|
16560
|
-
onClose: unselectNavigatedEntity
|
|
17207
|
+
onClose: unselectNavigatedEntity,
|
|
17208
|
+
onUpdate: onCreatedInPanel
|
|
16561
17209
|
});
|
|
16562
17210
|
}, [
|
|
16563
17211
|
path,
|
|
16564
17212
|
sidePanelController,
|
|
16565
17213
|
openEntityMode,
|
|
16566
17214
|
urlController,
|
|
16567
|
-
unselectNavigatedEntity
|
|
17215
|
+
unselectNavigatedEntity,
|
|
17216
|
+
onCreatedInPanel
|
|
16568
17217
|
]);
|
|
17218
|
+
const onNewClick = useCallback(() => openNewDocument(), [openNewDocument]);
|
|
16569
17219
|
const onMultipleDeleteClick = () => {
|
|
16570
17220
|
analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", { path });
|
|
16571
17221
|
setDeleteEntityClicked(selection);
|
|
@@ -16774,7 +17424,9 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16774
17424
|
collection,
|
|
16775
17425
|
collectionWithLocalOrder,
|
|
16776
17426
|
path,
|
|
16777
|
-
sidePanelController
|
|
17427
|
+
sidePanelController,
|
|
17428
|
+
openEntityMode,
|
|
17429
|
+
urlController
|
|
16778
17430
|
]);
|
|
16779
17431
|
const updateLastDeleteTimestamp = useCallback(() => {
|
|
16780
17432
|
setLastDeleteTimestamp(Date.now());
|
|
@@ -16836,7 +17488,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16836
17488
|
filter: tableController.filterValues,
|
|
16837
17489
|
sortBy: tableController.sortBy,
|
|
16838
17490
|
searchString: tableController.searchString,
|
|
16839
|
-
onCountChange: setDocsCount
|
|
17491
|
+
onCountChange: setDocsCount,
|
|
17492
|
+
refreshKey: lastDeleteTimestamp
|
|
16840
17493
|
});
|
|
16841
17494
|
const liveSelectionQuery = useMemo(() => ({
|
|
16842
17495
|
path,
|
|
@@ -17215,7 +17868,7 @@ function DefaultCollectionEmptyState({ canCreate, onNewClick, isSearching, searc
|
|
|
17215
17868
|
* callers (e.g. React StrictMode double-mount) share the same promise.
|
|
17216
17869
|
*/
|
|
17217
17870
|
var inflightCountRequests = /* @__PURE__ */ new Map();
|
|
17218
|
-
function EntitiesCount({ path, collection, filter, sortBy, searchString, onCountChange }) {
|
|
17871
|
+
function EntitiesCount({ path, collection, filter, sortBy, searchString, onCountChange, refreshKey }) {
|
|
17219
17872
|
const dataClient = useData();
|
|
17220
17873
|
const sortKey = React.useMemo(() => sortBy ? serializeOrderBy(sortBy) ?? "" : "", [sortBy]);
|
|
17221
17874
|
const dataClientRef = React.useRef(dataClient);
|
|
@@ -17232,7 +17885,7 @@ function EntitiesCount({ path, collection, filter, sortBy, searchString, onCount
|
|
|
17232
17885
|
let cancelled = false;
|
|
17233
17886
|
const whereParams = filter && Object.keys(filter).length > 0 ? filter : void 0;
|
|
17234
17887
|
const orderByParams = sortBy && sortBy.length > 0 ? sortBy.map(([field, direction]) => [String(field), direction]) : void 0;
|
|
17235
|
-
const cacheKey = `${path}|${filterKey}|${sortKey}|${searchString ?? ""}`;
|
|
17888
|
+
const cacheKey = `${path}|${filterKey}|${sortKey}|${searchString ?? ""}|${refreshKey ?? ""}`;
|
|
17236
17889
|
let countPromise = inflightCountRequests.get(cacheKey);
|
|
17237
17890
|
if (!countPromise) {
|
|
17238
17891
|
countPromise = accessor.count({
|
|
@@ -17256,7 +17909,8 @@ function EntitiesCount({ path, collection, filter, sortBy, searchString, onCount
|
|
|
17256
17909
|
path,
|
|
17257
17910
|
filterKey,
|
|
17258
17911
|
sortKey,
|
|
17259
|
-
searchString
|
|
17912
|
+
searchString,
|
|
17913
|
+
refreshKey
|
|
17260
17914
|
]);
|
|
17261
17915
|
return null;
|
|
17262
17916
|
}
|
|
@@ -17408,7 +18062,7 @@ function getChanges(source, comparison) {
|
|
|
17408
18062
|
continue;
|
|
17409
18063
|
}
|
|
17410
18064
|
if (sourceValue.some((item, index) => !deepEqual(item, comparisonArray[index]))) changes[key] = sourceValue;
|
|
17411
|
-
} else if (
|
|
18065
|
+
} else if (isPlainObject(sourceValue) && isPlainObject(comparisonValue)) {
|
|
17412
18066
|
const nestedChanges = getChanges(sourceValue, comparisonValue);
|
|
17413
18067
|
if (Object.keys(nestedChanges).length > 0) changes[key] = nestedChanges;
|
|
17414
18068
|
} else changes[key] = sourceValue;
|
|
@@ -17416,6 +18070,24 @@ function getChanges(source, comparison) {
|
|
|
17416
18070
|
return changes;
|
|
17417
18071
|
}
|
|
17418
18072
|
/**
|
|
18073
|
+
* What an update of a stored record sends: every top-level property whose value
|
|
18074
|
+
* differs from the stored one — whole.
|
|
18075
|
+
*
|
|
18076
|
+
* Whole, because that is the unit an update replaces. It sets each property it
|
|
18077
|
+
* carries, so the value sent *is* the property's new value: a map sent as the
|
|
18078
|
+
* one key that changed would erase every key left out, a key-value map with a
|
|
18079
|
+
* key removed would arrive empty, and a geopoint with one coordinate moved is
|
|
18080
|
+
* refused for lacking the other. {@link getChanges} walks into plain objects to
|
|
18081
|
+
* find what changed inside them, which is right for comparing a draft with the
|
|
18082
|
+
* form and wrong for a write.
|
|
18083
|
+
*/
|
|
18084
|
+
function getChangedProperties(values, storedValues) {
|
|
18085
|
+
const changes = {};
|
|
18086
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(values), ...Object.keys(storedValues)]);
|
|
18087
|
+
for (const key of keys) if (!deepEqual(values[key], storedValues[key])) changes[key] = values[key];
|
|
18088
|
+
return changes;
|
|
18089
|
+
}
|
|
18090
|
+
/**
|
|
17419
18091
|
* What the local-changes backup still has to offer, given what the form is
|
|
17420
18092
|
* already showing — the answer the "unsaved local changes" banner is asking for.
|
|
17421
18093
|
* `undefined` means there is nothing left to apply, and no banner.
|
|
@@ -18051,6 +18723,18 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
18051
18723
|
path,
|
|
18052
18724
|
entity: usedEntity
|
|
18053
18725
|
});
|
|
18726
|
+
const leaveRecord = useCallback(() => {
|
|
18727
|
+
if (layout === "side_panel" || layout === "dialog") sideDialogContext.close(true);
|
|
18728
|
+
else if (onCloseRequest) onCloseRequest();
|
|
18729
|
+
else navigate(withListState(urlController.buildUrlCollectionPath(path)), { replace: true });
|
|
18730
|
+
}, [
|
|
18731
|
+
layout,
|
|
18732
|
+
sideDialogContext,
|
|
18733
|
+
onCloseRequest,
|
|
18734
|
+
navigate,
|
|
18735
|
+
urlController,
|
|
18736
|
+
path
|
|
18737
|
+
]);
|
|
18054
18738
|
const recordActionItems = usedEntity && recordActions.length ? recordActions.map((action, index) => {
|
|
18055
18739
|
const clickProps = {
|
|
18056
18740
|
view: "form",
|
|
@@ -18060,7 +18744,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
18060
18744
|
context,
|
|
18061
18745
|
sidePanelController: adminContext.sidePanelController,
|
|
18062
18746
|
openEntityMode: layout,
|
|
18063
|
-
navigateBack:
|
|
18747
|
+
navigateBack: leaveRecord,
|
|
18064
18748
|
formContext
|
|
18065
18749
|
};
|
|
18066
18750
|
return /* @__PURE__ */ jsxs(MenuItem, {
|
|
@@ -18795,8 +19479,8 @@ var useBuildSidePanel = (collectionRegistryController, urlController, navigation
|
|
|
18795
19479
|
const panelsFromUrl = buildSidePanelsFromUrl(urlController.urlPathToDataPath(location.pathname), collectionRegistryController.collections ?? [], newFlag);
|
|
18796
19480
|
for (let i = 0; i < panelsFromUrl.length; i++) {
|
|
18797
19481
|
const props = panelsFromUrl[i];
|
|
18798
|
-
if (i === 0) sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
18799
|
-
else sideDialogsController.open(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19482
|
+
if (i === 0) sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController));
|
|
19483
|
+
else sideDialogsController.open(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController));
|
|
18800
19484
|
}
|
|
18801
19485
|
}
|
|
18802
19486
|
initialised.current = true;
|
|
@@ -18810,7 +19494,7 @@ var useBuildSidePanel = (collectionRegistryController, urlController, navigation
|
|
|
18810
19494
|
const panelsFromUrl = buildSidePanelsFromUrl(urlController.urlPathToDataPath(location.pathname), collectionRegistryController.collections ?? [], false);
|
|
18811
19495
|
if (panelsFromUrl.length <= currentKeys.length) return;
|
|
18812
19496
|
const lastPanel = panelsFromUrl[panelsFromUrl.length - 1];
|
|
18813
|
-
const panelProps = propsToSidePanel(lastPanel, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19497
|
+
const panelProps = propsToSidePanel(lastPanel, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController);
|
|
18814
19498
|
const lastCurrentPanel = currentKeys.length > 0 ? currentKeys[currentKeys.length - 1] : void 0;
|
|
18815
19499
|
if (!lastCurrentPanel || lastCurrentPanel !== panelProps.key) sideDialogsController.replace(panelProps);
|
|
18816
19500
|
}
|
|
@@ -18842,25 +19526,23 @@ var useBuildSidePanel = (collectionRegistryController, urlController, navigation
|
|
|
18842
19526
|
sideDialogsController.open(propsToSidePanel({
|
|
18843
19527
|
...props,
|
|
18844
19528
|
selectedTab: props.selectedTab ?? defaultSelectedView
|
|
18845
|
-
}, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19529
|
+
}, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController));
|
|
18846
19530
|
}, [
|
|
18847
19531
|
sideDialogsController,
|
|
18848
19532
|
urlController.buildUrlCollectionPath,
|
|
18849
19533
|
urlController.resolveDatabasePathsFrom,
|
|
18850
19534
|
smallLayout,
|
|
18851
|
-
authController.user
|
|
18852
|
-
location.search
|
|
19535
|
+
authController.user
|
|
18853
19536
|
]);
|
|
18854
19537
|
const replace = useCallback((props) => {
|
|
18855
19538
|
if (props.copy && !props.entityId) throw Error("If you want to copy a entity you need to provide a entityId");
|
|
18856
|
-
sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19539
|
+
sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController));
|
|
18857
19540
|
}, [
|
|
18858
19541
|
urlController.buildUrlCollectionPath,
|
|
18859
19542
|
urlController.resolveDatabasePathsFrom,
|
|
18860
19543
|
sideDialogsController,
|
|
18861
19544
|
smallLayout,
|
|
18862
|
-
authController.user
|
|
18863
|
-
location.search
|
|
19545
|
+
authController.user
|
|
18864
19546
|
]);
|
|
18865
19547
|
return useMemo(() => ({
|
|
18866
19548
|
close,
|
|
@@ -18917,8 +19599,9 @@ function buildSidePanelsFromUrl(path, collections, newFlag) {
|
|
|
18917
19599
|
};
|
|
18918
19600
|
return sidePanel ? [sidePanel] : [];
|
|
18919
19601
|
}
|
|
18920
|
-
var propsToSidePanel = (props, buildUrlCollectionPath, resolveIdsFrom, smallLayout, customizationController, authController
|
|
19602
|
+
var propsToSidePanel = (props, buildUrlCollectionPath, resolveIdsFrom, smallLayout, customizationController, authController) => {
|
|
18921
19603
|
const collectionPath = removeInitialAndTrailingSlashes(props.path);
|
|
19604
|
+
const locationSearch = typeof window !== "undefined" ? window.location.search : "";
|
|
18922
19605
|
const shouldUpdateUrl = props.updateUrl !== false;
|
|
18923
19606
|
const urlPath = shouldUpdateUrl ? props.entityId ? buildUrlCollectionPath(`${collectionPath}/${props.entityId}${props.selectedTab ? "/" + props.selectedTab : ""}${locationSearch}#${SIDE_URL_HASH}`) : buildUrlCollectionPath(`${collectionPath}${locationSearch}#${NEW_URL_HASH}`) : void 0;
|
|
18924
19607
|
const parentUrlPath = shouldUpdateUrl ? buildUrlCollectionPath(collectionPath) : void 0;
|
|
@@ -19096,12 +19779,11 @@ function useHistory(params) {
|
|
|
19096
19779
|
refreshTrigger
|
|
19097
19780
|
]);
|
|
19098
19781
|
const loadMore = useCallback(() => {
|
|
19099
|
-
if (!isLoading && hasMore &&
|
|
19782
|
+
if (!isLoading && hasMore && entries.length < total) setOffset((prev) => prev + pageSize);
|
|
19100
19783
|
}, [
|
|
19101
19784
|
isLoading,
|
|
19102
19785
|
hasMore,
|
|
19103
19786
|
pageSize,
|
|
19104
|
-
offset,
|
|
19105
19787
|
entries.length,
|
|
19106
19788
|
total
|
|
19107
19789
|
]);
|
|
@@ -20184,7 +20866,7 @@ function SelectionTableBinding(props) {
|
|
|
20184
20866
|
collection: props.collection
|
|
20185
20867
|
});
|
|
20186
20868
|
}
|
|
20187
|
-
function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntitiesSelected, multiselect, collection, path: pathInput, selectedEntityIds: selectedEntityIdsProp, description, fixedFilter, maxSelection }) {
|
|
20869
|
+
function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntitiesSelected, onSelectionDone, multiselect, collection, path: pathInput, selectedEntityIds: selectedEntityIdsProp, description, fixedFilter, maxSelection }) {
|
|
20188
20870
|
const sideDialogContext = useSideDialogContext();
|
|
20189
20871
|
const sidePanelController = useSidePanel();
|
|
20190
20872
|
const urlController = useUrlController();
|
|
@@ -20291,8 +20973,13 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
|
|
|
20291
20973
|
};
|
|
20292
20974
|
const onDone = useCallback((event) => {
|
|
20293
20975
|
event.stopPropagation();
|
|
20976
|
+
onSelectionDone?.(selectedEntities);
|
|
20294
20977
|
sideDialogContext.close(false);
|
|
20295
|
-
}, [
|
|
20978
|
+
}, [
|
|
20979
|
+
sideDialogContext,
|
|
20980
|
+
onSelectionDone,
|
|
20981
|
+
selectedEntities
|
|
20982
|
+
]);
|
|
20296
20983
|
const displayedColumnIds = useColumnIds(collection, false);
|
|
20297
20984
|
const tableController = useDataTableController({
|
|
20298
20985
|
path,
|
|
@@ -20457,6 +21144,7 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20457
21144
|
});
|
|
20458
21145
|
const onEntryClick = (e) => {
|
|
20459
21146
|
e.preventDefault();
|
|
21147
|
+
if (disabled || isSubmitting) return;
|
|
20460
21148
|
referenceDialogController.open();
|
|
20461
21149
|
};
|
|
20462
21150
|
const buildEntry = useCallback(({ index, internalId, storedProps, storeProps }) => {
|
|
@@ -20475,7 +21163,9 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20475
21163
|
}, [
|
|
20476
21164
|
ofProperty.path,
|
|
20477
21165
|
ofProperty.admin?.previewProperties,
|
|
20478
|
-
value
|
|
21166
|
+
value,
|
|
21167
|
+
disabled,
|
|
21168
|
+
isSubmitting
|
|
20479
21169
|
]);
|
|
20480
21170
|
const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIcon, {
|
|
20481
21171
|
propertyKey,
|
|
@@ -20497,7 +21187,7 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20497
21187
|
children: [/* @__PURE__ */ jsx(ArrayContainer, {
|
|
20498
21188
|
droppableId: propertyKey,
|
|
20499
21189
|
value,
|
|
20500
|
-
disabled: isSubmitting,
|
|
21190
|
+
disabled: disabled || isSubmitting,
|
|
20501
21191
|
buildEntry,
|
|
20502
21192
|
canAddElements: false,
|
|
20503
21193
|
addLabel: property.name ? t("add_reference_to", { name: property.name }) : t("add_reference"),
|
|
@@ -20506,14 +21196,9 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20506
21196
|
}), /* @__PURE__ */ jsxs(Button, {
|
|
20507
21197
|
className: "ml-3.5 my-4 justify-center text-left",
|
|
20508
21198
|
variant: "text",
|
|
20509
|
-
disabled: isSubmitting,
|
|
21199
|
+
disabled: disabled || isSubmitting,
|
|
20510
21200
|
onClick: onEntryClick,
|
|
20511
|
-
children: [
|
|
20512
|
-
/* @__PURE__ */ jsx(PencilIcon, { size: 16 }),
|
|
20513
|
-
t("edit"),
|
|
20514
|
-
" ",
|
|
20515
|
-
property.name
|
|
20516
|
-
]
|
|
21201
|
+
children: [/* @__PURE__ */ jsx(PencilIcon, { size: 16 }), t("edit_name", { name: property.name ?? propertyKey })]
|
|
20517
21202
|
})]
|
|
20518
21203
|
})] });
|
|
20519
21204
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -20711,7 +21396,8 @@ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error,
|
|
|
20711
21396
|
size,
|
|
20712
21397
|
value: internalValue,
|
|
20713
21398
|
onChange: (dateValue) => setValue(dateValue),
|
|
20714
|
-
mode: property
|
|
21399
|
+
mode: getDatePropertyMode(property),
|
|
21400
|
+
timezone: getDatePropertyTimezone(property),
|
|
20715
21401
|
clearable: property.admin?.clearable,
|
|
20716
21402
|
locale,
|
|
20717
21403
|
error: showError,
|
|
@@ -21294,13 +21980,20 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
21294
21980
|
const internalValue = useRef(value);
|
|
21295
21981
|
useEffect(() => {
|
|
21296
21982
|
getMarkdownUtils().then(() => {
|
|
21297
|
-
canonicalRef.current = canonicalizeMarkdown(
|
|
21983
|
+
canonicalRef.current = canonicalizeMarkdown(canonicalSourceRef.current);
|
|
21298
21984
|
});
|
|
21299
21985
|
}, []);
|
|
21300
21986
|
const canonicalRef = useRef(useMemo(() => canonicalizeMarkdown(value), []));
|
|
21987
|
+
const canonicalSourceRef = useRef(value);
|
|
21301
21988
|
const onContentChange = useCallback((content) => {
|
|
21302
21989
|
const normalizedContent = normalizeMarkdown(content);
|
|
21303
|
-
if (normalizedContent === canonicalRef.current)
|
|
21990
|
+
if (normalizedContent === canonicalRef.current) {
|
|
21991
|
+
if (value !== canonicalSourceRef.current) {
|
|
21992
|
+
internalValue.current = canonicalSourceRef.current;
|
|
21993
|
+
setValue(canonicalSourceRef.current);
|
|
21994
|
+
}
|
|
21995
|
+
return;
|
|
21996
|
+
}
|
|
21304
21997
|
if (normalizedContent === normalizeMarkdown(value)) return;
|
|
21305
21998
|
internalValue.current = content;
|
|
21306
21999
|
setValue(content);
|
|
@@ -21308,6 +22001,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
21308
22001
|
useEffect(() => {
|
|
21309
22002
|
if (internalValue.current !== value) {
|
|
21310
22003
|
internalValue.current = value;
|
|
22004
|
+
canonicalSourceRef.current = value;
|
|
21311
22005
|
canonicalRef.current = canonicalizeMarkdown(value);
|
|
21312
22006
|
setFieldVersion((v) => v + 1);
|
|
21313
22007
|
}
|
|
@@ -21510,6 +22204,7 @@ function ReferenceFieldBinding(props) {
|
|
|
21510
22204
|
}
|
|
21511
22205
|
function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, hideLabel, size = "medium" }) {
|
|
21512
22206
|
if (!property.path) throw new Error("Property path is required for ReferenceFieldBinding");
|
|
22207
|
+
const { t } = useTranslation();
|
|
21513
22208
|
const refValue = value;
|
|
21514
22209
|
useClearRestoreValue({
|
|
21515
22210
|
property,
|
|
@@ -21535,6 +22230,7 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
|
|
|
21535
22230
|
});
|
|
21536
22231
|
const onEntryClick = (e) => {
|
|
21537
22232
|
e.preventDefault();
|
|
22233
|
+
if (disabled || isSubmitting) return;
|
|
21538
22234
|
referenceDialogController.open();
|
|
21539
22235
|
};
|
|
21540
22236
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -21559,12 +22255,12 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
|
|
|
21559
22255
|
className: "justify-center text-left",
|
|
21560
22256
|
children: /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
21561
22257
|
className: cls("px-6 h-12 text-sm font-medium flex items-center gap-6", disabled || isSubmitting ? "text-surface-accent-500" : "cursor-pointer text-surface-accent-700 dark:text-surface-accent-300 hover:bg-surface-hover group-hover:bg-surface-hover"),
|
|
21562
|
-
onClick: onEntryClick,
|
|
22258
|
+
onClick: disabled || isSubmitting ? void 0 : onEntryClick,
|
|
21563
22259
|
size: "medium",
|
|
21564
22260
|
children: [/* @__PURE__ */ jsx(IconForView, {
|
|
21565
22261
|
collectionOrView: collection,
|
|
21566
22262
|
className: "text-surface-300 dark:text-surface-600"
|
|
21567
|
-
}),
|
|
22263
|
+
}), t("edit_name", { name: property.name ?? propertyKey }).toUpperCase()]
|
|
21568
22264
|
})
|
|
21569
22265
|
})] }),
|
|
21570
22266
|
/* @__PURE__ */ jsx(FieldHelperText, {
|
|
@@ -22620,6 +23316,7 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22620
23316
|
});
|
|
22621
23317
|
const onEntryClick = (e) => {
|
|
22622
23318
|
e.preventDefault();
|
|
23319
|
+
if (disabled || isSubmitting) return;
|
|
22623
23320
|
referenceDialogController.open();
|
|
22624
23321
|
};
|
|
22625
23322
|
const buildEntry = useCallback(({ index, internalId, storedProps, storeProps }) => {
|
|
@@ -22637,7 +23334,9 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22637
23334
|
}, [
|
|
22638
23335
|
relation,
|
|
22639
23336
|
property.admin?.previewProperties,
|
|
22640
|
-
value
|
|
23337
|
+
value,
|
|
23338
|
+
disabled,
|
|
23339
|
+
isSubmitting
|
|
22641
23340
|
]);
|
|
22642
23341
|
const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIcon, {
|
|
22643
23342
|
propertyKey,
|
|
@@ -22659,7 +23358,7 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22659
23358
|
children: [/* @__PURE__ */ jsx(ArrayContainer, {
|
|
22660
23359
|
droppableId: propertyKey,
|
|
22661
23360
|
value,
|
|
22662
|
-
disabled: isSubmitting,
|
|
23361
|
+
disabled: disabled || isSubmitting,
|
|
22663
23362
|
buildEntry,
|
|
22664
23363
|
canAddElements: false,
|
|
22665
23364
|
addLabel: property.name ? t("add_reference_to", { name: property.name }) : t("add_reference"),
|
|
@@ -22669,14 +23368,9 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22669
23368
|
className: "ml-3.5 my-4 justify-center text-left",
|
|
22670
23369
|
variant: "text",
|
|
22671
23370
|
color: "primary",
|
|
22672
|
-
disabled: isSubmitting,
|
|
23371
|
+
disabled: disabled || isSubmitting,
|
|
22673
23372
|
onClick: onEntryClick,
|
|
22674
|
-
children: [
|
|
22675
|
-
/* @__PURE__ */ jsx(PencilIcon, { size: 16 }),
|
|
22676
|
-
t("edit"),
|
|
22677
|
-
" ",
|
|
22678
|
-
property.name
|
|
22679
|
-
]
|
|
23373
|
+
children: [/* @__PURE__ */ jsx(PencilIcon, { size: 16 }), t("edit_name", { name: property.name ?? propertyKey })]
|
|
22680
23374
|
})]
|
|
22681
23375
|
})] });
|
|
22682
23376
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ExpandablePanel, {
|
|
@@ -22749,6 +23443,7 @@ function RelationSelectorBinding({ propertyKey, value, size, error, showError, d
|
|
|
22749
23443
|
});
|
|
22750
23444
|
}
|
|
22751
23445
|
function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation }) {
|
|
23446
|
+
const { t } = useTranslation();
|
|
22752
23447
|
const normalizedValue = value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null;
|
|
22753
23448
|
const validValue = !!normalizedValue;
|
|
22754
23449
|
const collection = relation?.target();
|
|
@@ -22766,6 +23461,7 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
22766
23461
|
});
|
|
22767
23462
|
const onEntryClick = (e) => {
|
|
22768
23463
|
e.preventDefault();
|
|
23464
|
+
if (disabled || isSubmitting) return;
|
|
22769
23465
|
referenceDialogController.open();
|
|
22770
23466
|
};
|
|
22771
23467
|
const usedRelation = Array.isArray(value) ? void 0 : normalizedValue ?? void 0;
|
|
@@ -22791,12 +23487,12 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
22791
23487
|
className: "justify-center text-left",
|
|
22792
23488
|
children: /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
22793
23489
|
className: cls("px-6 h-12 text-sm font-medium flex items-center gap-6", disabled || isSubmitting ? "text-surface-accent-500" : "cursor-pointer text-surface-accent-700 dark:text-surface-accent-300 hover:bg-surface-hover group-hover:bg-surface-hover"),
|
|
22794
|
-
onClick: onEntryClick,
|
|
23490
|
+
onClick: disabled || isSubmitting ? void 0 : onEntryClick,
|
|
22795
23491
|
size: "medium",
|
|
22796
23492
|
children: [/* @__PURE__ */ jsx(IconForView, {
|
|
22797
23493
|
collectionOrView: collection,
|
|
22798
23494
|
className: "text-surface-300 dark:text-surface-600"
|
|
22799
|
-
}),
|
|
23495
|
+
}), t("edit_name", { name: property.name ?? propertyKey }).toUpperCase()]
|
|
22800
23496
|
})
|
|
22801
23497
|
})] }),
|
|
22802
23498
|
/* @__PURE__ */ jsx(FieldHelperText, {
|
|
@@ -23402,6 +24098,7 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
|
|
|
23402
24098
|
}
|
|
23403
24099
|
function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, hideLabel, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
|
|
23404
24100
|
const { plugins } = useCustomizationController();
|
|
24101
|
+
const { t } = useTranslation();
|
|
23405
24102
|
const rebaseContext = useRebaseContext();
|
|
23406
24103
|
const fieldSlotProps = React.useMemo(() => ({
|
|
23407
24104
|
propertyKey,
|
|
@@ -23472,7 +24169,7 @@ function FieldInternal({ Component, componentProps: { propertyKey, property, inc
|
|
|
23472
24169
|
underlyingValueHasChanged && !isSubmitting && /* @__PURE__ */ jsx(Typography, {
|
|
23473
24170
|
variant: "caption",
|
|
23474
24171
|
className: "ml-3.5",
|
|
23475
|
-
children: "
|
|
24172
|
+
children: t("value_updated_elsewhere")
|
|
23476
24173
|
})
|
|
23477
24174
|
] });
|
|
23478
24175
|
}
|
|
@@ -23978,7 +24675,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
23978
24675
|
Promise.resolve(onSubmitProp(valuesToBeSaved, formex)).finally(() => setIsSavingAutoSave(false));
|
|
23979
24676
|
}
|
|
23980
24677
|
}, false, 2e3);
|
|
23981
|
-
|
|
24678
|
+
/**
|
|
24679
|
+
* The properties the record changed underneath an edit of the user's own,
|
|
24680
|
+
* keyed to the value they changed to — each one shown as "updated
|
|
24681
|
+
* elsewhere" while the form still holds something else. Filled by the
|
|
24682
|
+
* effect that follows the baseline, below.
|
|
24683
|
+
*/
|
|
24684
|
+
const [underlyingChanges, setUnderlyingChanges] = useState({});
|
|
23982
24685
|
const initialEntityId = useMemo(() => {
|
|
23983
24686
|
if (status === "new" || status === "copy") return;
|
|
23984
24687
|
else return entityIdProp;
|
|
@@ -24047,6 +24750,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24047
24750
|
}), {}) : {},
|
|
24048
24751
|
onSubmit,
|
|
24049
24752
|
onReset: () => {
|
|
24753
|
+
setUnderlyingChanges({});
|
|
24050
24754
|
onResetProp?.();
|
|
24051
24755
|
onValuesModified?.(false, baseInitialValues);
|
|
24052
24756
|
},
|
|
@@ -24093,12 +24797,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24093
24797
|
};
|
|
24094
24798
|
const lastSavedValues = useRef(entity?.values);
|
|
24095
24799
|
const save = async (values) => {
|
|
24096
|
-
const
|
|
24800
|
+
const writtenValues = applyValueTransforms(values, collection.properties);
|
|
24801
|
+
const valuesToSave = status === "existing" ? getChangedProperties(writtenValues, entity?.values || {}) : writtenValues;
|
|
24097
24802
|
if (status === "existing" && Object.keys(valuesToSave).length === 0 && entity) return Promise.resolve(entity);
|
|
24098
24803
|
lastSavedValues.current = values;
|
|
24099
24804
|
if (!onSubmitProp) return;
|
|
24100
24805
|
if (autoSave ?? false) {
|
|
24101
|
-
setValuesToBeSaved(
|
|
24806
|
+
setValuesToBeSaved(valuesToSave);
|
|
24102
24807
|
return Promise.resolve();
|
|
24103
24808
|
}
|
|
24104
24809
|
return Promise.resolve(onSubmitProp(valuesToSave, formex)).then((result) => {
|
|
@@ -24155,23 +24860,45 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24155
24860
|
uniqueFieldValidator
|
|
24156
24861
|
]);
|
|
24157
24862
|
useOnAutoSave(autoSave, formex, lastSavedValues, save);
|
|
24863
|
+
/**
|
|
24864
|
+
* The record changed while the form was open: someone else saved it, and
|
|
24865
|
+
* the listener delivered the new version as a new baseline.
|
|
24866
|
+
*
|
|
24867
|
+
* An unmodified form follows it whole — formex does that. A modified one
|
|
24868
|
+
* keeps the edit, and formex keeps *everything* it holds: a property the
|
|
24869
|
+
* user never changed would still show the old value, differ from the new
|
|
24870
|
+
* stored one, and be written back by the next save, reverting the other
|
|
24871
|
+
* change without a word.
|
|
24872
|
+
*
|
|
24873
|
+
* So each top-level property that changed underneath is judged on its own,
|
|
24874
|
+
* against the baseline the form last followed. One the user has not changed
|
|
24875
|
+
* follows the record. One the user has changed keeps the edit — that is
|
|
24876
|
+
* what a save writes — and is marked as updated elsewhere, so the overwrite
|
|
24877
|
+
* is not a surprise.
|
|
24878
|
+
*/
|
|
24879
|
+
const followedBaseline = useRef(baseInitialValues);
|
|
24158
24880
|
useEffect(() => {
|
|
24159
|
-
|
|
24160
|
-
|
|
24161
|
-
|
|
24162
|
-
|
|
24163
|
-
|
|
24164
|
-
|
|
24881
|
+
const previous = followedBaseline.current ?? {};
|
|
24882
|
+
const next = baseInitialValues ?? {};
|
|
24883
|
+
followedBaseline.current = baseInitialValues;
|
|
24884
|
+
if (previous === next) return;
|
|
24885
|
+
const current = formex.values;
|
|
24886
|
+
if (deepEqual(previous, current)) return;
|
|
24887
|
+
const conflicts = {};
|
|
24888
|
+
const changedUnderneath = /* @__PURE__ */ new Set([...Object.keys(previous), ...Object.keys(next)]);
|
|
24889
|
+
for (const key of changedUnderneath) if (deepEqual(previous[key], next[key])) changedUnderneath.delete(key);
|
|
24890
|
+
else if (deepEqual(current[key], previous[key])) formex.setFieldValue(key, next[key]);
|
|
24891
|
+
else if (!deepEqual(current[key], next[key])) conflicts[key] = next[key];
|
|
24892
|
+
if (changedUnderneath.size === 0) return;
|
|
24893
|
+
setUnderlyingChanges((prev) => {
|
|
24894
|
+
const updated = { ...prev };
|
|
24895
|
+
changedUnderneath.forEach((key) => {
|
|
24896
|
+
if (key in conflicts) updated[key] = conflicts[key];
|
|
24897
|
+
else delete updated[key];
|
|
24898
|
+
});
|
|
24899
|
+
return updated;
|
|
24165
24900
|
});
|
|
24166
|
-
}, [
|
|
24167
|
-
formex.isSubmitting,
|
|
24168
|
-
autoSave,
|
|
24169
|
-
underlyingChanges,
|
|
24170
|
-
entity,
|
|
24171
|
-
formex.values,
|
|
24172
|
-
formex.touched,
|
|
24173
|
-
formex.setFieldValue
|
|
24174
|
-
]);
|
|
24901
|
+
}, [baseInitialValues]);
|
|
24175
24902
|
const formFieldKeys = getFormFieldKeys(collection);
|
|
24176
24903
|
const layout = useMemo(() => resolveFormLayout({
|
|
24177
24904
|
collection,
|
|
@@ -24248,7 +24975,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24248
24975
|
console.warn(`Property ${field.key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
|
|
24249
24976
|
return null;
|
|
24250
24977
|
}
|
|
24251
|
-
const underlyingValueHasChanged =
|
|
24978
|
+
const underlyingValueHasChanged = field.key in underlyingChanges && !deepEqual(formex.values[field.key], underlyingChanges[field.key]);
|
|
24252
24979
|
const disabled = isFieldDisabled(property);
|
|
24253
24980
|
const selfLabelling = isSelfLabellingProperty(property);
|
|
24254
24981
|
const cmsFormFieldProps = {
|
|
@@ -25658,6 +26385,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
25658
26385
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
25659
26386
|
}
|
|
25660
26387
|
//#endregion
|
|
25661
|
-
export { NAVIGATION_DEFAULT_GROUP_NAME as $,
|
|
26388
|
+
export { NAVIGATION_DEFAULT_GROUP_NAME as $, ArrayPropertyEnumPreview as $n, PropertyConfigBadge as $t, getFieldId as A, useAdminContext as An, FormRail as Ar, guessIdColumn as At, MapFieldBinding as B, ReadOnlyFieldBinding as Bn, getIconForWidget as Br, convertFileToJson as Bt, EntityFormBinding as C, serializeSelectionQuery as Cn, renderSkeletonIcon as Cr, EntityCardBinding as Ct, getDefaultFieldConfig as D, CollectionTableBinding as Dn, ImagePreview as Dr, useCollectionEditorDialogsState as Dt, DEFAULT_FIELD_CONFIGS as E, VirtualTableInput$1 as En, UrlComponentPreview as Er, ConfigControllerProvider as Et, SelectFieldBinding as F, useSideDialogsController as Fn, spanClass as Fr, useImportConfig as Ft, useSelectionDialog as G, UserPreview as Gn, isRelationProperty as Gr, downloadDataAsCsv as Gt, DateTimeFieldBinding as H, FieldHelperText as Hn, getPropertyInPath$1 as Hr, parseCsvRows as Ht, RepeatFieldBinding as I, SideDialogsControllerContext as In, PropertyKeyHint as Ir, ImportSaveInProgress as It, useBuildUrlController as J, DatePreview as Jn, escapeCsvFormula as Jt, SelectionTableBinding as K, NumberPropertyPreview as Kn, downloadEntitiesExport as Kt, ReferenceFieldBinding as L, SelectableTableContext as Ln, getBracketNotation as Lr, IMPORT_BATCH_SIZE as Lt, TextFieldBinding as M, useUrlController as Mn, FieldBlock as Mr, flattenEntry as Mt, SwitchFieldBinding as N, NavigationStateContext as Nn, LABEL_ICON_SIZE as Nr, processValueMapping as Nt, getDefaultFieldId as O, SelectableTable as On, EmptyValue as Or, ImportNewPropertyFieldPreview as Ot, StorageUploadFieldBinding as P, useNavigationStateController as Pn, isSelfLabellingProperty as Pr, getInferenceType as Pt, useResolvedCollections as Q, ArrayOfStringsPreview as Qn, ArrayContainer as Qt, MultiSelectFieldBinding as R, ArrayCustomShapedFieldBinding as Rn, getDefaultPropertiesOrder as Rr, saveImportedEntities as Rt, isSchemaChangeCancelled as S, selectionQueryToFindParams as Sn, renderSkeletonCaptionText as Sr, CollectionCardViewBinding as St, PropertyFieldBinding as T, useSelectionController as Tn, renderSkeletonText as Tr, useCollectionEditorController as Tt, BlockFieldBinding as U, ArrayOfMapsPreview as Un, getResolvedPropertyInPath as Ur, parseCsvToObjects as Ut, KeyValueFieldBinding as V, LabelWithIcon as Vn, getPropertiesWithPropertiesOrder as Vr, detectCsvDelimiter as Vt, ArrayOfReferencesFieldBinding as W, PropertyPreview as Wn, isReferenceProperty as Wr, downloadBlob as Wt, useTopLevelNavigation as X, MapPropertyPreview as Xn, getEntityJsonExportableData as Xt, useBuildNavigationStateController as Y, KeyValuePreview as Yn, getEntityCSVExportableData as Yt, useResolvedViews as Z, ArrayOneOfPreview as Zn, unescapeCsvFormula as Zt, useCollectionsConfigController as _, resolveViewMode as _n, StringPropertyPreview as _r, editEntityAction as _t, namespaceToPropertiesPath as a, resolveEntityView as an, ReferencePreview as ar, getEntityViewWidth as at, asUnavailable as b, SELECTION_PAGE_SIZE as bn, StorageThumbnailInternal as br, DetailViewBinding as bt, buildCollectionGenerationCallback as c, getCollectionBySlugWithin as cn, useSidePanel as cr, useSafeSnackbarController as ct, fromSerializableCollectionConfigs as d, isPathWithinRecord as dn, getUserLabel as dr, getInitialEntityValues as dt, SearchIconsView as en, ArrayEnumPreview as er, useBuildCollectionRegistryController as et, fromSerializableProperties as f, removeInitialAndTrailingSlashes$1 as fn, useResolvedUser as fr, removeEmptyContainers as ft, toSerializableProperty as g, resolveOpenEntityMode as gn, ArrayPropertyPreview as gr, deleteEntityAction as gt, toSerializableProperties as h, resolveCollectionPathIds$1 as hn, getEntityTitlePropertyKeyForEntity as hr, copyEntityAction as ht, namespaceToPropertiesOrderPath as i, resolveEntityAction as in, InlineEntityListPreview as ir, buildSidePanelsFromUrl as it, VectorFieldBinding as j, UrlContext as jn, RecordMeta as jr, convertDataToEntity as jt, getFieldConfig as k, CollectionRowActions as kn, FormSections as kr, DataNewPropertiesMapping as kt, validateCollectionJson as l, getCollectionPathsCombinations as ln, CollectionRegistryContext as lr, extractTouchedValues as lt, toSerializableCollectionConfig as m, removeTrailingSlash$1 as mn, getEntityTitlePropertyKey as mr, CollectionViewBinding as mt, getFullIdPath as n, useBreadcrumbsController as nn, RelationPreview as nr, resolveNavigationFrom as nt, CollectionGenerationApiError as o, mergeEntityActions as on, EntityPreviewBinding as or, useBuildSidePanel as ot, fromSerializableProperty as p, removeInitialSlash as pn, getEntityPreviewKeys as pr, zodToFormErrors as pt, SideDialogs as q, BooleanPreview as qn, entryToCSVRow as qt, idToPropertiesPath as r, BreadcrumbsProvider as rn, ArrayOfReferencesPreview as rr, useResolvedNavigationFrom as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, addInitialSlash as sn, SidePanelControllerContext as sr, EditViewBinding as st, getFullId as t, FieldCaption as tn, ArrayOfStorageComponentsPreview as tr, useHistory as tt, fromSerializableCollectionConfig as u, getLastSegment$1 as un, useCollectionRegistryController as ur, getChanges as ut, LiveSchemaError as v, SelectionMenu as vn, EnumValuesChip as vr, resetPasswordAction as vt, EntityForm as w, walkEntityPages as wn, renderSkeletonImageThumbnail as wr, CollectionViewActions as wt, createLiveSchemaClient as x, resolveSelection as xn, SkeletonPropertyComponent as xr, EntityViewBinding as xt, SchemaChangeCancelled as y, MAX_SELECTION_ROWS as yn, StorageThumbnail as yr, CreationResultDialog as yt, MarkdownEditorFieldBinding as z, useClearRestoreValue as zn, getIconForProperty as zr, ImportFileUpload as zt };
|
|
25662
26389
|
|
|
25663
|
-
//# sourceMappingURL=util-
|
|
26390
|
+
//# sourceMappingURL=util-DybQBkug.js.map
|