@rebasepro/cms 0.21.2-canary.g1ea48be → 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-B1mG1cfx.js → CollectionEditorDialog-DLHCka00.js} +103 -58
- package/dist/CollectionEditorDialog-DLHCka00.js.map +1 -0
- package/dist/{PropertyEditView-gKNUHxLY.js → PropertyEditView-_pu5hzZn.js} +53 -21
- package/dist/PropertyEditView-_pu5hzZn.js.map +1 -0
- package/dist/{RouterCollectionsStudioView-qgDWC6E2.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-DIyk1720.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-CrZEzQ5b.js → history-BElrZ5Rw.js} +2 -2
- package/dist/{history-CrZEzQ5b.js.map → history-BElrZ5Rw.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +4 -5
- package/dist/{import-ByaJMZuT.js → import-TCgkTVlS.js} +3 -4
- package/dist/import-TCgkTVlS.js.map +1 -0
- package/dist/index.js +16 -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-qfCPxJzB.js → util-DybQBkug.js} +1482 -717
- package/dist/util-DybQBkug.js.map +1 -0
- package/package.json +9 -9
- package/dist/CollectionEditorDialog-B1mG1cfx.js.map +0 -1
- package/dist/PropertyEditView-gKNUHxLY.js.map +0 -1
- package/dist/RouterCollectionsStudioView-qgDWC6E2.js.map +0 -1
- package/dist/export-DIyk1720.js.map +0 -1
- package/dist/import-ByaJMZuT.js.map +0 -1
- package/dist/util-DB31IoJE.js.map +0 -1
- package/dist/util-qfCPxJzB.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
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5622
|
+
updateValue,
|
|
5623
|
+
open,
|
|
5624
|
+
onOpenChange,
|
|
5625
|
+
preview,
|
|
5626
|
+
anchorRef
|
|
5627
|
+
}),
|
|
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;
|
|
@@ -6151,15 +6467,7 @@ var PropertyTableCell = React.memo(function PropertyTableCell({ propertyKey, cus
|
|
|
6151
6467
|
}
|
|
6152
6468
|
if (!innerComponent) {
|
|
6153
6469
|
allowScroll = false;
|
|
6154
|
-
|
|
6155
|
-
innerComponent = /* @__PURE__ */ jsx(PropertyPreview, {
|
|
6156
|
-
width,
|
|
6157
|
-
height,
|
|
6158
|
-
propertyKey,
|
|
6159
|
-
value: internalValue,
|
|
6160
|
-
property,
|
|
6161
|
-
size: getPreviewSizeFrom(size)
|
|
6162
|
-
});
|
|
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
|
|
@@ -6285,15 +6595,43 @@ var CollectionRowActions = function CollectionRowActions({ entity, collection, p
|
|
|
6285
6595
|
const context = useAdminContext();
|
|
6286
6596
|
const sidePanelCtrl = context.sidePanelController;
|
|
6287
6597
|
const { t } = useTranslation();
|
|
6598
|
+
const slotActions = useSlot("entity.row.actions", React.useMemo(() => ({
|
|
6599
|
+
entity,
|
|
6600
|
+
entityId: entity.id,
|
|
6601
|
+
path,
|
|
6602
|
+
collection,
|
|
6603
|
+
selectionController,
|
|
6604
|
+
context
|
|
6605
|
+
}), [
|
|
6606
|
+
entity,
|
|
6607
|
+
path,
|
|
6608
|
+
collection,
|
|
6609
|
+
selectionController,
|
|
6610
|
+
context
|
|
6611
|
+
]));
|
|
6288
6612
|
const onCheckedChange = useCallback((checked) => {
|
|
6289
6613
|
selectionController?.toggleEntitySelection(entity, checked);
|
|
6290
6614
|
}, [entity, selectionController?.toggleEntitySelection]);
|
|
6291
|
-
const hasActions = actions.length > 0;
|
|
6615
|
+
const hasActions = actions.length > 0 || slotActions.length > 0;
|
|
6292
6616
|
const hasCollapsedActions = actions.some((a) => a.collapsed || a.collapsed === void 0);
|
|
6293
6617
|
const collapsedActions = actions.filter((a) => a.collapsed || a.collapsed === void 0);
|
|
6294
6618
|
const uncollapsedActions = actions.filter((a) => a.collapsed === false);
|
|
6295
6619
|
const hasDraft = (collection ? getLocalChangesBackup(collection) : false) ? getEntityFromCache(path + "/" + entity.id) : false;
|
|
6296
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);
|
|
6297
6635
|
const content = /* @__PURE__ */ jsxs("div", {
|
|
6298
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" : ""),
|
|
6299
6637
|
onClick: useCallback((event) => {
|
|
@@ -6312,21 +6650,10 @@ var CollectionRowActions = function CollectionRowActions({ entity, collection, p
|
|
|
6312
6650
|
const isEditAction = action.key === "edit";
|
|
6313
6651
|
const tooltip = isEditAction && hasDraft ? t("unsaved_local_changes") : action.name;
|
|
6314
6652
|
let iconButton = /* @__PURE__ */ jsx(IconButton, {
|
|
6653
|
+
disabled: !isActionEnabled(action),
|
|
6315
6654
|
onClick: (event) => {
|
|
6316
6655
|
event.stopPropagation();
|
|
6317
|
-
action.onClick(
|
|
6318
|
-
view: "collection",
|
|
6319
|
-
entity,
|
|
6320
|
-
path,
|
|
6321
|
-
collection,
|
|
6322
|
-
context,
|
|
6323
|
-
sidePanelController: sidePanelCtrl,
|
|
6324
|
-
selectionController,
|
|
6325
|
-
highlightEntity,
|
|
6326
|
-
unhighlightEntity,
|
|
6327
|
-
onCollectionChange,
|
|
6328
|
-
openEntityMode: openEntityMode ?? collection?.openEntityMode
|
|
6329
|
-
});
|
|
6656
|
+
action.onClick(clickProps);
|
|
6330
6657
|
},
|
|
6331
6658
|
size: iconSize,
|
|
6332
6659
|
children: getIcon(action.icon, void 0, void 0, "smallest")
|
|
@@ -6341,27 +6668,17 @@ var CollectionRowActions = function CollectionRowActions({ entity, collection, p
|
|
|
6341
6668
|
children: iconButton
|
|
6342
6669
|
}, index);
|
|
6343
6670
|
}),
|
|
6671
|
+
slotActions,
|
|
6344
6672
|
hasCollapsedActions && /* @__PURE__ */ jsx(Menu, {
|
|
6345
6673
|
trigger: /* @__PURE__ */ jsx(IconButton, {
|
|
6346
6674
|
size: iconSize,
|
|
6347
6675
|
children: /* @__PURE__ */ jsx(MoreVerticalIcon, {})
|
|
6348
6676
|
}),
|
|
6349
6677
|
children: collapsedActions.map((action, index) => /* @__PURE__ */ jsxs(MenuItem, {
|
|
6678
|
+
disabled: !isActionEnabled(action),
|
|
6350
6679
|
onClick: (e) => {
|
|
6351
6680
|
e.stopPropagation();
|
|
6352
|
-
action.onClick(
|
|
6353
|
-
view: "collection",
|
|
6354
|
-
entity,
|
|
6355
|
-
path,
|
|
6356
|
-
collection,
|
|
6357
|
-
context,
|
|
6358
|
-
sidePanelController: sidePanelCtrl,
|
|
6359
|
-
selectionController,
|
|
6360
|
-
highlightEntity,
|
|
6361
|
-
unhighlightEntity,
|
|
6362
|
-
onCollectionChange,
|
|
6363
|
-
openEntityMode: openEntityMode ?? collection?.openEntityMode
|
|
6364
|
-
});
|
|
6681
|
+
action.onClick(clickProps);
|
|
6365
6682
|
},
|
|
6366
6683
|
children: [getIcon(action.icon, void 0, void 0, "smallest"), action.name]
|
|
6367
6684
|
}, index))
|
|
@@ -6623,6 +6940,7 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
6623
6940
|
const [operation, setOperation] = useState(fieldOperation === "==" && fieldValue === null ? "is-null" : fieldOperation === "!=" && fieldValue === null ? "is-not-null" : fieldOperation);
|
|
6624
6941
|
const [internalValue, setInternalValue] = useState(containsOperations.includes(fieldOperation) ? unwrapContains(fieldValue) : fieldValue);
|
|
6625
6942
|
const isNullOperation = operation === "is-null" || operation === "is-not-null";
|
|
6943
|
+
const hasSingleValue = internalValue !== void 0 && internalValue !== null && !Array.isArray(internalValue);
|
|
6626
6944
|
if (possibleOperations.length === 0) return null;
|
|
6627
6945
|
function updateFilter(op, val) {
|
|
6628
6946
|
if (op === "is-null" || op === "is-not-null") {
|
|
@@ -6696,11 +7014,11 @@ function StringNumberFilterField({ name, value, setValue, type, isArray, enumVal
|
|
|
6696
7014
|
position: "item-aligned",
|
|
6697
7015
|
fullWidth: true,
|
|
6698
7016
|
disabled: isNullOperation,
|
|
6699
|
-
value:
|
|
7017
|
+
value: hasSingleValue ? String(internalValue) : "",
|
|
6700
7018
|
onValueChange: (value) => {
|
|
6701
7019
|
if (value !== "") updateFilter(operation, type === "number" ? parseInt(value) : value);
|
|
6702
7020
|
},
|
|
6703
|
-
endAdornment:
|
|
7021
|
+
endAdornment: hasSingleValue && /* @__PURE__ */ jsx(IconButton, {
|
|
6704
7022
|
onClick: (e) => updateFilter(operation, void 0),
|
|
6705
7023
|
children: /* @__PURE__ */ jsx(XIcon, {})
|
|
6706
7024
|
}),
|
|
@@ -7303,7 +7621,8 @@ function DefaultFilterField({ propertyKey, property, isArray, operators, value,
|
|
|
7303
7621
|
value,
|
|
7304
7622
|
setValue,
|
|
7305
7623
|
name: propertyKey,
|
|
7306
|
-
mode: property
|
|
7624
|
+
mode: getDatePropertyMode(property),
|
|
7625
|
+
timezone: getDatePropertyTimezone(property),
|
|
7307
7626
|
isArray,
|
|
7308
7627
|
operators,
|
|
7309
7628
|
title
|
|
@@ -7376,7 +7695,10 @@ var SelectableTable = function SelectableTable({ onValueChange, cellRenderer, on
|
|
|
7376
7695
|
}, Boolean(localSelectedCell));
|
|
7377
7696
|
useEffect(() => {
|
|
7378
7697
|
const escFunction = (event) => {
|
|
7379
|
-
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();
|
|
7380
7702
|
};
|
|
7381
7703
|
document.addEventListener("keydown", escFunction, false);
|
|
7382
7704
|
return () => {
|
|
@@ -8008,7 +8330,9 @@ function useCollectionInlineEditor({ path, collection, dataClient, context }) {
|
|
|
8008
8330
|
}, [path, dataClient]);
|
|
8009
8331
|
const onValueChange = useCallback(({ value, propertyKey, onValueUpdated, setError, data: entity }) => {
|
|
8010
8332
|
if (!entity) return;
|
|
8011
|
-
const
|
|
8333
|
+
const column = propertyKey.split(/[.[]/)[0];
|
|
8334
|
+
const updatedRow = setIn(entity.values ?? {}, propertyKey, value);
|
|
8335
|
+
const updatedValues = { [column]: getIn(updatedRow, column) };
|
|
8012
8336
|
return saveEntityWithCallbacks({
|
|
8013
8337
|
path: entity.path ?? path,
|
|
8014
8338
|
entityId: entity.id,
|
|
@@ -8084,40 +8408,25 @@ function getViewModeFromSearch(search, customKeys) {
|
|
|
8084
8408
|
return isViewMode(value, customKeys) ? value : null;
|
|
8085
8409
|
}
|
|
8086
8410
|
/**
|
|
8087
|
-
* Append the active view mode to a URL so the target route keeps rendering the
|
|
8088
|
-
* view the user was last looking at.
|
|
8089
|
-
*
|
|
8090
|
-
* Every entity/collection navigation must go through this — a missed call site
|
|
8091
|
-
* silently resets the user's view mode mid-session.
|
|
8092
|
-
*
|
|
8093
|
-
* @param url the URL to decorate. May already contain a query string and/or hash.
|
|
8094
|
-
* @param search query string to read the view mode from. Defaults to the
|
|
8095
|
-
* current browser location.
|
|
8096
|
-
*/
|
|
8097
|
-
function withViewMode(url, search) {
|
|
8098
|
-
const source = search ?? (typeof window !== "undefined" ? window.location.search : "");
|
|
8099
|
-
const viewMode = new URLSearchParams(source).get(VIEW_MODE_PARAM);
|
|
8100
|
-
if (!viewMode) return url;
|
|
8101
|
-
const hashIndex = url.indexOf("#");
|
|
8102
|
-
const hash = hashIndex >= 0 ? url.substring(hashIndex) : "";
|
|
8103
|
-
const base = hashIndex >= 0 ? url.substring(0, hashIndex) : url;
|
|
8104
|
-
if (new URLSearchParams(base.split("?")[1] ?? "").has("__view")) return url;
|
|
8105
|
-
return `${base}${base.includes("?") ? "&" : "?"}${VIEW_MODE_PARAM}=${viewMode}${hash}`;
|
|
8106
|
-
}
|
|
8107
|
-
/**
|
|
8108
8411
|
* Carry the whole of the collection list's URL state onto a navigation target.
|
|
8109
8412
|
*
|
|
8110
|
-
*
|
|
8111
|
-
*
|
|
8112
|
-
*
|
|
8113
|
-
*
|
|
8114
|
-
* unfiltered list with an empty search box,
|
|
8115
|
-
* 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.
|
|
8116
8419
|
*
|
|
8117
8420
|
* Copies every param the list currently has rather than an allowlist, because
|
|
8118
8421
|
* filters are encoded under the field's own name — there is no fixed set to
|
|
8119
8422
|
* list. Params already on the target win: the caller is more specific.
|
|
8120
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
|
+
*
|
|
8121
8430
|
* @param url the URL to decorate. May carry a query string and/or a hash.
|
|
8122
8431
|
* @param search query string to read from. Defaults to the browser location.
|
|
8123
8432
|
*/
|
|
@@ -8170,7 +8479,7 @@ function resolveOpenEntityMode({ collection, viewMode, customView }) {
|
|
|
8170
8479
|
* Navigate to a entity using either a side panel or full-screen mode.
|
|
8171
8480
|
* This is an admin-specific UI concern and lives here (not in common).
|
|
8172
8481
|
*/
|
|
8173
|
-
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 }) {
|
|
8174
8483
|
if (openEntityMode === "side_panel" || openEntityMode === "dialog") sidePanelController.open({
|
|
8175
8484
|
entityId,
|
|
8176
8485
|
path,
|
|
@@ -8179,6 +8488,7 @@ function navigateToEntity({ openEntityMode, collection, entityId, copy, path, se
|
|
|
8179
8488
|
collection,
|
|
8180
8489
|
updateUrl: openEntityMode !== "dialog",
|
|
8181
8490
|
onClose,
|
|
8491
|
+
onUpdate,
|
|
8182
8492
|
defaultValues
|
|
8183
8493
|
});
|
|
8184
8494
|
else {
|
|
@@ -8194,6 +8504,14 @@ function navigateToEntity({ openEntityMode, collection, entityId, copy, path, se
|
|
|
8194
8504
|
navigation.navigate(to, hasOptions ? navigateOptions : void 0);
|
|
8195
8505
|
}
|
|
8196
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
|
+
}
|
|
8197
8515
|
//#endregion
|
|
8198
8516
|
//#region src/util/entity_actions.ts
|
|
8199
8517
|
var reservedKeys = [
|
|
@@ -8703,60 +9021,273 @@ function getWorksheetHeaders(headerRow) {
|
|
|
8703
9021
|
order
|
|
8704
9022
|
};
|
|
8705
9023
|
}
|
|
8706
|
-
//#endregion
|
|
8707
|
-
//#region src/data_import/utils/transforms.ts
|
|
9024
|
+
//#endregion
|
|
9025
|
+
//#region src/data_import/utils/transforms.ts
|
|
9026
|
+
/**
|
|
9027
|
+
* Take an object with keys of type `address.street`, `address.city` and
|
|
9028
|
+
* convert it to an object with nested objects like `{ address: { street: ..., city: ... } }`
|
|
9029
|
+
*
|
|
9030
|
+
* Keys here are the **header row of an uploaded file**, so they are attacker
|
|
9031
|
+
* data. `res["__proto__"] = …` is the prototype setter rather than an own
|
|
9032
|
+
* property, so a column named `__proto__.polluted` walked out of the accumulator
|
|
9033
|
+
* and wrote onto `Object.prototype` for the life of the tab — and
|
|
9034
|
+
* `constructor.prototype.x` threw a `TypeError` that surfaced as an unreadable
|
|
9035
|
+
* file. Such a column is refused, not sanitised, the way `setIn` settled it
|
|
9036
|
+
* (`docs/bug-classes.md` class 22).
|
|
9037
|
+
*
|
|
9038
|
+
* @param flatObj
|
|
9039
|
+
*/
|
|
9040
|
+
function unflattenObject(flatObj) {
|
|
9041
|
+
return Object.keys(flatObj).reduce((nestedObj, key) => {
|
|
9042
|
+
if (pathTraversesPrototype(key)) {
|
|
9043
|
+
console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
|
|
9044
|
+
return nestedObj;
|
|
9045
|
+
}
|
|
9046
|
+
let currentObj = nestedObj;
|
|
9047
|
+
const keyParts = key.split(".");
|
|
9048
|
+
keyParts.forEach((keyPart, i) => {
|
|
9049
|
+
if (/^[\w]+\[\d+\]$/.test(keyPart)) {
|
|
9050
|
+
const mainPropertyName = keyPart.slice(0, keyPart.indexOf("["));
|
|
9051
|
+
const index = parseInt(keyPart.slice(keyPart.indexOf("[") + 1, keyPart.indexOf("]")));
|
|
9052
|
+
if (!currentObj[mainPropertyName]) currentObj[mainPropertyName] = [];
|
|
9053
|
+
if (i !== keyParts.length - 1) {
|
|
9054
|
+
currentObj[mainPropertyName][index] = currentObj[mainPropertyName][index] || {};
|
|
9055
|
+
currentObj = currentObj[mainPropertyName][index];
|
|
9056
|
+
} else currentObj[mainPropertyName][index] = flatObj[key];
|
|
9057
|
+
} else if (i !== keyParts.length - 1) {
|
|
9058
|
+
currentObj[keyPart] = currentObj[keyPart] || {};
|
|
9059
|
+
currentObj = currentObj[keyPart];
|
|
9060
|
+
} else currentObj[keyPart] = flatObj[key];
|
|
9061
|
+
});
|
|
9062
|
+
return nestedObj;
|
|
9063
|
+
}, {});
|
|
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
|
+
}
|
|
9089
|
+
function mapJsonParse(obj) {
|
|
9090
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
9091
|
+
if (isPrototypePollutingKey(key)) {
|
|
9092
|
+
console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
|
|
9093
|
+
return acc;
|
|
9094
|
+
}
|
|
9095
|
+
acc[key] = parseCanonicalJsonCell(obj[key]);
|
|
9096
|
+
return acc;
|
|
9097
|
+
}, {});
|
|
9098
|
+
}
|
|
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
|
+
}
|
|
8708
9264
|
/**
|
|
8709
|
-
*
|
|
8710
|
-
*
|
|
8711
|
-
*
|
|
8712
|
-
* Keys here are the **header row of an uploaded file**, so they are attacker
|
|
8713
|
-
* data. `res["__proto__"] = …` is the prototype setter rather than an own
|
|
8714
|
-
* property, so a column named `__proto__.polluted` walked out of the accumulator
|
|
8715
|
-
* and wrote onto `Object.prototype` for the life of the tab — and
|
|
8716
|
-
* `constructor.prototype.x` threw a `TypeError` that surfaced as an unreadable
|
|
8717
|
-
* file. Such a column is refused, not sanitised, the way `setIn` settled it
|
|
8718
|
-
* (`docs/bug-classes.md` class 22).
|
|
8719
|
-
*
|
|
8720
|
-
* @param flatObj
|
|
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.
|
|
8721
9268
|
*/
|
|
8722
|
-
function
|
|
8723
|
-
return
|
|
8724
|
-
if (pathTraversesPrototype(key)) {
|
|
8725
|
-
console.warn(`Skipping column "${key}": a header may not reach the prototype chain`);
|
|
8726
|
-
return nestedObj;
|
|
8727
|
-
}
|
|
8728
|
-
let currentObj = nestedObj;
|
|
8729
|
-
const keyParts = key.split(".");
|
|
8730
|
-
keyParts.forEach((keyPart, i) => {
|
|
8731
|
-
if (/^[\w]+\[\d+\]$/.test(keyPart)) {
|
|
8732
|
-
const mainPropertyName = keyPart.slice(0, keyPart.indexOf("["));
|
|
8733
|
-
const index = parseInt(keyPart.slice(keyPart.indexOf("[") + 1, keyPart.indexOf("]")));
|
|
8734
|
-
if (!currentObj[mainPropertyName]) currentObj[mainPropertyName] = [];
|
|
8735
|
-
if (i !== keyParts.length - 1) {
|
|
8736
|
-
currentObj[mainPropertyName][index] = currentObj[mainPropertyName][index] || {};
|
|
8737
|
-
currentObj = currentObj[mainPropertyName][index];
|
|
8738
|
-
} else currentObj[mainPropertyName][index] = flatObj[key];
|
|
8739
|
-
} else if (i !== keyParts.length - 1) {
|
|
8740
|
-
currentObj[keyPart] = currentObj[keyPart] || {};
|
|
8741
|
-
currentObj = currentObj[keyPart];
|
|
8742
|
-
} else currentObj[keyPart] = flatObj[key];
|
|
8743
|
-
});
|
|
8744
|
-
return nestedObj;
|
|
8745
|
-
}, {});
|
|
9269
|
+
function unescapeCsvFormula(value) {
|
|
9270
|
+
return value.charAt(0) === "'" && FORMULA_TRIGGERS.includes(value.charAt(1)) ? value.slice(1) : value;
|
|
8746
9271
|
}
|
|
8747
|
-
function
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
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");
|
|
8760
9291
|
}
|
|
8761
9292
|
//#endregion
|
|
8762
9293
|
//#region src/data_import/utils/csv.ts
|
|
@@ -8776,8 +9307,9 @@ var CANDIDATE_DELIMITERS = [
|
|
|
8776
9307
|
* Split CSV text into rows of raw cells, RFC 4180 style.
|
|
8777
9308
|
*
|
|
8778
9309
|
* Quoted fields may contain the delimiter, newlines and doubled quotes; this is
|
|
8779
|
-
* the exact inverse of
|
|
8780
|
-
*
|
|
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.
|
|
8781
9313
|
*/
|
|
8782
9314
|
function parseCsvRows(text, delimiter = ",") {
|
|
8783
9315
|
const rows = [];
|
|
@@ -8875,7 +9407,7 @@ function parseCsvToObjects(text) {
|
|
|
8875
9407
|
headers: [],
|
|
8876
9408
|
data: []
|
|
8877
9409
|
};
|
|
8878
|
-
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}`);
|
|
8879
9411
|
return {
|
|
8880
9412
|
headers,
|
|
8881
9413
|
data: rows.slice(1).map((cells) => {
|
|
@@ -8884,7 +9416,7 @@ function parseCsvToObjects(text) {
|
|
|
8884
9416
|
if (isPrototypePollutingKey(header)) return;
|
|
8885
9417
|
const cell = cells[index];
|
|
8886
9418
|
if (cell === void 0) return;
|
|
8887
|
-
obj[header] = cell;
|
|
9419
|
+
obj[header] = unescapeCsvFormula(cell);
|
|
8888
9420
|
});
|
|
8889
9421
|
return obj;
|
|
8890
9422
|
})
|
|
@@ -9287,9 +9819,11 @@ function convertDataToEntity(authController, navigation, data, idColumn, headers
|
|
|
9287
9819
|
const flatObject = flattenEntry(data);
|
|
9288
9820
|
if (idColumn) delete flatObject[idColumn];
|
|
9289
9821
|
const mappedKeysObject = Object.entries(flatObject).map(([key, value]) => {
|
|
9290
|
-
const mappedKey =
|
|
9822
|
+
const mappedKey = Object.prototype.hasOwnProperty.call(headersMapping, key) ? headersMapping[key] : key;
|
|
9823
|
+
if (mappedKey === null) return {};
|
|
9824
|
+
if (value === "") return {};
|
|
9291
9825
|
const mappedProperty = getPropertyInPath$1(properties, mappedKey);
|
|
9292
|
-
if (!mappedProperty) return {};
|
|
9826
|
+
if (!mappedProperty || typeof mappedProperty !== "object") return {};
|
|
9293
9827
|
const processedValue = processValueMapping(authController, value, navigation, mappedProperty);
|
|
9294
9828
|
return { [mappedKey]: processedValue };
|
|
9295
9829
|
}).reduce((acc, curr) => ({
|
|
@@ -9342,19 +9876,19 @@ function processValueMapping(authController, value, navigation, property) {
|
|
|
9342
9876
|
}
|
|
9343
9877
|
return value;
|
|
9344
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);
|
|
9345
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));
|
|
9346
9883
|
else if (from === "string" && to === "number" && typeof value === "string") {
|
|
9347
9884
|
const trimmed = value.trim();
|
|
9348
9885
|
if (trimmed === "") return null;
|
|
9349
9886
|
const num = Number(trimmed);
|
|
9350
9887
|
return Number.isNaN(num) ? null : num;
|
|
9351
|
-
} else if (from === "
|
|
9352
|
-
else if (from === "string" && to === "boolean") return value === "true";
|
|
9353
|
-
else if (from === "number" && to === "boolean") return value === 1;
|
|
9888
|
+
} else if (from === "number" && to === "boolean") return value === 1;
|
|
9354
9889
|
else if (from === "boolean" && to === "number") return value ? 1 : 0;
|
|
9355
9890
|
else if (from === "boolean" && to === "string") return value ? "true" : "false";
|
|
9356
9891
|
else if (from === "number" && to === "string" && typeof value === "number") return value.toString();
|
|
9357
|
-
else if (from === "string" && to === "array" && typeof value === "string") return value.split(",").map((v) => v.trim());
|
|
9358
9892
|
else if (from === "string" && to === "date" && typeof value === "string") try {
|
|
9359
9893
|
return new Date(value);
|
|
9360
9894
|
} catch (e) {
|
|
@@ -9383,9 +9917,66 @@ function processValueMapping(authController, value, navigation, property) {
|
|
|
9383
9917
|
databaseId
|
|
9384
9918
|
});
|
|
9385
9919
|
} else if (from === to) return value;
|
|
9386
|
-
else if (from === "array" && to === "string" && Array.isArray(value)) return value.join(",");
|
|
9387
9920
|
return value;
|
|
9388
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
|
+
}
|
|
9389
9980
|
//#endregion
|
|
9390
9981
|
//#region src/data_import/components/DataNewPropertiesMapping.tsx
|
|
9391
9982
|
function DataNewPropertiesMapping({ importConfig, destinationProperties, buildPropertyView }) {
|
|
@@ -9911,8 +10502,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
|
|
|
9911
10502
|
}
|
|
9912
10503
|
//#endregion
|
|
9913
10504
|
//#region src/components/CollectionViewBinding/CollectionViewActions.tsx
|
|
9914
|
-
var ImportCollectionAction = lazyChunk(() => import("./import-
|
|
9915
|
-
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 })));
|
|
9916
10507
|
function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
|
|
9917
10508
|
const context = useAdminContext();
|
|
9918
10509
|
const { canCreate, canDelete } = usePermissions();
|
|
@@ -11452,27 +12043,32 @@ var ListRow = React.memo(function ListRow({ entity, collection, onClick, selecte
|
|
|
11452
12043
|
className: "flex items-center justify-end gap-0.5 flex-shrink-0 ml-auto",
|
|
11453
12044
|
style: { minWidth: actionsWidth },
|
|
11454
12045
|
onClick: (e) => e.stopPropagation(),
|
|
11455
|
-
children: listViewActions.map((action, index) =>
|
|
11456
|
-
|
|
11457
|
-
|
|
11458
|
-
|
|
11459
|
-
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11463
|
-
|
|
11464
|
-
|
|
11465
|
-
|
|
11466
|
-
|
|
11467
|
-
|
|
11468
|
-
|
|
11469
|
-
|
|
11470
|
-
|
|
11471
|
-
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11475
|
-
|
|
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
|
+
})
|
|
11476
12072
|
})
|
|
11477
12073
|
]
|
|
11478
12074
|
});
|
|
@@ -12273,7 +12869,7 @@ function SplitListShowButton({ onClick }) {
|
|
|
12273
12869
|
}
|
|
12274
12870
|
//#endregion
|
|
12275
12871
|
//#region src/components/EntityInspector.tsx
|
|
12276
|
-
var EntityHistoryView = lazyChunk(() => import("./history-
|
|
12872
|
+
var EntityHistoryView = lazyChunk(() => import("./history-BElrZ5Rw.js").then((m) => ({ default: m.EntityHistoryView })));
|
|
12277
12873
|
/**
|
|
12278
12874
|
* The JSON tab pulls `prism-react-renderer` — 85 kB, and it was EAGER.
|
|
12279
12875
|
*
|
|
@@ -12676,7 +13272,7 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
|
|
|
12676
13272
|
})
|
|
12677
13273
|
}, `entity_detail_collection_tab_${view.name}`);
|
|
12678
13274
|
});
|
|
12679
|
-
const collectionUrl =
|
|
13275
|
+
const collectionUrl = withListState(urlController.buildUrlCollectionPath(path));
|
|
12680
13276
|
const backToCollection = layout === "full_screen" && !onShowList ? () => navigate(collectionUrl) : void 0;
|
|
12681
13277
|
const fullScreenButton = !barActions && (layout === "side_panel" || layout === "dialog") && entityId ? /* @__PURE__ */ jsx(Tooltip, {
|
|
12682
13278
|
title: "Open full screen",
|
|
@@ -12985,7 +13581,7 @@ function SplitListView({ collection, tableController, onEntityClick: externalOnE
|
|
|
12985
13581
|
};
|
|
12986
13582
|
}, [selectedEntityId]);
|
|
12987
13583
|
const buildUrl = useCallback((subPath) => {
|
|
12988
|
-
return
|
|
13584
|
+
return withListState(urlController.buildUrlCollectionPath(subPath));
|
|
12989
13585
|
}, [urlController]);
|
|
12990
13586
|
const handleCloseDetail = useCallback(() => {
|
|
12991
13587
|
navigate(buildUrl(path));
|
|
@@ -13435,6 +14031,8 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13435
14031
|
[currentColumnProperty]: ["==", column]
|
|
13436
14032
|
};
|
|
13437
14033
|
const orderByParam = currentOrderProperty ? [currentOrderProperty, "asc"] : void 0;
|
|
14034
|
+
let active = true;
|
|
14035
|
+
const isCurrent = () => active && !isCleaningUpRef.current;
|
|
13438
14036
|
setColumnData((prev) => ({
|
|
13439
14037
|
...prev,
|
|
13440
14038
|
[column]: {
|
|
@@ -13444,7 +14042,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13444
14042
|
}
|
|
13445
14043
|
}));
|
|
13446
14044
|
const onUpdate = async (entities) => {
|
|
13447
|
-
if (
|
|
14045
|
+
if (!isCurrent()) return;
|
|
13448
14046
|
const pendingMap = pendingItemsRef.current;
|
|
13449
14047
|
const mergedEntities = entities.map((e) => {
|
|
13450
14048
|
const pending = pendingMap[String(e.id)];
|
|
@@ -13503,6 +14101,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13503
14101
|
} catch (e) {
|
|
13504
14102
|
console.error("Error in afterRead callback:", e);
|
|
13505
14103
|
}
|
|
14104
|
+
if (!isCurrent()) return;
|
|
13506
14105
|
const newHasMore = entities.length >= itemCount;
|
|
13507
14106
|
setColumnData((prev) => {
|
|
13508
14107
|
const existing = prev[column];
|
|
@@ -13544,10 +14143,10 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13544
14143
|
searchString: currentSearchString
|
|
13545
14144
|
});
|
|
13546
14145
|
const onError = (error) => {
|
|
13547
|
-
if (
|
|
14146
|
+
if (!isCurrent()) return;
|
|
13548
14147
|
console.error(`Error loading column ${column}:`, error);
|
|
13549
14148
|
fetchOnce().then((res) => onUpdate(res.data)).catch(() => {
|
|
13550
|
-
if (
|
|
14149
|
+
if (!isCurrent()) return;
|
|
13551
14150
|
setColumnData((prev) => ({
|
|
13552
14151
|
...prev,
|
|
13553
14152
|
[column]: {
|
|
@@ -13573,29 +14172,35 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13573
14172
|
onUpdate(res.data);
|
|
13574
14173
|
}, onError);
|
|
13575
14174
|
const firstPaintFallback = setTimeout(() => {
|
|
13576
|
-
if (liveDataReceived ||
|
|
14175
|
+
if (liveDataReceived || !isCurrent()) return;
|
|
13577
14176
|
fetchOnce().then((res) => {
|
|
13578
|
-
if (liveDataReceived ||
|
|
14177
|
+
if (liveDataReceived || !isCurrent()) return;
|
|
13579
14178
|
onUpdate(res.data);
|
|
13580
14179
|
}).catch(() => void 0);
|
|
13581
14180
|
}, LIVE_FIRST_PAINT_TIMEOUT_MS);
|
|
13582
14181
|
unsubscribersRef.current[column] = () => {
|
|
14182
|
+
active = false;
|
|
13583
14183
|
clearTimeout(firstPaintFallback);
|
|
13584
14184
|
unsubscribe?.();
|
|
13585
14185
|
};
|
|
13586
|
-
} else
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
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
|
+
}
|
|
13599
14204
|
}, []);
|
|
13600
14205
|
useEffect(() => {
|
|
13601
14206
|
isCleaningUpRef.current = false;
|
|
@@ -13614,6 +14219,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13614
14219
|
const currentResolvedPath = resolvedPathRef.current;
|
|
13615
14220
|
const currentColumns = columns;
|
|
13616
14221
|
const currentColumnItemCounts = columnItemCounts;
|
|
14222
|
+
let cancelled = false;
|
|
13617
14223
|
const timeoutId = setTimeout(() => {
|
|
13618
14224
|
if (isCleaningUpRef.current) return;
|
|
13619
14225
|
currentColumns.forEach((column) => {
|
|
@@ -13630,7 +14236,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13630
14236
|
where: whereFilter,
|
|
13631
14237
|
searchString: currentSearchString
|
|
13632
14238
|
}).then((count) => {
|
|
13633
|
-
if (isCleaningUpRef.current) return;
|
|
14239
|
+
if (cancelled || isCleaningUpRef.current) return;
|
|
13634
14240
|
setColumnData((prev) => ({
|
|
13635
14241
|
...prev,
|
|
13636
14242
|
[column]: {
|
|
@@ -13646,6 +14252,7 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13646
14252
|
prevColumnItemCountsRef.current = { ...currentColumnItemCounts };
|
|
13647
14253
|
}, 0);
|
|
13648
14254
|
return () => {
|
|
14255
|
+
cancelled = true;
|
|
13649
14256
|
clearTimeout(timeoutId);
|
|
13650
14257
|
isCleaningUpRef.current = true;
|
|
13651
14258
|
const unsubscribers = { ...unsubscribersRef.current };
|
|
@@ -13698,19 +14305,54 @@ function useBoardDataController({ fullPath, collection, columnProperty, columns,
|
|
|
13698
14305
|
[column]: (prev[column] ?? pageSize) + pageSize
|
|
13699
14306
|
}));
|
|
13700
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
|
+
}, []);
|
|
13701
14320
|
const refreshColumn = useCallback((column) => {
|
|
14321
|
+
dropPendingMoves((expected) => expected[columnPropertyRef.current] === column);
|
|
13702
14322
|
setColumnItemCounts((prev) => ({
|
|
13703
14323
|
...prev,
|
|
13704
14324
|
[column]: pageSize
|
|
13705
14325
|
}));
|
|
13706
|
-
|
|
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
|
+
]);
|
|
13707
14343
|
const refreshAll = useCallback(() => {
|
|
14344
|
+
dropPendingMoves(() => true);
|
|
13708
14345
|
const reset = {};
|
|
13709
14346
|
columns.forEach((col) => {
|
|
13710
14347
|
reset[col] = pageSize;
|
|
13711
14348
|
});
|
|
13712
14349
|
setColumnItemCounts(reset);
|
|
13713
|
-
|
|
14350
|
+
setSubscriptionVersion((v) => v + 1);
|
|
14351
|
+
}, [
|
|
14352
|
+
columns,
|
|
14353
|
+
pageSize,
|
|
14354
|
+
dropPendingMoves
|
|
14355
|
+
]);
|
|
13714
14356
|
const moveItemOptimistically = useCallback((itemId, sourceColumn, targetColumn, newValues, newIndex) => {
|
|
13715
14357
|
setColumnData((prev) => {
|
|
13716
14358
|
const updated = { ...prev };
|
|
@@ -13848,6 +14490,8 @@ function isValidOrderKey(value) {
|
|
|
13848
14490
|
}
|
|
13849
14491
|
}
|
|
13850
14492
|
function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderProperty, dataClient, context, boardDataController, analyticsController }) {
|
|
14493
|
+
const snackbarController = useSnackbarController();
|
|
14494
|
+
const { t } = useTranslation();
|
|
13851
14495
|
const handleItemsReorder = useCallback(async (items, moveInfo) => {
|
|
13852
14496
|
const entity = items.find((item) => item.id === moveInfo?.itemId)?.data;
|
|
13853
14497
|
if (!entity || !moveInfo) return;
|
|
@@ -13899,16 +14543,16 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
13899
14543
|
...saveProps,
|
|
13900
14544
|
collection,
|
|
13901
14545
|
data: dataClient,
|
|
13902
|
-
context
|
|
13903
|
-
afterSave: () => {},
|
|
13904
|
-
afterSaveError: (e) => {
|
|
13905
|
-
console.error("Failed to save entity after reorder:", e);
|
|
13906
|
-
if (boardDataController.refreshAll) boardDataController.refreshAll();
|
|
13907
|
-
}
|
|
14546
|
+
context
|
|
13908
14547
|
});
|
|
13909
14548
|
} catch (e) {
|
|
13910
|
-
console.error("
|
|
13911
|
-
|
|
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();
|
|
13912
14556
|
}
|
|
13913
14557
|
}, [
|
|
13914
14558
|
collection,
|
|
@@ -13918,7 +14562,9 @@ function useKanbanDragAndDrop({ collection, fullPath, columnProperty, orderPrope
|
|
|
13918
14562
|
dataClient,
|
|
13919
14563
|
boardDataController,
|
|
13920
14564
|
analyticsController,
|
|
13921
|
-
fullPath
|
|
14565
|
+
fullPath,
|
|
14566
|
+
snackbarController,
|
|
14567
|
+
t
|
|
13922
14568
|
]);
|
|
13923
14569
|
return useMemo(() => ({ handleItemsReorder }), [handleItemsReorder]);
|
|
13924
14570
|
}
|
|
@@ -14734,7 +15380,9 @@ function DeleteEntityDialog({ target, collection, onClose, open, onEntityDelete,
|
|
|
14734
15380
|
maxWidth: singleEntity ? "2xl" : "lg",
|
|
14735
15381
|
"aria-labelledby": "delete-dialog",
|
|
14736
15382
|
open,
|
|
14737
|
-
onOpenChange: (open) =>
|
|
15383
|
+
onOpenChange: (open) => {
|
|
15384
|
+
if (!open) handleCancel();
|
|
15385
|
+
},
|
|
14738
15386
|
children: [
|
|
14739
15387
|
/* @__PURE__ */ jsx(DialogTitle, {
|
|
14740
15388
|
id: "delete-dialog-title",
|
|
@@ -14960,7 +15608,7 @@ var copyEntityAction = {
|
|
|
14960
15608
|
name: "Copy",
|
|
14961
15609
|
key: "copy",
|
|
14962
15610
|
isEnabled: ({ entity }) => Boolean(entity),
|
|
14963
|
-
onClick({ entity, collection, context, path, sidePanelController, highlightEntity, unhighlightEntity, openEntityMode }) {
|
|
15611
|
+
onClick({ entity, collection, context, path, sidePanelController, highlightEntity, unhighlightEntity, onCollectionChange, openEntityMode }) {
|
|
14964
15612
|
if (!entity) throw new Error("INTERNAL: copyEntityAction: Entity is undefined");
|
|
14965
15613
|
if (!sidePanelController) throw new Error("INTERNAL: copyEntityAction: sidePanelController is undefined");
|
|
14966
15614
|
highlightEntity?.(entity);
|
|
@@ -14978,6 +15626,7 @@ var copyEntityAction = {
|
|
|
14978
15626
|
copy: true,
|
|
14979
15627
|
sidePanelController,
|
|
14980
15628
|
onClose: () => unhighlightEntity?.(entity),
|
|
15629
|
+
onUpdate: onCollectionChange,
|
|
14981
15630
|
navigation: context.urlController
|
|
14982
15631
|
});
|
|
14983
15632
|
return Promise.resolve(void 0);
|
|
@@ -15317,6 +15966,29 @@ function useWindowSize() {
|
|
|
15317
15966
|
}
|
|
15318
15967
|
//#endregion
|
|
15319
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
|
+
}
|
|
15320
15992
|
function PopupFormField(props) {
|
|
15321
15993
|
if (!props.open) return null;
|
|
15322
15994
|
return /* @__PURE__ */ jsx(PopupFormFieldLoading, { ...props });
|
|
@@ -15416,7 +16088,7 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
15416
16088
|
]);
|
|
15417
16089
|
const validationSchema = useMemo(() => {
|
|
15418
16090
|
if (!collection || !entityId) return;
|
|
15419
|
-
return getEntitySchema(entityId, propertyKey
|
|
16091
|
+
return getEntitySchema(entityId, propertyKey ? propertiesOfCell(collection.properties, propertyKey) : {}, customFieldValidator);
|
|
15420
16092
|
}, [
|
|
15421
16093
|
collection,
|
|
15422
16094
|
entityId,
|
|
@@ -15441,7 +16113,7 @@ function PopupFormFieldInternal({ tableKey, entityId, customFieldValidator, prop
|
|
|
15441
16113
|
const saveValue = async (values) => {
|
|
15442
16114
|
setSavingError(null);
|
|
15443
16115
|
if (collection && entity && onCellValueChange && propertyKey) return onCellValueChange({
|
|
15444
|
-
value: values
|
|
16116
|
+
value: getIn(applyValueTransforms(values, collection.properties), propertyKey),
|
|
15445
16117
|
propertyKey,
|
|
15446
16118
|
data: entity,
|
|
15447
16119
|
setError: setSavingError,
|
|
@@ -16164,7 +16836,7 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
|
|
|
16164
16836
|
openNewDocument
|
|
16165
16837
|
};
|
|
16166
16838
|
const handleBackClick = useCallback(() => {
|
|
16167
|
-
navigate(
|
|
16839
|
+
navigate(withListState(urlController.buildUrlCollectionPath(path)));
|
|
16168
16840
|
}, [
|
|
16169
16841
|
navigate,
|
|
16170
16842
|
urlController,
|
|
@@ -16477,19 +17149,9 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16477
17149
|
urlController,
|
|
16478
17150
|
analyticsController
|
|
16479
17151
|
]);
|
|
16480
|
-
const
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
navigateToEntity({
|
|
16484
|
-
openEntityMode,
|
|
16485
|
-
collection,
|
|
16486
|
-
entityId: void 0,
|
|
16487
|
-
path,
|
|
16488
|
-
sidePanelController,
|
|
16489
|
-
navigation: urlController,
|
|
16490
|
-
onClose: unselectNavigatedEntity
|
|
16491
|
-
});
|
|
16492
|
-
}, [path, sidePanelController]);
|
|
17152
|
+
const onCreatedInPanel = useCallback(() => {
|
|
17153
|
+
setLastDeleteTimestamp(Date.now());
|
|
17154
|
+
}, []);
|
|
16493
17155
|
/**
|
|
16494
17156
|
* Attach rows that already exist to this parent.
|
|
16495
17157
|
*
|
|
@@ -16529,7 +17191,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16529
17191
|
const selectionDialog = useSelectionDialog({
|
|
16530
17192
|
path: linkedTargetSlug ?? false,
|
|
16531
17193
|
multiselect: true,
|
|
16532
|
-
|
|
17194
|
+
onSelectionDone: linkExistingEntities
|
|
16533
17195
|
});
|
|
16534
17196
|
const openNewDocument = useCallback((defaultValues) => {
|
|
16535
17197
|
const collection = collectionRef.current;
|
|
@@ -16542,15 +17204,18 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16542
17204
|
path,
|
|
16543
17205
|
sidePanelController,
|
|
16544
17206
|
navigation: urlController,
|
|
16545
|
-
onClose: unselectNavigatedEntity
|
|
17207
|
+
onClose: unselectNavigatedEntity,
|
|
17208
|
+
onUpdate: onCreatedInPanel
|
|
16546
17209
|
});
|
|
16547
17210
|
}, [
|
|
16548
17211
|
path,
|
|
16549
17212
|
sidePanelController,
|
|
16550
17213
|
openEntityMode,
|
|
16551
17214
|
urlController,
|
|
16552
|
-
unselectNavigatedEntity
|
|
17215
|
+
unselectNavigatedEntity,
|
|
17216
|
+
onCreatedInPanel
|
|
16553
17217
|
]);
|
|
17218
|
+
const onNewClick = useCallback(() => openNewDocument(), [openNewDocument]);
|
|
16554
17219
|
const onMultipleDeleteClick = () => {
|
|
16555
17220
|
analyticsController.onAnalyticsEvent?.("multiple_delete_dialog_open", { path });
|
|
16556
17221
|
setDeleteEntityClicked(selection);
|
|
@@ -16759,7 +17424,9 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16759
17424
|
collection,
|
|
16760
17425
|
collectionWithLocalOrder,
|
|
16761
17426
|
path,
|
|
16762
|
-
sidePanelController
|
|
17427
|
+
sidePanelController,
|
|
17428
|
+
openEntityMode,
|
|
17429
|
+
urlController
|
|
16763
17430
|
]);
|
|
16764
17431
|
const updateLastDeleteTimestamp = useCallback(() => {
|
|
16765
17432
|
setLastDeleteTimestamp(Date.now());
|
|
@@ -16821,7 +17488,8 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
|
|
|
16821
17488
|
filter: tableController.filterValues,
|
|
16822
17489
|
sortBy: tableController.sortBy,
|
|
16823
17490
|
searchString: tableController.searchString,
|
|
16824
|
-
onCountChange: setDocsCount
|
|
17491
|
+
onCountChange: setDocsCount,
|
|
17492
|
+
refreshKey: lastDeleteTimestamp
|
|
16825
17493
|
});
|
|
16826
17494
|
const liveSelectionQuery = useMemo(() => ({
|
|
16827
17495
|
path,
|
|
@@ -17200,7 +17868,7 @@ function DefaultCollectionEmptyState({ canCreate, onNewClick, isSearching, searc
|
|
|
17200
17868
|
* callers (e.g. React StrictMode double-mount) share the same promise.
|
|
17201
17869
|
*/
|
|
17202
17870
|
var inflightCountRequests = /* @__PURE__ */ new Map();
|
|
17203
|
-
function EntitiesCount({ path, collection, filter, sortBy, searchString, onCountChange }) {
|
|
17871
|
+
function EntitiesCount({ path, collection, filter, sortBy, searchString, onCountChange, refreshKey }) {
|
|
17204
17872
|
const dataClient = useData();
|
|
17205
17873
|
const sortKey = React.useMemo(() => sortBy ? serializeOrderBy(sortBy) ?? "" : "", [sortBy]);
|
|
17206
17874
|
const dataClientRef = React.useRef(dataClient);
|
|
@@ -17217,7 +17885,7 @@ function EntitiesCount({ path, collection, filter, sortBy, searchString, onCount
|
|
|
17217
17885
|
let cancelled = false;
|
|
17218
17886
|
const whereParams = filter && Object.keys(filter).length > 0 ? filter : void 0;
|
|
17219
17887
|
const orderByParams = sortBy && sortBy.length > 0 ? sortBy.map(([field, direction]) => [String(field), direction]) : void 0;
|
|
17220
|
-
const cacheKey = `${path}|${filterKey}|${sortKey}|${searchString ?? ""}`;
|
|
17888
|
+
const cacheKey = `${path}|${filterKey}|${sortKey}|${searchString ?? ""}|${refreshKey ?? ""}`;
|
|
17221
17889
|
let countPromise = inflightCountRequests.get(cacheKey);
|
|
17222
17890
|
if (!countPromise) {
|
|
17223
17891
|
countPromise = accessor.count({
|
|
@@ -17241,7 +17909,8 @@ function EntitiesCount({ path, collection, filter, sortBy, searchString, onCount
|
|
|
17241
17909
|
path,
|
|
17242
17910
|
filterKey,
|
|
17243
17911
|
sortKey,
|
|
17244
|
-
searchString
|
|
17912
|
+
searchString,
|
|
17913
|
+
refreshKey
|
|
17245
17914
|
]);
|
|
17246
17915
|
return null;
|
|
17247
17916
|
}
|
|
@@ -17393,7 +18062,7 @@ function getChanges(source, comparison) {
|
|
|
17393
18062
|
continue;
|
|
17394
18063
|
}
|
|
17395
18064
|
if (sourceValue.some((item, index) => !deepEqual(item, comparisonArray[index]))) changes[key] = sourceValue;
|
|
17396
|
-
} else if (
|
|
18065
|
+
} else if (isPlainObject(sourceValue) && isPlainObject(comparisonValue)) {
|
|
17397
18066
|
const nestedChanges = getChanges(sourceValue, comparisonValue);
|
|
17398
18067
|
if (Object.keys(nestedChanges).length > 0) changes[key] = nestedChanges;
|
|
17399
18068
|
} else changes[key] = sourceValue;
|
|
@@ -17401,6 +18070,24 @@ function getChanges(source, comparison) {
|
|
|
17401
18070
|
return changes;
|
|
17402
18071
|
}
|
|
17403
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
|
+
/**
|
|
17404
18091
|
* What the local-changes backup still has to offer, given what the form is
|
|
17405
18092
|
* already showing — the answer the "unsaved local changes" banner is asking for.
|
|
17406
18093
|
* `undefined` means there is nothing left to apply, and no banner.
|
|
@@ -18036,6 +18723,18 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
18036
18723
|
path,
|
|
18037
18724
|
entity: usedEntity
|
|
18038
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
|
+
]);
|
|
18039
18738
|
const recordActionItems = usedEntity && recordActions.length ? recordActions.map((action, index) => {
|
|
18040
18739
|
const clickProps = {
|
|
18041
18740
|
view: "form",
|
|
@@ -18045,7 +18744,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
|
|
|
18045
18744
|
context,
|
|
18046
18745
|
sidePanelController: adminContext.sidePanelController,
|
|
18047
18746
|
openEntityMode: layout,
|
|
18048
|
-
navigateBack:
|
|
18747
|
+
navigateBack: leaveRecord,
|
|
18049
18748
|
formContext
|
|
18050
18749
|
};
|
|
18051
18750
|
return /* @__PURE__ */ jsxs(MenuItem, {
|
|
@@ -18780,8 +19479,8 @@ var useBuildSidePanel = (collectionRegistryController, urlController, navigation
|
|
|
18780
19479
|
const panelsFromUrl = buildSidePanelsFromUrl(urlController.urlPathToDataPath(location.pathname), collectionRegistryController.collections ?? [], newFlag);
|
|
18781
19480
|
for (let i = 0; i < panelsFromUrl.length; i++) {
|
|
18782
19481
|
const props = panelsFromUrl[i];
|
|
18783
|
-
if (i === 0) sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
18784
|
-
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));
|
|
18785
19484
|
}
|
|
18786
19485
|
}
|
|
18787
19486
|
initialised.current = true;
|
|
@@ -18795,7 +19494,7 @@ var useBuildSidePanel = (collectionRegistryController, urlController, navigation
|
|
|
18795
19494
|
const panelsFromUrl = buildSidePanelsFromUrl(urlController.urlPathToDataPath(location.pathname), collectionRegistryController.collections ?? [], false);
|
|
18796
19495
|
if (panelsFromUrl.length <= currentKeys.length) return;
|
|
18797
19496
|
const lastPanel = panelsFromUrl[panelsFromUrl.length - 1];
|
|
18798
|
-
const panelProps = propsToSidePanel(lastPanel, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19497
|
+
const panelProps = propsToSidePanel(lastPanel, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController);
|
|
18799
19498
|
const lastCurrentPanel = currentKeys.length > 0 ? currentKeys[currentKeys.length - 1] : void 0;
|
|
18800
19499
|
if (!lastCurrentPanel || lastCurrentPanel !== panelProps.key) sideDialogsController.replace(panelProps);
|
|
18801
19500
|
}
|
|
@@ -18827,25 +19526,23 @@ var useBuildSidePanel = (collectionRegistryController, urlController, navigation
|
|
|
18827
19526
|
sideDialogsController.open(propsToSidePanel({
|
|
18828
19527
|
...props,
|
|
18829
19528
|
selectedTab: props.selectedTab ?? defaultSelectedView
|
|
18830
|
-
}, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19529
|
+
}, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController));
|
|
18831
19530
|
}, [
|
|
18832
19531
|
sideDialogsController,
|
|
18833
19532
|
urlController.buildUrlCollectionPath,
|
|
18834
19533
|
urlController.resolveDatabasePathsFrom,
|
|
18835
19534
|
smallLayout,
|
|
18836
|
-
authController.user
|
|
18837
|
-
location.search
|
|
19535
|
+
authController.user
|
|
18838
19536
|
]);
|
|
18839
19537
|
const replace = useCallback((props) => {
|
|
18840
19538
|
if (props.copy && !props.entityId) throw Error("If you want to copy a entity you need to provide a entityId");
|
|
18841
|
-
sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController
|
|
19539
|
+
sideDialogsController.replace(propsToSidePanel(props, urlController.buildUrlCollectionPath, urlController.resolveDatabasePathsFrom, smallLayout, customizationController, authController));
|
|
18842
19540
|
}, [
|
|
18843
19541
|
urlController.buildUrlCollectionPath,
|
|
18844
19542
|
urlController.resolveDatabasePathsFrom,
|
|
18845
19543
|
sideDialogsController,
|
|
18846
19544
|
smallLayout,
|
|
18847
|
-
authController.user
|
|
18848
|
-
location.search
|
|
19545
|
+
authController.user
|
|
18849
19546
|
]);
|
|
18850
19547
|
return useMemo(() => ({
|
|
18851
19548
|
close,
|
|
@@ -18902,8 +19599,9 @@ function buildSidePanelsFromUrl(path, collections, newFlag) {
|
|
|
18902
19599
|
};
|
|
18903
19600
|
return sidePanel ? [sidePanel] : [];
|
|
18904
19601
|
}
|
|
18905
|
-
var propsToSidePanel = (props, buildUrlCollectionPath, resolveIdsFrom, smallLayout, customizationController, authController
|
|
19602
|
+
var propsToSidePanel = (props, buildUrlCollectionPath, resolveIdsFrom, smallLayout, customizationController, authController) => {
|
|
18906
19603
|
const collectionPath = removeInitialAndTrailingSlashes(props.path);
|
|
19604
|
+
const locationSearch = typeof window !== "undefined" ? window.location.search : "";
|
|
18907
19605
|
const shouldUpdateUrl = props.updateUrl !== false;
|
|
18908
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;
|
|
18909
19607
|
const parentUrlPath = shouldUpdateUrl ? buildUrlCollectionPath(collectionPath) : void 0;
|
|
@@ -19081,12 +19779,11 @@ function useHistory(params) {
|
|
|
19081
19779
|
refreshTrigger
|
|
19082
19780
|
]);
|
|
19083
19781
|
const loadMore = useCallback(() => {
|
|
19084
|
-
if (!isLoading && hasMore &&
|
|
19782
|
+
if (!isLoading && hasMore && entries.length < total) setOffset((prev) => prev + pageSize);
|
|
19085
19783
|
}, [
|
|
19086
19784
|
isLoading,
|
|
19087
19785
|
hasMore,
|
|
19088
19786
|
pageSize,
|
|
19089
|
-
offset,
|
|
19090
19787
|
entries.length,
|
|
19091
19788
|
total
|
|
19092
19789
|
]);
|
|
@@ -20169,7 +20866,7 @@ function SelectionTableBinding(props) {
|
|
|
20169
20866
|
collection: props.collection
|
|
20170
20867
|
});
|
|
20171
20868
|
}
|
|
20172
|
-
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 }) {
|
|
20173
20870
|
const sideDialogContext = useSideDialogContext();
|
|
20174
20871
|
const sidePanelController = useSidePanel();
|
|
20175
20872
|
const urlController = useUrlController();
|
|
@@ -20276,8 +20973,13 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
|
|
|
20276
20973
|
};
|
|
20277
20974
|
const onDone = useCallback((event) => {
|
|
20278
20975
|
event.stopPropagation();
|
|
20976
|
+
onSelectionDone?.(selectedEntities);
|
|
20279
20977
|
sideDialogContext.close(false);
|
|
20280
|
-
}, [
|
|
20978
|
+
}, [
|
|
20979
|
+
sideDialogContext,
|
|
20980
|
+
onSelectionDone,
|
|
20981
|
+
selectedEntities
|
|
20982
|
+
]);
|
|
20281
20983
|
const displayedColumnIds = useColumnIds(collection, false);
|
|
20282
20984
|
const tableController = useDataTableController({
|
|
20283
20985
|
path,
|
|
@@ -20442,6 +21144,7 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20442
21144
|
});
|
|
20443
21145
|
const onEntryClick = (e) => {
|
|
20444
21146
|
e.preventDefault();
|
|
21147
|
+
if (disabled || isSubmitting) return;
|
|
20445
21148
|
referenceDialogController.open();
|
|
20446
21149
|
};
|
|
20447
21150
|
const buildEntry = useCallback(({ index, internalId, storedProps, storeProps }) => {
|
|
@@ -20460,7 +21163,9 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20460
21163
|
}, [
|
|
20461
21164
|
ofProperty.path,
|
|
20462
21165
|
ofProperty.admin?.previewProperties,
|
|
20463
|
-
value
|
|
21166
|
+
value,
|
|
21167
|
+
disabled,
|
|
21168
|
+
isSubmitting
|
|
20464
21169
|
]);
|
|
20465
21170
|
const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIcon, {
|
|
20466
21171
|
propertyKey,
|
|
@@ -20482,7 +21187,7 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20482
21187
|
children: [/* @__PURE__ */ jsx(ArrayContainer, {
|
|
20483
21188
|
droppableId: propertyKey,
|
|
20484
21189
|
value,
|
|
20485
|
-
disabled: isSubmitting,
|
|
21190
|
+
disabled: disabled || isSubmitting,
|
|
20486
21191
|
buildEntry,
|
|
20487
21192
|
canAddElements: false,
|
|
20488
21193
|
addLabel: property.name ? t("add_reference_to", { name: property.name }) : t("add_reference"),
|
|
@@ -20491,14 +21196,9 @@ function ArrayOfReferencesFieldBinding({ propertyKey, value, error, showError, d
|
|
|
20491
21196
|
}), /* @__PURE__ */ jsxs(Button, {
|
|
20492
21197
|
className: "ml-3.5 my-4 justify-center text-left",
|
|
20493
21198
|
variant: "text",
|
|
20494
|
-
disabled: isSubmitting,
|
|
21199
|
+
disabled: disabled || isSubmitting,
|
|
20495
21200
|
onClick: onEntryClick,
|
|
20496
|
-
children: [
|
|
20497
|
-
/* @__PURE__ */ jsx(PencilIcon, { size: 16 }),
|
|
20498
|
-
t("edit"),
|
|
20499
|
-
" ",
|
|
20500
|
-
property.name
|
|
20501
|
-
]
|
|
21201
|
+
children: [/* @__PURE__ */ jsx(PencilIcon, { size: 16 }), t("edit_name", { name: property.name ?? propertyKey })]
|
|
20502
21202
|
})]
|
|
20503
21203
|
})] });
|
|
20504
21204
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -20696,7 +21396,8 @@ function DateTimeFieldBinding({ propertyKey, value, setValue, autoFocus, error,
|
|
|
20696
21396
|
size,
|
|
20697
21397
|
value: internalValue,
|
|
20698
21398
|
onChange: (dateValue) => setValue(dateValue),
|
|
20699
|
-
mode: property
|
|
21399
|
+
mode: getDatePropertyMode(property),
|
|
21400
|
+
timezone: getDatePropertyTimezone(property),
|
|
20700
21401
|
clearable: property.admin?.clearable,
|
|
20701
21402
|
locale,
|
|
20702
21403
|
error: showError,
|
|
@@ -21279,13 +21980,20 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
21279
21980
|
const internalValue = useRef(value);
|
|
21280
21981
|
useEffect(() => {
|
|
21281
21982
|
getMarkdownUtils().then(() => {
|
|
21282
|
-
canonicalRef.current = canonicalizeMarkdown(
|
|
21983
|
+
canonicalRef.current = canonicalizeMarkdown(canonicalSourceRef.current);
|
|
21283
21984
|
});
|
|
21284
21985
|
}, []);
|
|
21285
21986
|
const canonicalRef = useRef(useMemo(() => canonicalizeMarkdown(value), []));
|
|
21987
|
+
const canonicalSourceRef = useRef(value);
|
|
21286
21988
|
const onContentChange = useCallback((content) => {
|
|
21287
21989
|
const normalizedContent = normalizeMarkdown(content);
|
|
21288
|
-
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
|
+
}
|
|
21289
21997
|
if (normalizedContent === normalizeMarkdown(value)) return;
|
|
21290
21998
|
internalValue.current = content;
|
|
21291
21999
|
setValue(content);
|
|
@@ -21293,6 +22001,7 @@ function MarkdownEditorFieldBinding({ property, propertyKey, value, setValue, in
|
|
|
21293
22001
|
useEffect(() => {
|
|
21294
22002
|
if (internalValue.current !== value) {
|
|
21295
22003
|
internalValue.current = value;
|
|
22004
|
+
canonicalSourceRef.current = value;
|
|
21296
22005
|
canonicalRef.current = canonicalizeMarkdown(value);
|
|
21297
22006
|
setFieldVersion((v) => v + 1);
|
|
21298
22007
|
}
|
|
@@ -21495,6 +22204,7 @@ function ReferenceFieldBinding(props) {
|
|
|
21495
22204
|
}
|
|
21496
22205
|
function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, showError, isSubmitting, disabled, minimalistView, touched, autoFocus, property, includeDescription, hideLabel, size = "medium" }) {
|
|
21497
22206
|
if (!property.path) throw new Error("Property path is required for ReferenceFieldBinding");
|
|
22207
|
+
const { t } = useTranslation();
|
|
21498
22208
|
const refValue = value;
|
|
21499
22209
|
useClearRestoreValue({
|
|
21500
22210
|
property,
|
|
@@ -21520,6 +22230,7 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
|
|
|
21520
22230
|
});
|
|
21521
22231
|
const onEntryClick = (e) => {
|
|
21522
22232
|
e.preventDefault();
|
|
22233
|
+
if (disabled || isSubmitting) return;
|
|
21523
22234
|
referenceDialogController.open();
|
|
21524
22235
|
};
|
|
21525
22236
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -21544,12 +22255,12 @@ function ReferenceFieldBindingInternal({ propertyKey, value, setValue, error, sh
|
|
|
21544
22255
|
className: "justify-center text-left",
|
|
21545
22256
|
children: /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
21546
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"),
|
|
21547
|
-
onClick: onEntryClick,
|
|
22258
|
+
onClick: disabled || isSubmitting ? void 0 : onEntryClick,
|
|
21548
22259
|
size: "medium",
|
|
21549
22260
|
children: [/* @__PURE__ */ jsx(IconForView, {
|
|
21550
22261
|
collectionOrView: collection,
|
|
21551
22262
|
className: "text-surface-300 dark:text-surface-600"
|
|
21552
|
-
}),
|
|
22263
|
+
}), t("edit_name", { name: property.name ?? propertyKey }).toUpperCase()]
|
|
21553
22264
|
})
|
|
21554
22265
|
})] }),
|
|
21555
22266
|
/* @__PURE__ */ jsx(FieldHelperText, {
|
|
@@ -22605,6 +23316,7 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22605
23316
|
});
|
|
22606
23317
|
const onEntryClick = (e) => {
|
|
22607
23318
|
e.preventDefault();
|
|
23319
|
+
if (disabled || isSubmitting) return;
|
|
22608
23320
|
referenceDialogController.open();
|
|
22609
23321
|
};
|
|
22610
23322
|
const buildEntry = useCallback(({ index, internalId, storedProps, storeProps }) => {
|
|
@@ -22622,7 +23334,9 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22622
23334
|
}, [
|
|
22623
23335
|
relation,
|
|
22624
23336
|
property.admin?.previewProperties,
|
|
22625
|
-
value
|
|
23337
|
+
value,
|
|
23338
|
+
disabled,
|
|
23339
|
+
isSubmitting
|
|
22626
23340
|
]);
|
|
22627
23341
|
const title = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LabelWithIcon, {
|
|
22628
23342
|
propertyKey,
|
|
@@ -22644,7 +23358,7 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22644
23358
|
children: [/* @__PURE__ */ jsx(ArrayContainer, {
|
|
22645
23359
|
droppableId: propertyKey,
|
|
22646
23360
|
value,
|
|
22647
|
-
disabled: isSubmitting,
|
|
23361
|
+
disabled: disabled || isSubmitting,
|
|
22648
23362
|
buildEntry,
|
|
22649
23363
|
canAddElements: false,
|
|
22650
23364
|
addLabel: property.name ? t("add_reference_to", { name: property.name }) : t("add_reference"),
|
|
@@ -22654,14 +23368,9 @@ function MultipleRelationFieldBinding({ propertyKey, value: valueProp, error, sh
|
|
|
22654
23368
|
className: "ml-3.5 my-4 justify-center text-left",
|
|
22655
23369
|
variant: "text",
|
|
22656
23370
|
color: "primary",
|
|
22657
|
-
disabled: isSubmitting,
|
|
23371
|
+
disabled: disabled || isSubmitting,
|
|
22658
23372
|
onClick: onEntryClick,
|
|
22659
|
-
children: [
|
|
22660
|
-
/* @__PURE__ */ jsx(PencilIcon, { size: 16 }),
|
|
22661
|
-
t("edit"),
|
|
22662
|
-
" ",
|
|
22663
|
-
property.name
|
|
22664
|
-
]
|
|
23373
|
+
children: [/* @__PURE__ */ jsx(PencilIcon, { size: 16 }), t("edit_name", { name: property.name ?? propertyKey })]
|
|
22665
23374
|
})]
|
|
22666
23375
|
})] });
|
|
22667
23376
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ExpandablePanel, {
|
|
@@ -22734,6 +23443,7 @@ function RelationSelectorBinding({ propertyKey, value, size, error, showError, d
|
|
|
22734
23443
|
});
|
|
22735
23444
|
}
|
|
22736
23445
|
function SingleRelationFieldBinding({ propertyKey, value, size, error, showError, disabled, isSubmitting, property, includeDescription, hideLabel, setValue, relation }) {
|
|
23446
|
+
const { t } = useTranslation();
|
|
22737
23447
|
const normalizedValue = value && !Array.isArray(value) ? normalizeToEntityRelation(value) : null;
|
|
22738
23448
|
const validValue = !!normalizedValue;
|
|
22739
23449
|
const collection = relation?.target();
|
|
@@ -22751,6 +23461,7 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
22751
23461
|
});
|
|
22752
23462
|
const onEntryClick = (e) => {
|
|
22753
23463
|
e.preventDefault();
|
|
23464
|
+
if (disabled || isSubmitting) return;
|
|
22754
23465
|
referenceDialogController.open();
|
|
22755
23466
|
};
|
|
22756
23467
|
const usedRelation = Array.isArray(value) ? void 0 : normalizedValue ?? void 0;
|
|
@@ -22776,12 +23487,12 @@ function SingleRelationFieldBinding({ propertyKey, value, size, error, showError
|
|
|
22776
23487
|
className: "justify-center text-left",
|
|
22777
23488
|
children: /* @__PURE__ */ jsxs(EntityPreviewContainer, {
|
|
22778
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"),
|
|
22779
|
-
onClick: onEntryClick,
|
|
23490
|
+
onClick: disabled || isSubmitting ? void 0 : onEntryClick,
|
|
22780
23491
|
size: "medium",
|
|
22781
23492
|
children: [/* @__PURE__ */ jsx(IconForView, {
|
|
22782
23493
|
collectionOrView: collection,
|
|
22783
23494
|
className: "text-surface-300 dark:text-surface-600"
|
|
22784
|
-
}),
|
|
23495
|
+
}), t("edit_name", { name: property.name ?? propertyKey }).toUpperCase()]
|
|
22785
23496
|
})
|
|
22786
23497
|
})] }),
|
|
22787
23498
|
/* @__PURE__ */ jsx(FieldHelperText, {
|
|
@@ -23387,6 +24098,25 @@ function PropertyFieldBindingInternal({ propertyKey, property, context, includeD
|
|
|
23387
24098
|
}
|
|
23388
24099
|
function FieldInternal({ Component, componentProps: { propertyKey, property, includeDescription, hideLabel, underlyingValueHasChanged, partOfArray, partOfBlock, minimalistView, autoFocus, context, disabled, size, onPropertyChange }, formexFieldProps }) {
|
|
23389
24100
|
const { plugins } = useCustomizationController();
|
|
24101
|
+
const { t } = useTranslation();
|
|
24102
|
+
const rebaseContext = useRebaseContext();
|
|
24103
|
+
const fieldSlotProps = React.useMemo(() => ({
|
|
24104
|
+
propertyKey,
|
|
24105
|
+
property,
|
|
24106
|
+
path: context.path ?? "",
|
|
24107
|
+
entityId: context.entityId,
|
|
24108
|
+
collection: context.collection,
|
|
24109
|
+
context: rebaseContext
|
|
24110
|
+
}), [
|
|
24111
|
+
propertyKey,
|
|
24112
|
+
property,
|
|
24113
|
+
context.path,
|
|
24114
|
+
context.entityId,
|
|
24115
|
+
context.collection,
|
|
24116
|
+
rebaseContext
|
|
24117
|
+
]);
|
|
24118
|
+
const beforeField = useSlot("entity.field.before", fieldSlotProps);
|
|
24119
|
+
const afterField = useSlot("entity.field.after", fieldSlotProps);
|
|
23390
24120
|
const customFieldProps = property.admin?.customProps;
|
|
23391
24121
|
const value = formexFieldProps.field.value;
|
|
23392
24122
|
const error = getIn(formexFieldProps.form.errors, propertyKey);
|
|
@@ -23401,42 +24131,47 @@ function FieldInternal({ Component, componentProps: { propertyKey, property, inc
|
|
|
23401
24131
|
plugins
|
|
23402
24132
|
}) ?? Component;
|
|
23403
24133
|
const isSubmitting = formexFieldProps.form.isSubmitting;
|
|
23404
|
-
return /* @__PURE__ */ jsxs(ErrorBoundary, { children: [
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
|
|
23415
|
-
|
|
23416
|
-
|
|
23417
|
-
|
|
23418
|
-
|
|
23419
|
-
|
|
23420
|
-
|
|
23421
|
-
|
|
23422
|
-
|
|
23423
|
-
|
|
23424
|
-
|
|
23425
|
-
|
|
23426
|
-
|
|
23427
|
-
|
|
23428
|
-
|
|
23429
|
-
|
|
23430
|
-
|
|
23431
|
-
|
|
23432
|
-
|
|
23433
|
-
|
|
24134
|
+
return /* @__PURE__ */ jsxs(ErrorBoundary, { children: [
|
|
24135
|
+
beforeField,
|
|
24136
|
+
/* @__PURE__ */ jsx(Suspense, {
|
|
24137
|
+
fallback: null,
|
|
24138
|
+
children: /* @__PURE__ */ jsx(UsedComponent, {
|
|
24139
|
+
propertyKey,
|
|
24140
|
+
value,
|
|
24141
|
+
setValue: useCallback((value, shouldValidate) => {
|
|
24142
|
+
formexFieldProps.form.setFieldTouched(propertyKey, true, false);
|
|
24143
|
+
formexFieldProps.form.setFieldValue(propertyKey, value, shouldValidate);
|
|
24144
|
+
}, []),
|
|
24145
|
+
setFieldValue: useCallback((otherPropertyKey, value, shouldValidate) => {
|
|
24146
|
+
formexFieldProps.form.setFieldTouched(propertyKey, true, false);
|
|
24147
|
+
formexFieldProps.form.setFieldValue(otherPropertyKey, value, shouldValidate);
|
|
24148
|
+
}, []),
|
|
24149
|
+
error,
|
|
24150
|
+
touched,
|
|
24151
|
+
showError,
|
|
24152
|
+
isSubmitting,
|
|
24153
|
+
includeDescription: includeDescription ?? true,
|
|
24154
|
+
hideLabel: hideLabel ?? false,
|
|
24155
|
+
property,
|
|
24156
|
+
disabled: disabled ?? false,
|
|
24157
|
+
underlyingValueHasChanged: underlyingValueHasChanged ?? false,
|
|
24158
|
+
partOfArray: partOfArray ?? false,
|
|
24159
|
+
partOfBlock: partOfBlock ?? false,
|
|
24160
|
+
minimalistView: minimalistView ?? false,
|
|
24161
|
+
autoFocus: autoFocus ?? false,
|
|
24162
|
+
customProps: customFieldProps,
|
|
24163
|
+
context,
|
|
24164
|
+
size,
|
|
24165
|
+
onPropertyChange
|
|
24166
|
+
})
|
|
24167
|
+
}),
|
|
24168
|
+
afterField,
|
|
24169
|
+
underlyingValueHasChanged && !isSubmitting && /* @__PURE__ */ jsx(Typography, {
|
|
24170
|
+
variant: "caption",
|
|
24171
|
+
className: "ml-3.5",
|
|
24172
|
+
children: t("value_updated_elsewhere")
|
|
23434
24173
|
})
|
|
23435
|
-
})
|
|
23436
|
-
variant: "caption",
|
|
23437
|
-
className: "ml-3.5",
|
|
23438
|
-
children: "This value has been updated elsewhere"
|
|
23439
|
-
})] });
|
|
24174
|
+
] });
|
|
23440
24175
|
}
|
|
23441
24176
|
var shouldPropertyReRender = (property, plugins) => {
|
|
23442
24177
|
if (plugins?.some((plugin) => plugin.fieldBuilder)) return true;
|
|
@@ -23940,7 +24675,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
23940
24675
|
Promise.resolve(onSubmitProp(valuesToBeSaved, formex)).finally(() => setIsSavingAutoSave(false));
|
|
23941
24676
|
}
|
|
23942
24677
|
}, false, 2e3);
|
|
23943
|
-
|
|
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({});
|
|
23944
24685
|
const initialEntityId = useMemo(() => {
|
|
23945
24686
|
if (status === "new" || status === "copy") return;
|
|
23946
24687
|
else return entityIdProp;
|
|
@@ -24009,6 +24750,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24009
24750
|
}), {}) : {},
|
|
24010
24751
|
onSubmit,
|
|
24011
24752
|
onReset: () => {
|
|
24753
|
+
setUnderlyingChanges({});
|
|
24012
24754
|
onResetProp?.();
|
|
24013
24755
|
onValuesModified?.(false, baseInitialValues);
|
|
24014
24756
|
},
|
|
@@ -24055,12 +24797,13 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24055
24797
|
};
|
|
24056
24798
|
const lastSavedValues = useRef(entity?.values);
|
|
24057
24799
|
const save = async (values) => {
|
|
24058
|
-
const
|
|
24800
|
+
const writtenValues = applyValueTransforms(values, collection.properties);
|
|
24801
|
+
const valuesToSave = status === "existing" ? getChangedProperties(writtenValues, entity?.values || {}) : writtenValues;
|
|
24059
24802
|
if (status === "existing" && Object.keys(valuesToSave).length === 0 && entity) return Promise.resolve(entity);
|
|
24060
24803
|
lastSavedValues.current = values;
|
|
24061
24804
|
if (!onSubmitProp) return;
|
|
24062
24805
|
if (autoSave ?? false) {
|
|
24063
|
-
setValuesToBeSaved(
|
|
24806
|
+
setValuesToBeSaved(valuesToSave);
|
|
24064
24807
|
return Promise.resolve();
|
|
24065
24808
|
}
|
|
24066
24809
|
return Promise.resolve(onSubmitProp(valuesToSave, formex)).then((result) => {
|
|
@@ -24117,23 +24860,45 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24117
24860
|
uniqueFieldValidator
|
|
24118
24861
|
]);
|
|
24119
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);
|
|
24120
24880
|
useEffect(() => {
|
|
24121
|
-
|
|
24122
|
-
|
|
24123
|
-
|
|
24124
|
-
|
|
24125
|
-
|
|
24126
|
-
|
|
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;
|
|
24127
24900
|
});
|
|
24128
|
-
}, [
|
|
24129
|
-
formex.isSubmitting,
|
|
24130
|
-
autoSave,
|
|
24131
|
-
underlyingChanges,
|
|
24132
|
-
entity,
|
|
24133
|
-
formex.values,
|
|
24134
|
-
formex.touched,
|
|
24135
|
-
formex.setFieldValue
|
|
24136
|
-
]);
|
|
24901
|
+
}, [baseInitialValues]);
|
|
24137
24902
|
const formFieldKeys = getFormFieldKeys(collection);
|
|
24138
24903
|
const layout = useMemo(() => resolveFormLayout({
|
|
24139
24904
|
collection,
|
|
@@ -24210,7 +24975,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
|
|
|
24210
24975
|
console.warn(`Property ${field.key} not found in collection ${collection.name} in properties or additional fields. Skipping.`);
|
|
24211
24976
|
return null;
|
|
24212
24977
|
}
|
|
24213
|
-
const underlyingValueHasChanged =
|
|
24978
|
+
const underlyingValueHasChanged = field.key in underlyingChanges && !deepEqual(formex.values[field.key], underlyingChanges[field.key]);
|
|
24214
24979
|
const disabled = isFieldDisabled(property);
|
|
24215
24980
|
const selfLabelling = isSelfLabellingProperty(property);
|
|
24216
24981
|
const cmsFormFieldProps = {
|
|
@@ -25620,6 +26385,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
|
|
|
25620
26385
|
return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
|
|
25621
26386
|
}
|
|
25622
26387
|
//#endregion
|
|
25623
|
-
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 };
|
|
25624
26389
|
|
|
25625
|
-
//# sourceMappingURL=util-
|
|
26390
|
+
//# sourceMappingURL=util-DybQBkug.js.map
|