@rebasepro/admin 0.10.0 → 0.10.1-canary.14e53ae

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.
Files changed (38) hide show
  1. package/dist/{CollectionEditorDialog-BOlKGGg7.js → CollectionEditorDialog-BqVYCTmU.js} +3 -3
  2. package/dist/{CollectionEditorDialog-BOlKGGg7.js.map → CollectionEditorDialog-BqVYCTmU.js.map} +1 -1
  3. package/dist/{PropertyEditView-i51tFOfM.js → PropertyEditView-CIX85COE.js} +2 -2
  4. package/dist/{PropertyEditView-i51tFOfM.js.map → PropertyEditView-CIX85COE.js.map} +1 -1
  5. package/dist/{RouterCollectionsStudioView-Dh4W53PU.js → RouterCollectionsStudioView-_1RsFWS0.js} +4 -4
  6. package/dist/{RouterCollectionsStudioView-Dh4W53PU.js.map → RouterCollectionsStudioView-_1RsFWS0.js.map} +1 -1
  7. package/dist/collection_editor_ui.js +4 -4
  8. package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +2 -0
  9. package/dist/{export-BM6HPujv.js → export-CIi-Zrsn.js} +2 -2
  10. package/dist/{export-BM6HPujv.js.map → export-CIi-Zrsn.js.map} +1 -1
  11. package/dist/{history-CtIPJxvQ.js → history-BojXI3uS.js} +2 -2
  12. package/dist/{history-CtIPJxvQ.js.map → history-BojXI3uS.js.map} +1 -1
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/useResolvedUsers.d.ts +8 -0
  15. package/dist/{import-DzQnT03v.js → import-SVoKfekq.js} +2 -2
  16. package/dist/{import-DzQnT03v.js.map → import-SVoKfekq.js.map} +1 -1
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +7 -7
  19. package/dist/preview/components/UserPreview.d.ts +3 -2
  20. package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
  21. package/dist/util/previews.d.ts +18 -0
  22. package/dist/{util-XFwQ9FEc.js → util-aJuPuy_1.js} +198 -65
  23. package/dist/util-aJuPuy_1.js.map +1 -0
  24. package/package.json +8 -8
  25. package/src/components/CollectionViewBinding/usePreviewSlots.ts +41 -10
  26. package/src/components/DetailViewBinding.tsx +12 -5
  27. package/src/components/EntityPreviewBinding.tsx +2 -2
  28. package/src/components/SideDialogs.tsx +1 -1
  29. package/src/form/EntityForm.tsx +12 -5
  30. package/src/hooks/index.ts +1 -0
  31. package/src/hooks/useResolvedUsers.ts +143 -0
  32. package/src/index.ts +2 -1
  33. package/src/preview/components/ReferencePreview.tsx +2 -2
  34. package/src/preview/components/RelationPreview.tsx +2 -2
  35. package/src/preview/components/UserPreview.tsx +13 -4
  36. package/src/preview/property_previews/StringPropertyPreview.tsx +15 -9
  37. package/src/util/previews.ts +35 -46
  38. package/dist/util-XFwQ9FEc.js.map +0 -1
@@ -2,7 +2,7 @@ import * as React$1 from "react";
2
2
  import React, { Suspense, createContext, createElement, forwardRef, lazy, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { deepEqual } from "fast-equals";
4
4
  import { Alert, AlertCircleIcon, AlertTriangleIcon, AlignLeftIcon, ArrowLeftIcon, ArrowRightToLineIcon, Badge, BooleanSwitchWithLabel, Button, CHIP_COLORS, 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, LinkIcon, ListIcon, ListOrderedIcon, ListView, LoaderIcon, LoadingButton, MailIcon, Markdown, Maximize2Icon, Menu, MenuItem, MinusIcon, MoreVerticalIcon, MultiSelect, MultiSelectItem, 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, TableView, Tabs, TextField, TextIcon, TextareaAutosize, ToggleButtonGroup, Tooltip, Trash2Icon, Typography, UploadIcon, UserIcon, VirtualTableDateField, VirtualTableInput, VirtualTableInput as VirtualTableInput$1, VirtualTableNumberInput, VirtualTableSwitch, VoteIcon, XCircleIcon, XIcon, cls, coolIconKeys, createVirtualTableSelectionStore, debounce, defaultBorderMixin, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, fieldBackgroundInvisibleMixin, fieldBackgroundMixin, focusedDisabled, getColorSchemeForSeed, iconSize, lucideIcons, paperMixin, useDebouncedCallback, useInjectStyles, useOutsideAlerter } from "@rebasepro/ui";
5
- import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, addInitialSlash, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getLabelOrConfigFrom, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, isHidden, isPropertyBuilder, isReadOnly, normalizeToEntityRelation, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveDefaultSelectedView, resolveEnumValues, resolveFilterOperators, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
5
+ import { CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, addInitialSlash, canReadCollection, enumToObjectEntries, getArrayResolvedProperties, getCollectionBySlugWithin, getCollectionPathsCombinations, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEntityImagePreviewPropertyKey, getLabelOrConfigFrom, getLastSegment as getLastSegment$1, getLocalChangesBackup, getNavigationEntriesFromPath, getParentReferencesFromPath, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTitlePropertyCandidates, getTitlePropertyKey, getTitlePropertyKeyForValues, isHidden, isPropertyBuilder, isReadOnly, looksLikeIdentifierValue, normalizeToEntityRelation, removeInitialAndTrailingSlashes, removeInitialAndTrailingSlashes as removeInitialAndTrailingSlashes$1, removeInitialSlash, removeTrailingSlash, removeTrailingSlash as removeTrailingSlash$1, resolveArrayProperties, resolveCollectionPathIds, resolveCollectionPathIds as resolveCollectionPathIds$1, resolveDefaultSelectedView, resolveEnumValues, resolveFilterOperators, resolveProperty, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource } from "@rebasepro/common";
6
6
  import { ADDITIONAL_TAB_WIDTH, CONTAINER_FULL_WIDTH, CollectionScopeProvider, DEFAULT_PAGE_SIZE, ErrorTooltip, ErrorView, FORM_CONTAINER_WIDTH, IconForView, PluginProviderStack, StorageSourceContext, UnsavedChangesDialog, UserDisplay, buildEnumLabel, createFormexStub, deleteEntityWithCallbacks, flattenKeys, getColorScheme, getColumnKeysForProperty, getEntityFromCache, getEntityFromMemoryCache, getFormFieldKeys, getIcon, getRelationIncludeParams, getRowHeight, iconsSearch, removeEntityFromCache, removeEntityFromMemoryCache, resolveComponentRef, saveEntityToCache, saveEntityToMemoryCache, saveEntityWithCallbacks, useAnalyticsController, useApiConfig, useAuthController, useCollectionScope, useColumnIds, useComponentOverride, useCustomizationController, useData, useDataTableController, useFetch, useLargeLayout, useModeController, useNavigationBlocker, usePermissions, useRebaseClient, useRebaseContext, useRelationSelector, useResolvedComponent, useScrollRestoration, useSlot, useSnackbarController, useStorageSource, useStorageSources, useStorageUploadController, useTranslation, useUnsavedChangesDialog, useUserConfigurationPersistence } from "@rebasepro/app";
7
7
  import { Field, Formex, getIn, setIn, useCreateFormex, useFormex } from "@rebasepro/forms";
8
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -540,7 +540,7 @@ function EnumValuesChip({ enumValues, enumKey, size, className, children }) {
540
540
  /**
541
541
  * @group Preview components
542
542
  */
543
- function StringPropertyPreview({ propertyKey, value, property, size }) {
543
+ function StringPropertyPreview({ propertyKey, value, property, size, textOnly }) {
544
544
  const strValue = value;
545
545
  if (property.enum) return /* @__PURE__ */ jsx(EnumValuesChip, {
546
546
  enumKey: strValue,
@@ -559,10 +559,16 @@ function StringPropertyPreview({ propertyKey, value, property, size }) {
559
559
  });
560
560
  else {
561
561
  if (!strValue) return /* @__PURE__ */ jsx(Fragment, {});
562
- const lines = strValue.split("\n");
563
- return strValue && strValue.includes("\n") ? /* @__PURE__ */ jsx("div", {
564
- className: cls("overflow-x-scroll overflow-hidden", size === "small" ? "text-sm" : ""),
565
- children: lines.map((str, index) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx("span", { children: str }), index !== lines.length - 1 && /* @__PURE__ */ jsx("br", {})] }, `string_preview_${index}`))
562
+ if (textOnly) {
563
+ const singleLine = strValue.replace(/\s*\n\s*/g, " ");
564
+ return size === "small" ? /* @__PURE__ */ jsx("span", {
565
+ className: "text-sm",
566
+ children: singleLine
567
+ }) : /* @__PURE__ */ jsx(Fragment, { children: singleLine });
568
+ }
569
+ return strValue.includes("\n") ? /* @__PURE__ */ jsx("div", {
570
+ className: cls("whitespace-pre-line line-clamp-3 overflow-hidden", size === "small" ? "text-sm" : ""),
571
+ children: strValue
566
572
  }) : size === "small" ? /* @__PURE__ */ jsx("span", {
567
573
  className: "text-sm",
568
574
  children: strValue
@@ -776,38 +782,34 @@ function getEntityPreviewKeys(authController, targetCollection, fields, previewP
776
782
  }).slice(0, limit);
777
783
  }
778
784
  }
785
+ /**
786
+ * The property that fills the title slot for a collection.
787
+ *
788
+ * Ranking lives in `@rebasepro/common` so every surface (list rows, cards,
789
+ * board cards, detail header, relation chips) agrees on what an entity is
790
+ * called. Identifiers are excluded from the property *schema* — primary keys,
791
+ * foreign keys, UUID columns — not from the key name.
792
+ */
779
793
  function getEntityTitlePropertyKey(collection, propertyConfigs) {
780
- if (collection.titleProperty) return collection.titleProperty;
781
- const hasExplicitOrder = !!collection.propertiesOrder;
782
- const orderToSearch = collection.propertiesOrder || Object.keys(collection.properties);
783
- if (hasExplicitOrder) for (const key of orderToSearch) {
784
- const property = collection.properties[key];
785
- if (property && !isPropertyBuilder(property)) {
786
- const prop = property;
787
- if (prop && typeof prop === "object" && "isId" in prop && Boolean(prop.isId) || key === "id" || isHiddenProperty(prop)) continue;
788
- if (prop.type === "relation" || prop.type === "string" && !isStorageProperty$1(prop)) return key;
789
- }
790
- }
791
- let firstStringCandidate;
792
- for (const key of orderToSearch) {
793
- const property = collection.properties[key];
794
- if (property && !isPropertyBuilder(property)) {
795
- const prop = property;
796
- if (isHiddenProperty(prop)) continue;
797
- if (prop.type === "string" && !prop.ui?.multiline && !prop.ui?.markdown && !prop.storage && !prop.isId) {
798
- if (!firstStringCandidate) firstStringCandidate = key;
799
- const lowerKey = key.toLowerCase();
800
- if ([
801
- "name",
802
- "title",
803
- "label",
804
- "displayname",
805
- "username"
806
- ].includes(lowerKey)) return key;
807
- }
808
- }
809
- }
810
- return firstStringCandidate;
794
+ return getTitlePropertyKey(collection);
795
+ }
796
+ /**
797
+ * Same as {@link getEntityTitlePropertyKey}, but skips candidates that carry
798
+ * no readable value for this particular entity (empty, or an opaque id).
799
+ */
800
+ function getEntityTitlePropertyKeyForEntity(collection, values, entityId) {
801
+ return getTitlePropertyKeyForValues(collection, values, entityId);
802
+ }
803
+ /**
804
+ * True when a property key points at a user picker, whose stored value is an
805
+ * auth user id that has to be resolved before it can be displayed.
806
+ */
807
+ function isUserSelectProperty(collection, propertyKey) {
808
+ if (!propertyKey) return false;
809
+ const property = collection.properties[propertyKey];
810
+ if (!property || isPropertyBuilder(property)) return false;
811
+ const prop = property;
812
+ return prop.type === "string" && Boolean(prop.userSelect);
811
813
  }
812
814
  /**
813
815
  * Attempt to turn a title value (which might be a relation, reference, date, array, or other complex object)
@@ -895,7 +897,7 @@ function EntityPreviewBindingData({ actions, collection: collectionProp, preview
895
897
  customizationController.propertyConfigs
896
898
  ]);
897
899
  if (!collection) return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorView, { error: `Collection not found: ${entity.path}` }) });
898
- const titleProperty = includeTitle ? getEntityTitlePropertyKey(collection, customizationController.propertyConfigs) : void 0;
900
+ const titleProperty = includeTitle ? getEntityTitlePropertyKeyForEntity(collection, entity.values, entity.id) : void 0;
899
901
  const imagePropertyKey = includeImage ? getEntityImagePreviewPropertyKey(collection) : void 0;
900
902
  const imageProperty = imagePropertyKey ? collection.properties[imagePropertyKey] : void 0;
901
903
  const ofProp = imageProperty && "of" in imageProperty ? imageProperty.of : void 0;
@@ -1109,7 +1111,7 @@ function ReferencePreviewInternal(props) {
1109
1111
  }
1110
1112
  function ReferencePreviewExisting({ reference, collection, previewProperties, size, disabled, includeEntityLink, includeId, onClick, hover, textOnly }) {
1111
1113
  const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
1112
- const customizationController = useCustomizationController();
1114
+ useCustomizationController();
1113
1115
  const { entity, dataLoading, dataLoadingError } = useFetch({
1114
1116
  path: reference.path,
1115
1117
  entityId: reference.id,
@@ -1152,7 +1154,7 @@ function ReferencePreviewExisting({ reference, collection, previewProperties, si
1152
1154
  });
1153
1155
  }
1154
1156
  if (textOnly) {
1155
- const titleProperty = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
1157
+ const titleProperty = getEntityTitlePropertyKeyForEntity(collection, usedEntity.values, usedEntity.id);
1156
1158
  const titleValue = titleProperty ? getValueInPath(usedEntity.values, titleProperty) : void 0;
1157
1159
  return /* @__PURE__ */ jsx("span", {
1158
1160
  className: "truncate",
@@ -1301,7 +1303,7 @@ function RelationPreviewInternal(props) {
1301
1303
  function RelationPreviewExisting({ relation, collection, previewProperties, size, disabled, includeEntityLink, includeId, onClick, hover, textOnly }) {
1302
1304
  const passedEntity = relation.data;
1303
1305
  const ResolvedEntityPreview = useComponentOverride("EntityPreview", EntityPreviewBinding);
1304
- const customizationController = useCustomizationController();
1306
+ useCustomizationController();
1305
1307
  const { entity, dataLoading, dataLoadingError } = useFetch({
1306
1308
  path: relation.path,
1307
1309
  entityId: passedEntity ? void 0 : relation.id,
@@ -1344,7 +1346,7 @@ function RelationPreviewExisting({ relation, collection, previewProperties, size
1344
1346
  });
1345
1347
  }
1346
1348
  if (textOnly) {
1347
- const titleProperty = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
1349
+ const titleProperty = getEntityTitlePropertyKeyForEntity(collection, usedEntity.values, usedEntity.id);
1348
1350
  const titleValue = titleProperty ? getValueInPath(usedEntity.values, titleProperty) : void 0;
1349
1351
  return /* @__PURE__ */ jsx("span", {
1350
1352
  className: "truncate",
@@ -1679,21 +1681,129 @@ function NumberPropertyPreview({ value, property, size }) {
1679
1681
  });
1680
1682
  }
1681
1683
  //#endregion
1684
+ //#region src/hooks/useResolvedUsers.ts
1685
+ var caches = /* @__PURE__ */ new Map();
1686
+ function getCache(baseUrl) {
1687
+ let cache = caches.get(baseUrl);
1688
+ if (!cache) {
1689
+ cache = {
1690
+ users: /* @__PURE__ */ new Map(),
1691
+ pending: /* @__PURE__ */ new Map(),
1692
+ queue: /* @__PURE__ */ new Set()
1693
+ };
1694
+ caches.set(baseUrl, cache);
1695
+ }
1696
+ return cache;
1697
+ }
1698
+ async function fetchUsers(baseUrl, ids, token) {
1699
+ const resolved = /* @__PURE__ */ new Map();
1700
+ for (const id of ids) resolved.set(id, null);
1701
+ const params = new URLSearchParams({ ids: ids.join(",") });
1702
+ const response = await fetch(`${baseUrl}/api/users?${params}`, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
1703
+ if (!response.ok) return resolved;
1704
+ const data = await response.json();
1705
+ const rows = data.users ?? data.data ?? [];
1706
+ for (const row of rows) {
1707
+ const uid = row.uid ?? row.id;
1708
+ if (!uid) continue;
1709
+ resolved.set(uid, {
1710
+ uid,
1711
+ email: row.email ?? "",
1712
+ displayName: row.displayName ?? row.display_name ?? null,
1713
+ photoURL: row.photoURL ?? row.photo_url ?? null,
1714
+ providerId: "custom",
1715
+ isAnonymous: false,
1716
+ roles: row.roles ?? []
1717
+ });
1718
+ }
1719
+ return resolved;
1720
+ }
1721
+ function requestUser(baseUrl, uid, getAuthToken) {
1722
+ const cache = getCache(baseUrl);
1723
+ const existing = cache.pending.get(uid);
1724
+ if (existing) return existing;
1725
+ cache.queue.add(uid);
1726
+ const promise = new Promise((resolve, reject) => {
1727
+ if (cache.flush) clearTimeout(cache.flush);
1728
+ cache.flush = setTimeout(async () => {
1729
+ const ids = [...cache.queue];
1730
+ cache.queue.clear();
1731
+ cache.flush = void 0;
1732
+ try {
1733
+ const resolved = await fetchUsers(baseUrl, ids, await getAuthToken?.());
1734
+ for (const id of ids) {
1735
+ cache.users.set(id, resolved.get(id) ?? null);
1736
+ cache.pending.delete(id);
1737
+ }
1738
+ resolve();
1739
+ } catch (error) {
1740
+ for (const id of ids) cache.pending.delete(id);
1741
+ reject(error);
1742
+ }
1743
+ }, 0);
1744
+ });
1745
+ cache.pending.set(uid, promise);
1746
+ return promise;
1747
+ }
1748
+ /**
1749
+ * The auth user behind a `userSelect` value, or `null` while it is loading or
1750
+ * when it cannot be resolved.
1751
+ */
1752
+ function useResolvedUser(uid) {
1753
+ const client = useRebaseClient();
1754
+ const { getAuthToken } = useAuthController();
1755
+ const baseUrl = client?.baseUrl;
1756
+ const [user, setUser] = useState(() => baseUrl && uid ? getCache(baseUrl).users.get(uid) ?? null : null);
1757
+ useEffect(() => {
1758
+ if (!baseUrl || !uid) {
1759
+ setUser(null);
1760
+ return;
1761
+ }
1762
+ const cache = getCache(baseUrl);
1763
+ if (cache.users.has(uid)) {
1764
+ setUser(cache.users.get(uid) ?? null);
1765
+ return;
1766
+ }
1767
+ let active = true;
1768
+ requestUser(baseUrl, uid, getAuthToken).then(() => {
1769
+ if (active) setUser(getCache(baseUrl).users.get(uid) ?? null);
1770
+ }).catch(() => {
1771
+ if (active) setUser(null);
1772
+ });
1773
+ return () => {
1774
+ active = false;
1775
+ };
1776
+ }, [
1777
+ baseUrl,
1778
+ uid,
1779
+ getAuthToken
1780
+ ]);
1781
+ return user;
1782
+ }
1783
+ /** Human-readable label for a resolved user. */
1784
+ function getUserLabel(user) {
1785
+ return user.displayName || user.email || user.uid;
1786
+ }
1787
+ //#endregion
1682
1788
  //#region src/preview/components/UserPreview.tsx
1683
1789
  /**
1684
1790
  * Preview component for displaying a user reference value.
1685
1791
  *
1686
- * Shows the raw user ID. Collection-based user management means
1687
- * user records are fetched through the standard data layer.
1792
+ * Resolves the stored auth user id to a name (or email) the same way a
1793
+ * relation resolves its target, and falls back to the raw id only while the
1794
+ * lookup is in flight or when the user cannot be resolved.
1688
1795
  *
1689
1796
  * @group Preview components
1690
1797
  */
1691
1798
  function UserPreview({ value }) {
1692
- if (typeof value !== "string" || !value) return /* @__PURE__ */ jsx(EmptyValue, {});
1799
+ const uid = typeof value === "string" && value ? value : void 0;
1800
+ const user = useResolvedUser(uid);
1801
+ if (!uid) return /* @__PURE__ */ jsx(EmptyValue, {});
1693
1802
  return /* @__PURE__ */ jsx(Typography, {
1694
1803
  variant: "caption",
1695
1804
  color: "secondary",
1696
- children: value
1805
+ className: "truncate",
1806
+ children: user ? getUserLabel(user) : uid
1697
1807
  });
1698
1808
  }
1699
1809
  //#endregion
@@ -7295,8 +7405,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
7295
7405
  }
7296
7406
  //#endregion
7297
7407
  //#region src/components/CollectionViewBinding/CollectionViewActions.tsx
7298
- var ImportCollectionAction = lazy(() => import("./import-DzQnT03v.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
7299
- var ExportCollectionAction = lazy(() => import("./export-BM6HPujv.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
7408
+ var ImportCollectionAction = lazy(() => import("./import-SVoKfekq.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
7409
+ var ExportCollectionAction = lazy(() => import("./export-CIi-Zrsn.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
7300
7410
  function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
7301
7411
  const context = useCMSContext();
7302
7412
  const { canCreate, canDelete } = usePermissions();
@@ -7408,7 +7518,8 @@ function CollectionViewActions({ collection, relativePath, parentCollectionSlugs
7408
7518
  * Pure function — call inside `useMemo`.
7409
7519
  */
7410
7520
  function resolveCollectionSlotKeys(collection, authController, propertyConfigs) {
7411
- const titleKey = getEntityTitlePropertyKey(collection, propertyConfigs);
7521
+ const titleKeyCandidates = getTitlePropertyCandidates(collection);
7522
+ const titleKey = titleKeyCandidates[0];
7412
7523
  const imageKey = getEntityImagePreviewPropertyKey(collection);
7413
7524
  let statusKey;
7414
7525
  if (!statusKey && collection.previewProperties) for (const key of collection.previewProperties) {
@@ -7473,6 +7584,7 @@ function resolveCollectionSlotKeys(collection, authController, propertyConfigs)
7473
7584
  });
7474
7585
  return {
7475
7586
  titleKey,
7587
+ titleKeyCandidates,
7476
7588
  imageKey,
7477
7589
  subtitleKey: sortedPreviewKeys.length > 0 ? sortedPreviewKeys[0] : void 0,
7478
7590
  relationKeys,
@@ -7499,6 +7611,24 @@ function resolveImageSlot(collection, imageKey, entity) {
7499
7611
  value: resolvedValue
7500
7612
  };
7501
7613
  }
7614
+ /**
7615
+ * Walk the collection's ranked title candidates and return the first one that
7616
+ * holds something a person can read for this entity. Candidates are already
7617
+ * ordered and free of identifier properties; this only rejects values —
7618
+ * empties, and strings that turn out to be an id at runtime.
7619
+ */
7620
+ function resolveEntityTitleKey(entity, candidates) {
7621
+ for (const key of candidates) {
7622
+ const value = getValueInPath(entity.values, key);
7623
+ if (value === void 0 || value === null || value === "") continue;
7624
+ if (typeof value === "string") {
7625
+ if (looksLikeIdentifierValue(value)) continue;
7626
+ if (value === String(entity.id)) continue;
7627
+ }
7628
+ return key;
7629
+ }
7630
+ return candidates[0];
7631
+ }
7502
7632
  function formatDateValue$1(value) {
7503
7633
  if (!value) return "";
7504
7634
  const date = value instanceof Date ? value : new Date(value);
@@ -7538,20 +7668,21 @@ function useCollectionSlotKeys(collection, authController, propertyConfigs) {
7538
7668
  * `React.memo` render functions or loops without violating hook rules.
7539
7669
  */
7540
7670
  function resolveEntitySlots(entity, collection, slotKeys) {
7541
- const { titleKey, imageKey, subtitleKey, relationKeys, statusKey, dateKey } = slotKeys;
7671
+ const { titleKey, titleKeyCandidates, imageKey, subtitleKey, relationKeys, statusKey, dateKey } = slotKeys;
7542
7672
  const image = imageKey ? resolveImageSlot(collection, imageKey, entity) : void 0;
7673
+ const resolvedTitleKey = resolveEntityTitleKey(entity, titleKeyCandidates ?? (titleKey ? [titleKey] : []));
7543
7674
  let title;
7544
- if (titleKey) {
7545
- const prop = collection.properties[titleKey];
7546
- const val = getValueInPath(entity.values, titleKey);
7675
+ if (resolvedTitleKey) {
7676
+ const prop = collection.properties[resolvedTitleKey];
7677
+ const val = getValueInPath(entity.values, resolvedTitleKey);
7547
7678
  if (prop) title = {
7548
7679
  property: prop,
7549
- propertyKey: titleKey,
7680
+ propertyKey: resolvedTitleKey,
7550
7681
  value: val
7551
7682
  };
7552
7683
  }
7553
7684
  let subtitle;
7554
- if (subtitleKey) {
7685
+ if (subtitleKey && subtitleKey !== resolvedTitleKey) {
7555
7686
  const prop = collection.properties[subtitleKey];
7556
7687
  const val = getValueInPath(entity.values, subtitleKey);
7557
7688
  if (prop && val !== void 0 && val !== null && val !== "") subtitle = {
@@ -8740,7 +8871,7 @@ var MAIN_TAB_VALUE = "__main_##Q$SC^#S6";
8740
8871
  var JSON_TAB_VALUE = "__json";
8741
8872
  //#endregion
8742
8873
  //#region src/components/DetailViewBinding.tsx
8743
- var EntityHistoryView$1 = lazy(() => import("./history-CtIPJxvQ.js").then((m) => ({ default: m.EntityHistoryView })));
8874
+ var EntityHistoryView$1 = lazy(() => import("./history-BojXI3uS.js").then((m) => ({ default: m.EntityHistoryView })));
8744
8875
  function DetailViewBinding({ entityId, ...props }) {
8745
8876
  const { entity, dataLoading, dataLoadingError } = useFetch({
8746
8877
  path: props.path,
@@ -8881,9 +9012,10 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
8881
9012
  ]));
8882
9013
  const formViewConfig = collection.formView;
8883
9014
  const FormViewBuilder = formViewConfig?.Builder ? resolveComponentRef(formViewConfig.Builder) : null;
8884
- const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
9015
+ const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, usedEntity?.values, usedEntity?.id);
8885
9016
  const rawTitle = usedEntity?.values && titlePropertyKey ? getValueInPath(usedEntity.values, titlePropertyKey) : void 0;
8886
- const title = rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
9017
+ const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
9018
+ const title = titleUser ? getUserLabel(titleUser) : rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
8887
9019
  const nonActionCustomViews = useMemo(() => resolvedEntityViews.filter((e) => !e.includeActions), [resolvedEntityViews]);
8888
9020
  const customViewsView = customViews && nonActionCustomViews.map((customView) => {
8889
9021
  if (!customView) return null;
@@ -13303,7 +13435,7 @@ function EntityActionButton({ action, enabled, props }) {
13303
13435
  }
13304
13436
  //#endregion
13305
13437
  //#region src/components/EditViewBinding.tsx
13306
- var EntityHistoryView = lazy(() => import("./history-CtIPJxvQ.js").then((m) => ({ default: m.EntityHistoryView })));
13438
+ var EntityHistoryView = lazy(() => import("./history-BojXI3uS.js").then((m) => ({ default: m.EntityHistoryView })));
13307
13439
  /**
13308
13440
  * This is the default view that is used as the content of a side panel when
13309
13441
  * a record is opened.
@@ -15410,7 +15542,7 @@ function SideDialogView({ offsetPosition, panel, panelIndex, isTopPanel }) {
15410
15542
  maxWidth: "4xl",
15411
15543
  scrollable: false,
15412
15544
  children: panel && /* @__PURE__ */ jsx("div", {
15413
- className: "flex flex-col h-[75vh] min-h-[500px] min-w-[55vw] max-w-full overflow-hidden",
15545
+ className: "flex flex-col h-[75vh] min-h-[500px] w-full min-w-0 max-w-full overflow-hidden",
15414
15546
  children: /* @__PURE__ */ jsx(ErrorBoundary, { children: panel.component })
15415
15547
  })
15416
15548
  }) : /* @__PURE__ */ jsxs(Sheet, {
@@ -18852,7 +18984,7 @@ function zodToFormErrors(zodError) {
18852
18984
  * @group Components
18853
18985
  */
18854
18986
  function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, forceActionsAtTheBottom, initialStatus, className, onStatusChange, onEntityChange, openEntityMode = "full_screen", formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent = EntityFormActions, showDefaultActions = true, showEntityPath = true, navigateBack: navigateBackProp, children, onSubmit: onSubmitProp, onValuesChangeDeferred: onValuesChangeDeferredProp, onReset: onResetProp, uniqueFieldValidator: uniqueFieldValidatorProp, beforeFields, afterFields, pluginActions: pluginActionsProp, computedInitialValues, hasLocalChanges: hasLocalChangesProp, localChangesData, manualApplyLocalChanges, localChangesCacheKey, onClearLocalChanges }) {
18855
- const customizationController = useCustomizationController();
18987
+ useCustomizationController();
18856
18988
  const navigateBack = useCallback(() => {
18857
18989
  if (navigateBackProp) navigateBackProp();
18858
18990
  }, [navigateBackProp]);
@@ -19012,9 +19144,10 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
19012
19144
  path
19013
19145
  ]);
19014
19146
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
19015
- const titlePropertyKey = getEntityTitlePropertyKey(collection, customizationController.propertyConfigs);
19147
+ const titlePropertyKey = getEntityTitlePropertyKeyForEntity(collection, formex.values, entityId);
19016
19148
  const rawTitle = formex.values && titlePropertyKey ? getValueInPath(formex.values, titlePropertyKey) : void 0;
19017
- const title = rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
19149
+ const titleUser = useResolvedUser(isUserSelectProperty(collection, titlePropertyKey) && typeof rawTitle === "string" ? rawTitle : void 0);
19150
+ const title = titleUser ? getUserLabel(titleUser) : rawTitle !== void 0 && rawTitle !== null ? resolveTitleToString(rawTitle) : collection.singularName ?? collection.name;
19018
19151
  const modified = formex.dirty;
19019
19152
  useEffect(() => {
19020
19153
  if (!autoSave) onValuesModified?.(modified, formex.values);
@@ -20280,6 +20413,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
20280
20413
  return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
20281
20414
  }
20282
20415
  //#endregion
20283
- export { useHistory as $, renderSkeletonImageThumbnail as $n, SelectableTable as $t, VectorFieldBinding as A, SidePanelControllerContext as An, unflattenObject as At, KeyValueFieldBinding as B, getPropertiesWithPropertiesOrder as Bn, getCollectionPathsCombinations as Bt, zodToFormErrors as C, ArrayPropertyEnumPreview as Cn, flattenEntry as Ct, getDefaultFieldId as D, ArrayOfReferencesPreview as Dn, ImportSaveInProgress as Dt, getDefaultFieldConfig as E, RelationPreview as En, useImportConfig as Et, RepeatFieldBinding as F, getEntityTitlePropertyKey as Fn, useBreadcrumbsController as Ft, SelectionTableBinding as G, ArrayPropertyPreview as Gn, resolveCollectionPathIds$1 as Gt, BlockFieldBinding as H, getResolvedPropertyInPath as Hn, removeInitialAndTrailingSlashes$1 as Ht, ReferenceFieldBinding as I, getBracketNotation as In, BreadcrumbsProvider as It, useBuildNavigationStateController as J, StorageThumbnail as Jn, resolveEntityAction as Jt, SideDialogs as K, StringPropertyPreview as Kn, resolveOpenEntityMode as Kt, MultiSelectFieldBinding as L, getDefaultPropertiesOrder as Ln, mergeEntityActions as Lt, SwitchFieldBinding as M, CollectionRegistryContext as Mn, PropertyConfigBadge as Mt, StorageUploadFieldBinding as N, useCollectionRegistryController as Nn, SearchIconsView as Nt, getFieldConfig as O, ReferencePreview as On, ImportFileUpload as Ot, SelectFieldBinding as P, getEntityPreviewKeys as Pn, FieldCaption as Pt, useBuildCollectionRegistryController as Q, renderSkeletonIcon as Qn, CollectionTableBinding as Qt, MarkdownEditorFieldBinding as R, getIconForProperty as Rn, addInitialSlash as Rt, removeEmptyContainers as S, ArrayOfStringsPreview as Sn, convertDataToEntity as St, DEFAULT_FIELD_CONFIGS as T, ArrayOfStorageComponentsPreview as Tn, getInferenceType as Tt, ArrayOfReferencesFieldBinding as U, isReferenceProperty as Un, removeInitialSlash as Ut, DateTimeFieldBinding as V, getPropertyInPath as Vn, getLastSegment$1 as Vt, useSelectionDialog as W, isRelationProperty as Wn, removeTrailingSlash$1 as Wt, useResolvedViews as X, SkeletonPropertyComponent as Xn, useSelectionController as Xt, useTopLevelNavigation as Y, StorageThumbnailInternal as Yn, resolveEntityView as Yt, useResolvedCollections as Z, renderSkeletonCaptionText as Zn, VirtualTableInput$1 as Zt, EntityFormBinding as _, BooleanPreview as _n, useCollectionEditorController as _t, namespaceToPropertiesPath as a, useNavigationStateController as an, LabelWithIconAndTooltip as ar, EditViewBinding as at, getChanges as b, MapPropertyPreview as bn, ImportNewPropertyFieldPreview as bt, buildCollectionGenerationCallback as c, SelectableTableContext as cn, FormLayout as cr, deleteEntityAction as ct, fromSerializableProperties as d, ReadOnlyFieldBinding as dn, CreationResultDialog as dt, CollectionRowActions as en, renderSkeletonText as er, resolveNavigationFrom as et, fromSerializableProperty as f, FieldHelperText as fn, DetailViewBinding as ft, useCollectionsConfigController as g, NumberPropertyPreview as gn, CollectionViewActions as gt, toSerializableProperty as h, UserPreview as hn, EntityCardBinding as ht, namespaceToPropertiesOrderPath as i, NavigationStateContext as in, EmptyValue as ir, useBuildSidePanel as it, TextFieldBinding as j, useSidePanel as jn, ArrayContainer as jt, getFieldId as k, EntityPreviewBinding as kn, convertFileToJson as kt, validateCollectionJson as l, ArrayCustomShapedFieldBinding as ln, FormEntry as lr, editEntityAction as lt, toSerializableProperties as m, PropertyPreview as mn, CollectionCardViewBinding as mt, getFullIdPath as n, UrlContext as nn, ImagePreview as nr, buildSidePanelsFromUrl as nt, CollectionGenerationApiError as o, useSideDialogsController as on, PropertyIdCopyTooltip as or, CollectionViewBinding as ot, toSerializableCollectionConfig as p, ArrayOfMapsPreview as pn, EntityViewBinding as pt, useBuildUrlController as q, EnumValuesChip as qn, resolveViewMode as qt, idToPropertiesPath as r, useUrlController as rn, sanitizeUrl as rr, getEntityViewWidth as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, SideDialogsControllerContext as sn, LabelWithIcon as sr, copyEntityAction as st, getFullId as t, useCMSContext as tn, UrlComponentPreview as tr, useResolvedNavigationFrom as tt, fromSerializableCollectionConfig as u, useClearRestoreValue as un, resetPasswordAction as ut, EntityForm as v, DatePreview as vn, ConfigControllerProvider as vt, PropertyFieldBinding as w, ArrayEnumPreview as wn, processValueMapping as wt, getInitialEntityValues as x, ArrayOneOfPreview as xn, DataNewPropertiesMapping as xt, extractTouchedValues as y, KeyValuePreview as yn, useCollectionEditorDialogsState as yt, MapFieldBinding as z, getIconForWidget as zn, getCollectionBySlugWithin as zt };
20416
+ export { useHistory as $, SkeletonPropertyComponent as $n, SelectableTable as $t, VectorFieldBinding as A, ReferencePreview as An, unflattenObject as At, KeyValueFieldBinding as B, getDefaultPropertiesOrder as Bn, getCollectionPathsCombinations as Bt, zodToFormErrors as C, ArrayOneOfPreview as Cn, flattenEntry as Ct, getDefaultFieldId as D, ArrayOfStorageComponentsPreview as Dn, ImportSaveInProgress as Dt, getDefaultFieldConfig as E, ArrayEnumPreview as En, useImportConfig as Et, RepeatFieldBinding as F, useCollectionRegistryController as Fn, useBreadcrumbsController as Ft, SelectionTableBinding as G, getResolvedPropertyInPath as Gn, resolveCollectionPathIds$1 as Gt, BlockFieldBinding as H, getIconForWidget as Hn, removeInitialAndTrailingSlashes$1 as Ht, ReferenceFieldBinding as I, getEntityPreviewKeys as In, BreadcrumbsProvider as It, useBuildNavigationStateController as J, ArrayPropertyPreview as Jn, resolveEntityAction as Jt, SideDialogs as K, isReferenceProperty as Kn, resolveOpenEntityMode as Kt, MultiSelectFieldBinding as L, getEntityTitlePropertyKey as Ln, mergeEntityActions as Lt, SwitchFieldBinding as M, SidePanelControllerContext as Mn, PropertyConfigBadge as Mt, StorageUploadFieldBinding as N, useSidePanel as Nn, SearchIconsView as Nt, getFieldConfig as O, RelationPreview as On, ImportFileUpload as Ot, SelectFieldBinding as P, CollectionRegistryContext as Pn, FieldCaption as Pt, useBuildCollectionRegistryController as Q, StorageThumbnailInternal as Qn, CollectionTableBinding as Qt, MarkdownEditorFieldBinding as R, getEntityTitlePropertyKeyForEntity as Rn, addInitialSlash as Rt, removeEmptyContainers as S, MapPropertyPreview as Sn, convertDataToEntity as St, DEFAULT_FIELD_CONFIGS as T, ArrayPropertyEnumPreview as Tn, getInferenceType as Tt, ArrayOfReferencesFieldBinding as U, getPropertiesWithPropertiesOrder as Un, removeInitialSlash as Ut, DateTimeFieldBinding as V, getIconForProperty as Vn, getLastSegment$1 as Vt, useSelectionDialog as W, getPropertyInPath as Wn, removeTrailingSlash$1 as Wt, useResolvedViews as X, EnumValuesChip as Xn, useSelectionController as Xt, useTopLevelNavigation as Y, StringPropertyPreview as Yn, resolveEntityView as Yt, useResolvedCollections as Z, StorageThumbnail as Zn, VirtualTableInput$1 as Zt, EntityFormBinding as _, useResolvedUser as _n, useCollectionEditorController as _t, namespaceToPropertiesPath as a, useNavigationStateController as an, ImagePreview as ar, EditViewBinding as at, getChanges as b, DatePreview as bn, ImportNewPropertyFieldPreview as bt, buildCollectionGenerationCallback as c, SelectableTableContext as cn, LabelWithIconAndTooltip as cr, deleteEntityAction as ct, fromSerializableProperties as d, ReadOnlyFieldBinding as dn, FormLayout as dr, CreationResultDialog as dt, CollectionRowActions as en, renderSkeletonCaptionText as er, resolveNavigationFrom as et, fromSerializableProperty as f, FieldHelperText as fn, FormEntry as fr, DetailViewBinding as ft, useCollectionsConfigController as g, getUserLabel as gn, CollectionViewActions as gt, toSerializableProperty as h, UserPreview as hn, EntityCardBinding as ht, namespaceToPropertiesOrderPath as i, NavigationStateContext as in, UrlComponentPreview as ir, useBuildSidePanel as it, TextFieldBinding as j, EntityPreviewBinding as jn, ArrayContainer as jt, getFieldId as k, ArrayOfReferencesPreview as kn, convertFileToJson as kt, validateCollectionJson as l, ArrayCustomShapedFieldBinding as ln, PropertyIdCopyTooltip as lr, editEntityAction as lt, toSerializableProperties as m, PropertyPreview as mn, CollectionCardViewBinding as mt, getFullIdPath as n, UrlContext as nn, renderSkeletonImageThumbnail as nr, buildSidePanelsFromUrl as nt, CollectionGenerationApiError as o, useSideDialogsController as on, sanitizeUrl as or, CollectionViewBinding as ot, toSerializableCollectionConfig as p, ArrayOfMapsPreview as pn, EntityViewBinding as pt, useBuildUrlController as q, isRelationProperty as qn, resolveViewMode as qt, idToPropertiesPath as r, useUrlController as rn, renderSkeletonText as rr, getEntityViewWidth as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, SideDialogsControllerContext as sn, EmptyValue as sr, copyEntityAction as st, getFullId as t, useCMSContext as tn, renderSkeletonIcon as tr, useResolvedNavigationFrom as tt, fromSerializableCollectionConfig as u, useClearRestoreValue as un, LabelWithIcon as ur, resetPasswordAction as ut, EntityForm as v, NumberPropertyPreview as vn, ConfigControllerProvider as vt, PropertyFieldBinding as w, ArrayOfStringsPreview as wn, processValueMapping as wt, getInitialEntityValues as x, KeyValuePreview as xn, DataNewPropertiesMapping as xt, extractTouchedValues as y, BooleanPreview as yn, useCollectionEditorDialogsState as yt, MapFieldBinding as z, getBracketNotation as zn, getCollectionBySlugWithin as zt };
20284
20417
 
20285
- //# sourceMappingURL=util-XFwQ9FEc.js.map
20418
+ //# sourceMappingURL=util-aJuPuy_1.js.map