@rebasepro/admin 0.15.0 → 0.16.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.
Files changed (40) hide show
  1. package/dist/{CollectionEditorDialog-Dxx0ZaDk.js → CollectionEditorDialog-D2Vf4C3-.js} +7 -7
  2. package/dist/{CollectionEditorDialog-Dxx0ZaDk.js.map → CollectionEditorDialog-D2Vf4C3-.js.map} +1 -1
  3. package/dist/{PropertyEditView-D6xNTm0d.js → PropertyEditView-D3NBsK9_.js} +4 -13
  4. package/dist/PropertyEditView-D3NBsK9_.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-C4vSn9Pu.js → RouterCollectionsStudioView-hPB54l3Q.js} +4 -4
  6. package/dist/{RouterCollectionsStudioView-C4vSn9Pu.js.map → RouterCollectionsStudioView-hPB54l3Q.js.map} +1 -1
  7. package/dist/collection_editor_ui.js +4 -4
  8. package/dist/components/CollectionViewBinding/CollectionViewStartActions.d.ts +9 -1
  9. package/dist/components/RelationSelector.d.ts +8 -0
  10. package/dist/{export-BZi1MzQ5.js → export-oauWlxuu.js} +2 -2
  11. package/dist/{export-BZi1MzQ5.js.map → export-oauWlxuu.js.map} +1 -1
  12. package/dist/form/useUndoableDiscard.d.ts +15 -0
  13. package/dist/{history-B5pkfldE.js → history-C2cqc0bw.js} +2 -2
  14. package/dist/{history-B5pkfldE.js.map → history-C2cqc0bw.js.map} +1 -1
  15. package/dist/{import-CXpuYo-T.js → import-Bxv5JPm-.js} +2 -2
  16. package/dist/{import-CXpuYo-T.js.map → import-Bxv5JPm-.js.map} +1 -1
  17. package/dist/index.js +17 -25
  18. package/dist/index.js.map +1 -1
  19. package/dist/{util-BKtHPLe4.js → util-C4qsI1QV.js} +163 -55
  20. package/dist/util-C4qsI1QV.js.map +1 -0
  21. package/package.json +12 -9
  22. package/src/collection_editor/ui/collection_editor/CollectionRLSTab.tsx +4 -4
  23. package/src/components/CollectionPanel.tsx +1 -1
  24. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +1 -1
  25. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +3 -11
  26. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +32 -2
  27. package/src/components/CollectionViewBinding/EntityCardBinding.tsx +10 -8
  28. package/src/components/DefaultAppBar.tsx +0 -10
  29. package/src/components/DetailViewBinding.tsx +18 -1
  30. package/src/components/EditViewBinding.tsx +26 -2
  31. package/src/components/ReferenceTable/SelectionTableBinding.tsx +10 -1
  32. package/src/components/RelationSelector.tsx +115 -3
  33. package/src/contexts/BreacrumbsContext.tsx +4 -21
  34. package/src/form/EntityForm.tsx +8 -3
  35. package/src/form/components/FormRail.tsx +15 -4
  36. package/src/form/useUndoableDiscard.ts +39 -0
  37. package/src/preview/components/StorageThumbnail.tsx +35 -1
  38. package/src/routes/RebaseRoute.tsx +2 -10
  39. package/dist/PropertyEditView-D6xNTm0d.js.map +0 -1
  40. package/dist/util-BKtHPLe4.js.map +0 -1
@@ -374,7 +374,7 @@ function isPromise(value) {
374
374
  function FormRail({ fields, showRecordMeta, entity, renderField }) {
375
375
  if (!fields.length && !showRecordMeta) return null;
376
376
  return /* @__PURE__ */ jsxs("aside", {
377
- className: cls("flex flex-col gap-6 shrink-0 w-76 border-l overflow-y-auto", "px-5 py-6 bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
377
+ className: cls("flex flex-col gap-6 shrink-0 w-76 @7xl/form:w-84 border-l overflow-y-auto", "px-5 py-6 bg-surface-50 dark:bg-surface-900", defaultBorderMixin),
378
378
  children: [fields.length > 0 && /* @__PURE__ */ jsx("div", {
379
379
  className: "flex flex-col gap-5",
380
380
  children: fields.map(renderField)
@@ -931,6 +931,33 @@ function areEqual$1(prevProps, nextProps) {
931
931
  return prevProps.size === nextProps.size && prevProps.storagePathOrDownloadUrl === nextProps.storagePathOrDownloadUrl && prevProps.storeUrl === nextProps.storeUrl && prevProps.interactive === nextProps.interactive && prevProps.fill === nextProps.fill && prevProps.storageSourceKey === nextProps.storageSourceKey;
932
932
  }
933
933
  var URL_CACHE = {};
934
+ /**
935
+ * The signed-URL request currently in flight for a given cache key.
936
+ *
937
+ * A collection view draws one thumbnail per row and rows share images far more
938
+ * often than not — the demo's 200 blog posts are illustrated by 20 hero images.
939
+ * Every one of those thumbnails mounts in the same tick, and each used to call
940
+ * `getSignedUrl` on its own: {@link URL_CACHE} is only written when a response
941
+ * *lands*, so it is still empty while the burst goes out and dedupes nothing.
942
+ * The result was five or more identical requests per distinct file, and minting
943
+ * a download token is a real request — `/api/storage/metadata/<path>` — against
944
+ * a rate-limited surface. One page view was enough to exhaust the budget and
945
+ * every thumbnail on the screen then failed with a 429.
946
+ *
947
+ * Sharing the promise collapses that burst to one request per file. It
948
+ * deliberately does not extend how long anything is cached: `DownloadConfig.url`
949
+ * is temporal, so a later mount still refetches exactly as it did before.
950
+ */
951
+ var IN_FLIGHT = /* @__PURE__ */ new Map();
952
+ function getSignedUrlOnce(storage, path, cacheKey) {
953
+ const existing = IN_FLIGHT.get(cacheKey);
954
+ if (existing) return existing;
955
+ const request = storage.getSignedUrl(path).finally(() => {
956
+ IN_FLIGHT.delete(cacheKey);
957
+ });
958
+ IN_FLIGHT.set(cacheKey, request);
959
+ return request;
960
+ }
934
961
  function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownloadUrl, size, fill, storageSourceKey }) {
935
962
  const { t } = useTranslation();
936
963
  const [error, setError] = React.useState(void 0);
@@ -945,7 +972,7 @@ function StorageThumbnailInternal({ storeUrl, interactive, storagePathOrDownload
945
972
  useEffect(() => {
946
973
  if (!storagePathOrDownloadUrl) return;
947
974
  let unmounted = false;
948
- storage.getSignedUrl(storagePathOrDownloadUrl).then(function(downloadConfig) {
975
+ getSignedUrlOnce(storage, storagePathOrDownloadUrl, cacheKey).then(function(downloadConfig) {
949
976
  if (!unmounted) {
950
977
  setDownloadConfig(downloadConfig);
951
978
  URL_CACHE[cacheKey] = downloadConfig;
@@ -4598,10 +4625,13 @@ function relationCardinality$2(relation) {
4598
4625
  if (relation.kind === "belongsTo" || relation.kind === "hasOne") return "one";
4599
4626
  return relation.cardinality;
4600
4627
  }
4601
- 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..." }, ref) => {
4628
+ 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) => {
4602
4629
  const collection = relation.target();
4630
+ const dataPath = getCollectionDataPath(collection);
4603
4631
  const dataClient = useData();
4604
4632
  const sidePanelController = useSidePanel();
4633
+ const { canCreate } = usePermissions();
4634
+ const { t } = useTranslation();
4605
4635
  const contextPortalContainer = usePortalContainer();
4606
4636
  const multiple = multipleOverride ?? relationCardinality$2(relation) === "many";
4607
4637
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@@ -4613,7 +4643,7 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4613
4643
  const localSelectionIdsRef = useRef(null);
4614
4644
  const pinnedIdsRef = useRef(null);
4615
4645
  const { items: availableItems, isLoading, hasMore, search, loadMore, entityToRelationItem } = useRelationSelector({
4616
- path: getCollectionDataPath(collection),
4646
+ path: dataPath,
4617
4647
  collection,
4618
4648
  fixedFilter,
4619
4649
  pageSize,
@@ -4770,6 +4800,8 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4770
4800
  if (multiple) onValueChange?.(selected.length ? selected.map((i) => i.relation) : void 0);
4771
4801
  else onValueChange?.(selected[0]?.relation);
4772
4802
  }, [onValueChange, multiple]);
4803
+ const emitValueChangeRef = useRef(emitValueChange);
4804
+ emitValueChangeRef.current = emitValueChange;
4773
4805
  const computeSelectionFingerprint = useCallback((items) => {
4774
4806
  return items.map((i) => String(i.id)).sort().join(",");
4775
4807
  }, []);
@@ -4846,6 +4878,45 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
4846
4878
  isPopoverOpenRef.current = false;
4847
4879
  pinnedIdsRef.current = null;
4848
4880
  }, []);
4881
+ const canCreateTarget = allowCreate && canCreate(collection, dataPath);
4882
+ const buildDefaultValues = useCallback((searchText) => {
4883
+ const trimmed = searchText.trim();
4884
+ if (!trimmed) return void 0;
4885
+ const titleKey = getTitlePropertyKey(collection);
4886
+ if (!titleKey || titleKey.includes(".")) return void 0;
4887
+ const titleProperty = collection.properties?.[titleKey];
4888
+ if (!titleProperty || typeof titleProperty === "function") return void 0;
4889
+ if (titleProperty.type !== "string" || titleProperty.enum) return void 0;
4890
+ return { [titleKey]: trimmed };
4891
+ }, [collection]);
4892
+ const handleCreateNew = useCallback(() => {
4893
+ const defaultValues = buildDefaultValues(searchString);
4894
+ closePopover();
4895
+ sidePanelController.open({
4896
+ path: dataPath,
4897
+ collection,
4898
+ updateUrl: false,
4899
+ closeOnSave: true,
4900
+ defaultValues,
4901
+ onUpdate: ({ entity }) => {
4902
+ if (!entity) return;
4903
+ const item = entityToRelationItemRef.current(entity, new EntityRelation(entity.id, dataPath));
4904
+ const current = selectedItemsRef.current;
4905
+ const newSelected = multiple ? [...current.filter((i) => String(i.id) !== String(item.id)), item] : [item];
4906
+ setSelectedItems(newSelected);
4907
+ localSelectionIdsRef.current = newSelected.map((i) => String(i.id)).sort().join(",");
4908
+ emitValueChangeRef.current(newSelected);
4909
+ }
4910
+ });
4911
+ }, [
4912
+ buildDefaultValues,
4913
+ searchString,
4914
+ closePopover,
4915
+ sidePanelController,
4916
+ dataPath,
4917
+ collection,
4918
+ multiple
4919
+ ]);
4849
4920
  const resolvedPlaceholder = placeholder || emptyPlaceholder || emptyText;
4850
4921
  const portalContainer = contextPortalContainer ?? (typeof document !== "undefined" ? document.body : void 0) ?? void 0;
4851
4922
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(PopoverPrimitive.Root, {
@@ -5069,7 +5140,24 @@ var RelationSelector = React$1.forwardRef(({ value, size = "medium", onValueChan
5069
5140
  })
5070
5141
  ] })
5071
5142
  ]
5072
- })
5143
+ }),
5144
+ canCreateTarget && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Separator, {
5145
+ orientation: "horizontal",
5146
+ className: "my-0"
5147
+ }), /* @__PURE__ */ jsxs("button", {
5148
+ type: "button",
5149
+ "data-relation-selector-create": true,
5150
+ onMouseDown: (e) => {
5151
+ e.preventDefault();
5152
+ e.stopPropagation();
5153
+ },
5154
+ onClick: handleCreateNew,
5155
+ className: cls("w-full flex flex-row items-center gap-2 px-4 py-3 text-sm text-left", "text-primary hover:bg-surface-accent-50 dark:hover:bg-surface-800"),
5156
+ children: [/* @__PURE__ */ jsx(PlusIcon, { size: iconSize.smallest }), /* @__PURE__ */ jsx("span", {
5157
+ className: "truncate",
5158
+ children: searchString.trim() ? t("add_named", { name: searchString.trim() }) : t("add_specific", { name: collection.singularName ?? collection.name })
5159
+ })]
5160
+ })] })
5073
5161
  ]
5074
5162
  })
5075
5163
  })
@@ -7757,40 +7845,21 @@ function resolvedSelectedEntityView(customViews, customizationController, select
7757
7845
  }
7758
7846
  var BreadcrumbContext = React.createContext({
7759
7847
  breadcrumbs: [],
7760
- set: () => {},
7761
- updateCount: () => {}
7848
+ set: () => {}
7762
7849
  });
7763
7850
  var BreadcrumbsProvider = ({ children }) => {
7764
7851
  const [breadcrumbs, setBreadcrumbs] = useState([]);
7765
7852
  const set = useCallback((props) => {
7766
7853
  setBreadcrumbs((prev) => {
7767
- const next = props.breadcrumbs.map((newEntry) => {
7768
- const prevEntry = newEntry.id ? prev.find((p) => p.id === newEntry.id) : void 0;
7769
- if (prevEntry && newEntry.count === null && typeof prevEntry.count === "number") return {
7770
- ...newEntry,
7771
- count: prevEntry.count
7772
- };
7773
- return newEntry;
7774
- });
7775
- if (prev.length === next.length && prev.every((p, i) => p.title === next[i].title && p.url === next[i].url && p.id === next[i].id && p.count === next[i].count)) return prev;
7854
+ const next = props.breadcrumbs;
7855
+ if (prev.length === next.length && prev.every((p, i) => p.title === next[i].title && p.url === next[i].url && p.id === next[i].id)) return prev;
7776
7856
  return next;
7777
7857
  });
7778
7858
  }, []);
7779
- const updateCount = useCallback((id, count) => {
7780
- setBreadcrumbs((prev) => prev.map((entry) => entry.id === id ? {
7781
- ...entry,
7782
- count
7783
- } : entry));
7784
- }, []);
7785
7859
  const value = useMemo(() => ({
7786
7860
  breadcrumbs,
7787
- set,
7788
- updateCount
7789
- }), [
7790
- breadcrumbs,
7791
- set,
7792
- updateCount
7793
- ]);
7861
+ set
7862
+ }), [breadcrumbs, set]);
7794
7863
  return /* @__PURE__ */ jsx(BreadcrumbContext.Provider, {
7795
7864
  value,
7796
7865
  children
@@ -9358,8 +9427,8 @@ function EditorCollectionAction({ path, parentCollectionSlugs, parentEntityIds,
9358
9427
  }
9359
9428
  //#endregion
9360
9429
  //#region src/components/CollectionViewBinding/CollectionViewActions.tsx
9361
- var ImportCollectionAction = lazyChunk(() => import("./import-CXpuYo-T.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
9362
- var ExportCollectionAction = lazyChunk(() => import("./export-BZi1MzQ5.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
9430
+ var ImportCollectionAction = lazyChunk(() => import("./import-Bxv5JPm-.js").then((n) => n.t).then((m) => ({ default: m.ImportCollectionAction })));
9431
+ var ExportCollectionAction = lazyChunk(() => import("./export-oauWlxuu.js").then((n) => n.t).then((m) => ({ default: m.ExportCollectionAction })));
9363
9432
  function CollectionViewActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, onNewClick, onAddExistingClick, onMultipleDeleteClick, selectionEnabled, path, selectionController, tableController, collectionEntitiesCount, compact, children, openNewDocument }) {
9364
9433
  const context = useAdminContext();
9365
9434
  const { canCreate, canDelete } = usePermissions();
@@ -9996,12 +10065,6 @@ function EntityCardBinding({ entity, collection, onClick, selected, highlighted,
9996
10065
  }), /* @__PURE__ */ jsxs("div", {
9997
10066
  className: "p-3",
9998
10067
  children: [
9999
- /* @__PURE__ */ jsx(Typography, {
10000
- variant: "caption",
10001
- color: "disabled",
10002
- className: "font-mono truncate block",
10003
- children: entity.id
10004
- }),
10005
10068
  /* @__PURE__ */ jsx("div", {
10006
10069
  className: "truncate my-1 text-sm font-medium min-h-[20px]",
10007
10070
  children: slots.title ? terms.length > 0 && typeof slots.title.value === "string" ? /* @__PURE__ */ jsx(Highlighted, {
@@ -11038,7 +11101,7 @@ function ListHeaderLabel({ column, direction, position, onSort }) {
11038
11101
  className: "flex-shrink-0"
11039
11102
  }),
11040
11103
  direction && position !== void 0 && /* @__PURE__ */ jsx("span", {
11041
- className: "flex-shrink-0 text-[9px] font-bold leading-none tabular-nums",
11104
+ className: "flex-shrink-0 text-[9px] font-semibold leading-none tabular-nums",
11042
11105
  children: position + 1
11043
11106
  })
11044
11107
  ] });
@@ -11713,7 +11776,7 @@ function JsonPreviewBinding({ values }) {
11713
11776
  }
11714
11777
  //#endregion
11715
11778
  //#region src/components/EntityInspector.tsx
11716
- var EntityHistoryView = lazyChunk(() => import("./history-B5pkfldE.js").then((m) => ({ default: m.EntityHistoryView })));
11779
+ var EntityHistoryView = lazyChunk(() => import("./history-C2cqc0bw.js").then((m) => ({ default: m.EntityHistoryView })));
11717
11780
  /**
11718
11781
  * Raw values and revision history, as an inspector rather than as tabs.
11719
11782
  *
@@ -12173,6 +12236,7 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
12173
12236
  onSideTabClick(value);
12174
12237
  },
12175
12238
  children: [
12239
+ customViewTabsStart,
12176
12240
  /* @__PURE__ */ jsx(Tab, {
12177
12241
  value: "__main_##Q$SC^#S6",
12178
12242
  className: cls("h-full data-[state=active]:bg-white dark:data-[state=active]:bg-surface-800", "text-sm min-w-[90px]"),
@@ -12181,7 +12245,6 @@ function DetailViewBindingInner({ path, entityId, selectedTab: selectedTabProp,
12181
12245
  children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
12182
12246
  })
12183
12247
  }),
12184
- customViewTabsStart,
12185
12248
  customViewTabsEnd,
12186
12249
  subcollectionTabs
12187
12250
  ]
@@ -15444,7 +15507,7 @@ function SortButton({ tableController, properties, compact }) {
15444
15507
  }
15445
15508
  //#endregion
15446
15509
  //#region src/components/CollectionViewBinding/CollectionViewStartActions.tsx
15447
- function CollectionViewStartActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, path, selectionController, tableController, collectionEntitiesCount, resolvedProperties, viewMode, compact, openNewDocument }) {
15510
+ function CollectionViewStartActions({ collection, relativePath, parentCollectionSlugs, parentEntityIds, path, selectionController, tableController, collectionEntitiesCount, entitiesCount, resolvedProperties, viewMode, compact, openNewDocument }) {
15448
15511
  const context = useAdminContext();
15449
15512
  const largeLayout = useLargeLayout();
15450
15513
  const { t } = useTranslation();
@@ -15515,6 +15578,13 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
15515
15578
  tableController,
15516
15579
  compact
15517
15580
  }, "filter_presets") : null;
15581
+ const countBadge = !iconOnlyToolbar && entitiesCount !== void 0 ? /* @__PURE__ */ jsx(Tooltip, {
15582
+ title: t("records_in_view"),
15583
+ children: entitiesCount === null ? /* @__PURE__ */ jsx(Skeleton, { className: "w-8 h-4 rounded-md mx-1" }) : /* @__PURE__ */ jsx("span", {
15584
+ className: "mx-1 text-xs text-surface-accent-600 dark:text-surface-accent-400 bg-surface-100 dark:bg-surface-800 px-1.5 py-0.5 rounded tabular-nums",
15585
+ children: entitiesCount.toLocaleString()
15586
+ })
15587
+ }, "entities_count") : null;
15518
15588
  return /* @__PURE__ */ jsxs(Fragment, { children: [
15519
15589
  [
15520
15590
  backButton,
@@ -15525,7 +15595,8 @@ function CollectionViewStartActions({ collection, relativePath, parentCollection
15525
15595
  compact: iconOnlyToolbar,
15526
15596
  enabled: !collection.fixedFilter
15527
15597
  }, "clear_filter"),
15528
- filterPresetsButton
15598
+ filterPresetsButton,
15599
+ countBadge
15529
15600
  ],
15530
15601
  useSlot("collection.actions.start", actionProps),
15531
15602
  resolvedProperties && tableController.setFilterValues && /* @__PURE__ */ jsx(FiltersDialog, {
@@ -15604,7 +15675,6 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
15604
15675
  const context = useAdminContext();
15605
15676
  const collectionRegistry = useCollectionRegistryController();
15606
15677
  const urlController = useUrlController();
15607
- const breadcrumbs = useBreadcrumbsController();
15608
15678
  const path = pathProp ?? getCollectionDataPath(collectionProp);
15609
15679
  const dataClient = useData();
15610
15680
  const sidePanelController = useSidePanel();
@@ -16099,11 +16169,6 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
16099
16169
  openEntityMode
16100
16170
  });
16101
16171
  }, [updateLastDeleteTimestamp, usedSelectionController]);
16102
- const updateCountRef = React.useRef(breadcrumbs.updateCount);
16103
- updateCountRef.current = breadcrumbs.updateCount;
16104
- useEffect(() => {
16105
- updateCountRef.current(path, docsCount);
16106
- }, [docsCount, path]);
16107
16172
  const countFetcher = /* @__PURE__ */ jsx(EntitiesCount, {
16108
16173
  path,
16109
16174
  collection,
@@ -16219,6 +16284,7 @@ var CollectionViewBindingInner = React.memo(function CollectionViewBindingInner(
16219
16284
  collectionEntitiesCount: docsCount ?? void 0,
16220
16285
  resolvedProperties: resolvedCollection.properties,
16221
16286
  viewMode,
16287
+ entitiesCount: docsCount,
16222
16288
  openNewDocument,
16223
16289
  compact: isCompact
16224
16290
  }),
@@ -16931,6 +16997,46 @@ function EntityActionButton({ action, enabled, props }) {
16931
16997
  });
16932
16998
  }
16933
16999
  //#endregion
17000
+ //#region src/collection_editor/useSafeSnackbarController.ts
17001
+ function useSafeSnackbarController() {
17002
+ try {
17003
+ return useSnackbarController();
17004
+ } catch {
17005
+ return;
17006
+ }
17007
+ }
17008
+ //#endregion
17009
+ //#region src/form/useUndoableDiscard.ts
17010
+ /**
17011
+ * Throw away everything the user has typed, reversibly.
17012
+ *
17013
+ * "Discard" and "Clear" sit next to Save, take one click, and undo everything
17014
+ * the user has done since they opened the record — the identity bar's version
17015
+ * does not even stop to ask. So the reset goes *through* the undo history
17016
+ * rather than around it (see `undoable` in `FormexResetProps`), and the
17017
+ * confirmation carries the Undo, which is the only place it can be: the form
17018
+ * has no undo button, only the ⌘Z the user has no reason to guess at.
17019
+ *
17020
+ * Pass `values` to reset to something other than the form's current baseline.
17021
+ */
17022
+ function useUndoableDiscard() {
17023
+ const snackbarController = useSafeSnackbarController();
17024
+ return useCallback((formex, status, values) => {
17025
+ formex.resetForm(values !== void 0 ? {
17026
+ values,
17027
+ undoable: true
17028
+ } : { undoable: true });
17029
+ snackbarController?.open({
17030
+ type: "info",
17031
+ message: status === "existing" ? "Changes discarded" : "Form cleared",
17032
+ action: {
17033
+ label: "Undo",
17034
+ onClick: () => formex.undo()
17035
+ }
17036
+ });
17037
+ }, [snackbarController]);
17038
+ }
17039
+ //#endregion
16934
17040
  //#region src/hooks/useRecordActions.tsx
16935
17041
  /**
16936
17042
  * The actions you can perform *on* a record — copy, delete, and whatever the
@@ -17040,6 +17146,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
17040
17146
  }, [entity]);
17041
17147
  const [formContext, setFormContext] = useState(void 0);
17042
17148
  useLargeLayout();
17149
+ const discard = useUndoableDiscard();
17043
17150
  const customizationController = useCustomizationController();
17044
17151
  const plugins = customizationController.plugins;
17045
17152
  const formActionTopProps = useMemo(() => ({
@@ -17454,7 +17561,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
17454
17561
  } : void 0,
17455
17562
  saveAndClosePlacement: layout === "split" ? "menu" : "button",
17456
17563
  onClose: onCloseRequest,
17457
- onDiscard: canEdit && formContext ? () => formContext.formex.resetForm() : void 0,
17564
+ onDiscard: canEdit && formContext ? () => discard(formContext.formex, status) : void 0,
17458
17565
  onInspect: includeJsonView ? () => setInspectorTab("json") : void 0,
17459
17566
  onViewHistory: includeHistoryView ? () => setInspectorTab("history") : void 0,
17460
17567
  externalLink: usedEntity ? customizationController?.entityLinkBuilder?.({ entity: usedEntity }) : void 0,
@@ -17492,6 +17599,7 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
17492
17599
  onSideTabClick(value);
17493
17600
  },
17494
17601
  children: [
17602
+ customViewTabsStart,
17495
17603
  /* @__PURE__ */ jsx(Tab, {
17496
17604
  value: "__main_##Q$SC^#S6",
17497
17605
  className: cls("h-full data-[state=active]:bg-white dark:data-[state=active]:bg-surface-800", "text-sm min-w-[90px]"),
@@ -17500,7 +17608,6 @@ function EditViewBindingInner({ path, entityId, selectedTab: selectedTabProp, co
17500
17608
  children: [getIcon(collection.icon, void 0, void 0, "smallest"), collection.singularName ?? collection.name]
17501
17609
  })
17502
17610
  }),
17503
- customViewTabsStart,
17504
17611
  customViewTabsEnd,
17505
17612
  subcollectionTabs
17506
17613
  ]
@@ -19399,7 +19506,7 @@ function SelectionTableBindingInternal({ onSingleEntitySelected, onMultipleEntit
19399
19506
  sidePanelController.open({
19400
19507
  path,
19401
19508
  collection,
19402
- updateUrl: true,
19509
+ updateUrl: false,
19403
19510
  onUpdate: ({ entity }) => {
19404
19511
  setEntitiesDisplayedFirst([entity, ...entitiesDisplayedFirst]);
19405
19512
  onEntityClick(entity);
@@ -23114,6 +23221,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
23114
23221
  const [savingError, setSavingError] = useState();
23115
23222
  const [isSavingAutoSave, setIsSavingAutoSave] = useState(false);
23116
23223
  const [discardDialogOpen, setDiscardDialogOpen] = useState(false);
23224
+ const discard = useUndoableDiscard();
23117
23225
  const autoSave = collection.formAutoSave;
23118
23226
  const baseInitialValues = useMemo(() => {
23119
23227
  if (computedInitialValues !== void 0) return computedInitialValues;
@@ -23457,7 +23565,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
23457
23565
  maxWidth: "sm",
23458
23566
  children: [
23459
23567
  /* @__PURE__ */ jsx(DialogTitle, { children: status === "existing" ? "Discard changes?" : "Clear form?" }),
23460
- /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(Typography, { children: status === "existing" ? "All unsaved changes will be lost. This cannot be undone." : "All entered values will be cleared. This cannot be undone." }) }),
23568
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Typography, { children: [status === "existing" ? "All unsaved changes will be lost. " : "All entered values will be cleared. ", "You can bring them back with Undo (⌘Z / Ctrl+Z), or from the notification, for as long as this form stays open."] }) }),
23461
23569
  /* @__PURE__ */ jsxs(DialogActions, { children: [/* @__PURE__ */ jsx(Button, {
23462
23570
  variant: "text",
23463
23571
  onClick: () => setDiscardDialogOpen(false),
@@ -23467,7 +23575,7 @@ function EntityForm({ path, entityId: entityIdProp, collection, onValuesModified
23467
23575
  color: "error",
23468
23576
  onClick: () => {
23469
23577
  setDiscardDialogOpen(false);
23470
- formex.resetForm({ values: baseInitialValues });
23578
+ discard(formex, status, baseInitialValues);
23471
23579
  },
23472
23580
  children: status === "existing" ? "Discard" : "Clear"
23473
23581
  })] })
@@ -24653,6 +24761,6 @@ function getFullIdPath(propertyKey, propertyNamespace) {
24653
24761
  return idToPropertiesPath(propertyNamespace ? `${propertyNamespace}.${propertyKey}` : propertyKey);
24654
24762
  }
24655
24763
  //#endregion
24656
- export { getEntityViewWidth as $, SkeletonPropertyComponent as $n, resolveCollectionPathIds$1 as $t, SelectFieldBinding as A, ArrayOfStringsPreview as An, saveImportedEntities as At, useSelectionDialog as B, useSidePanel as Bn, FieldCaption as Bt, getDefaultFieldId as C, UserPreview as Cn, isReferenceProperty as Cr, convertDataToEntity as Ct, TextFieldBinding as D, KeyValuePreview as Dn, useImportConfig as Dt, VectorFieldBinding as E, DatePreview as En, getInferenceType as Et, MapFieldBinding as F, ArrayOfReferencesPreview as Fn, parseCsvToObjects as Ft, useTopLevelNavigation as G, getEntityPreviewKeys as Gn, mergeEntityActions as Gt, SideDialogs as H, useCollectionRegistryController as Hn, BreadcrumbsProvider as Ht, KeyValueFieldBinding as I, InlineEntityListPreview as In, unflattenObject as It, useBuildCollectionRegistryController as J, ArrayPropertyPreview as Jn, getCollectionPathsCombinations as Jt, useResolvedViews as K, getEntityTitlePropertyKey as Kn, addInitialSlash as Kt, DateTimeFieldBinding as L, ReferencePreview as Ln, ArrayContainer as Lt, ReferenceFieldBinding as M, ArrayEnumPreview as Mn, convertFileToJson as Mt, MultiSelectFieldBinding as N, ArrayOfStorageComponentsPreview as Nn, detectCsvDelimiter as Nt, SwitchFieldBinding as O, MapPropertyPreview as On, ImportSaveInProgress as Ot, MarkdownEditorFieldBinding as P, RelationPreview as Pn, parseCsvRows as Pt, buildSidePanelsFromUrl as Q, StorageThumbnailInternal as Qn, removeTrailingSlash$1 as Qt, BlockFieldBinding as R, EntityPreviewBinding as Rn, PropertyConfigBadge as Rt, getDefaultFieldConfig as S, PropertyPreview as Sn, getResolvedPropertyInPath as Sr, DataNewPropertiesMapping as St, getFieldId as T, BooleanPreview as Tn, processValueMapping as Tt, useBuildUrlController as U, getUserLabel as Un, resolveEntityAction as Ut, SelectionTableBinding as V, CollectionRegistryContext as Vn, useBreadcrumbsController as Vt, useBuildNavigationStateController as W, useResolvedUser as Wn, resolveEntityView as Wt, resolveNavigationFrom as X, EnumValuesChip as Xn, removeInitialAndTrailingSlashes$1 as Xt, useHistory as Y, StringPropertyPreview as Yn, getLastSegment$1 as Yt, useResolvedNavigationFrom as Z, StorageThumbnail as Zn, removeInitialSlash as Zt, useCollectionsConfigController as _, ReadOnlyFieldBinding as _n, getDefaultPropertiesOrder as _r, CollectionViewActions as _t, namespaceToPropertiesPath as a, SelectableTable as an, ImagePreview as ar, removeEmptyContainers as at, PropertyFieldBinding as b, FieldHelperText as bn, getPropertiesWithPropertiesOrder as br, useCollectionEditorDialogsState as bt, buildCollectionGenerationCallback as c, UrlContext as cn, FormSections as cr, copyEntityAction as ct, fromSerializableCollectionConfigs as d, useNavigationStateController as dn, FieldBlock as dr, resetPasswordAction as dt, resolveOpenEntityMode as en, renderSkeletonCaptionText as er, useBuildSidePanel as et, fromSerializableProperties as f, useSideDialogsController as fn, LABEL_ICON_SIZE as fr, CreationResultDialog as ft, toSerializableProperty as g, useClearRestoreValue as gn, getBracketNotation as gr, EntityCardBinding as gt, toSerializableProperties as h, ArrayCustomShapedFieldBinding as hn, PropertyIdCopyTooltip as hr, CollectionCardViewBinding as ht, namespaceToPropertiesOrderPath as i, CollectionTableBinding as in, UrlComponentPreview as ir, getInitialEntityValues as it, RepeatFieldBinding as j, ArrayPropertyEnumPreview as jn, ImportFileUpload as jt, StorageUploadFieldBinding as k, ArrayOneOfPreview as kn, IMPORT_BATCH_SIZE as kt, validateCollectionJson as l, useUrlController as ln, FormRail as lr, deleteEntityAction as lt, toSerializableCollectionConfig as m, SelectableTableContext as mn, spanClass as mr, EntityViewBinding as mt, getFullIdPath as n, useSelectionController as nn, renderSkeletonImageThumbnail as nr, extractTouchedValues as nt, CollectionGenerationApiError as o, CollectionRowActions as on, sanitizeUrl as or, zodToFormErrors as ot, fromSerializableProperty as p, SideDialogsControllerContext as pn, isSelfLabellingProperty as pr, DetailViewBinding as pt, useResolvedCollections as q, getEntityTitlePropertyKeyForEntity as qn, getCollectionBySlugWithin as qt, idToPropertiesPath as r, VirtualTableInput$1 as rn, renderSkeletonText as rr, getChanges as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, useAdminContext as sn, EmptyValue as sr, CollectionViewBinding as st, getFullId as t, resolveViewMode as tn, renderSkeletonIcon as tr, EditViewBinding as tt, fromSerializableCollectionConfig as u, NavigationStateContext as un, RecordMeta as ur, editEntityAction as ut, EntityFormBinding as v, LabelWithIconAndTooltip as vn, getIconForProperty as vr, useCollectionEditorController as vt, getFieldConfig as w, NumberPropertyPreview as wn, isRelationProperty as wr, flattenEntry as wt, DEFAULT_FIELD_CONFIGS as x, ArrayOfMapsPreview as xn, getPropertyInPath$1 as xr, ImportNewPropertyFieldPreview as xt, EntityForm as y, LabelWithIcon as yn, getIconForWidget as yr, ConfigControllerProvider as yt, ArrayOfReferencesFieldBinding as z, SidePanelControllerContext as zn, SearchIconsView as zt };
24764
+ export { getEntityViewWidth as $, StorageThumbnailInternal as $n, removeTrailingSlash$1 as $t, SelectFieldBinding as A, ArrayOneOfPreview as An, IMPORT_BATCH_SIZE as At, useSelectionDialog as B, SidePanelControllerContext as Bn, SearchIconsView as Bt, getDefaultFieldId as C, PropertyPreview as Cn, getResolvedPropertyInPath as Cr, DataNewPropertiesMapping as Ct, TextFieldBinding as D, DatePreview as Dn, getInferenceType as Dt, VectorFieldBinding as E, BooleanPreview as En, processValueMapping as Et, MapFieldBinding as F, RelationPreview as Fn, parseCsvRows as Ft, useTopLevelNavigation as G, useResolvedUser as Gn, resolveEntityView as Gt, SideDialogs as H, CollectionRegistryContext as Hn, useBreadcrumbsController as Ht, KeyValueFieldBinding as I, ArrayOfReferencesPreview as In, parseCsvToObjects as It, useBuildCollectionRegistryController as J, getEntityTitlePropertyKeyForEntity as Jn, getCollectionBySlugWithin as Jt, useResolvedViews as K, getEntityPreviewKeys as Kn, mergeEntityActions as Kt, DateTimeFieldBinding as L, InlineEntityListPreview as Ln, unflattenObject as Lt, ReferenceFieldBinding as M, ArrayPropertyEnumPreview as Mn, ImportFileUpload as Mt, MultiSelectFieldBinding as N, ArrayEnumPreview as Nn, convertFileToJson as Nt, SwitchFieldBinding as O, KeyValuePreview as On, useImportConfig as Ot, MarkdownEditorFieldBinding as P, ArrayOfStorageComponentsPreview as Pn, detectCsvDelimiter as Pt, buildSidePanelsFromUrl as Q, StorageThumbnail as Qn, removeInitialSlash as Qt, BlockFieldBinding as R, ReferencePreview as Rn, ArrayContainer as Rt, getDefaultFieldConfig as S, ArrayOfMapsPreview as Sn, getPropertyInPath$1 as Sr, ImportNewPropertyFieldPreview as St, getFieldId as T, NumberPropertyPreview as Tn, isRelationProperty as Tr, flattenEntry as Tt, useBuildUrlController as U, useCollectionRegistryController as Un, BreadcrumbsProvider as Ut, SelectionTableBinding as V, useSidePanel as Vn, FieldCaption as Vt, useBuildNavigationStateController as W, getUserLabel as Wn, resolveEntityAction as Wt, resolveNavigationFrom as X, StringPropertyPreview as Xn, getLastSegment$1 as Xt, useHistory as Y, ArrayPropertyPreview as Yn, getCollectionPathsCombinations as Yt, useResolvedNavigationFrom as Z, EnumValuesChip as Zn, removeInitialAndTrailingSlashes$1 as Zt, useCollectionsConfigController as _, useClearRestoreValue as _n, getBracketNotation as _r, EntityCardBinding as _t, namespaceToPropertiesPath as a, CollectionTableBinding as an, UrlComponentPreview as ar, getInitialEntityValues as at, PropertyFieldBinding as b, LabelWithIcon as bn, getIconForWidget as br, ConfigControllerProvider as bt, buildCollectionGenerationCallback as c, useAdminContext as cn, EmptyValue as cr, CollectionViewBinding as ct, fromSerializableCollectionConfigs as d, NavigationStateContext as dn, RecordMeta as dr, editEntityAction as dt, resolveCollectionPathIds$1 as en, SkeletonPropertyComponent as er, useBuildSidePanel as et, fromSerializableProperties as f, useNavigationStateController as fn, FieldBlock as fr, resetPasswordAction as ft, toSerializableProperty as g, ArrayCustomShapedFieldBinding as gn, PropertyIdCopyTooltip as gr, CollectionCardViewBinding as gt, toSerializableProperties as h, SelectableTableContext as hn, spanClass as hr, EntityViewBinding as ht, namespaceToPropertiesOrderPath as i, VirtualTableInput$1 as in, renderSkeletonText as ir, getChanges as it, RepeatFieldBinding as j, ArrayOfStringsPreview as jn, saveImportedEntities as jt, StorageUploadFieldBinding as k, MapPropertyPreview as kn, ImportSaveInProgress as kt, validateCollectionJson as l, UrlContext as ln, FormSections as lr, copyEntityAction as lt, toSerializableCollectionConfig as m, SideDialogsControllerContext as mn, isSelfLabellingProperty as mr, DetailViewBinding as mt, getFullIdPath as n, resolveViewMode as nn, renderSkeletonIcon as nr, useSafeSnackbarController as nt, CollectionGenerationApiError as o, SelectableTable as on, ImagePreview as or, removeEmptyContainers as ot, fromSerializableProperty as p, useSideDialogsController as pn, LABEL_ICON_SIZE as pr, CreationResultDialog as pt, useResolvedCollections as q, getEntityTitlePropertyKey as qn, addInitialSlash as qt, idToPropertiesPath as r, useSelectionController as rn, renderSkeletonImageThumbnail as rr, extractTouchedValues as rt, DEFAULT_COLLECTION_GENERATION_ENDPOINT as s, CollectionRowActions as sn, sanitizeUrl as sr, zodToFormErrors as st, getFullId as t, resolveOpenEntityMode as tn, renderSkeletonCaptionText as tr, EditViewBinding as tt, fromSerializableCollectionConfig as u, useUrlController as un, FormRail as ur, deleteEntityAction as ut, EntityFormBinding as v, ReadOnlyFieldBinding as vn, getDefaultPropertiesOrder as vr, CollectionViewActions as vt, getFieldConfig as w, UserPreview as wn, isReferenceProperty as wr, convertDataToEntity as wt, DEFAULT_FIELD_CONFIGS as x, FieldHelperText as xn, getPropertiesWithPropertiesOrder as xr, useCollectionEditorDialogsState as xt, EntityForm as y, LabelWithIconAndTooltip as yn, getIconForProperty as yr, useCollectionEditorController as yt, ArrayOfReferencesFieldBinding as z, EntityPreviewBinding as zn, PropertyConfigBadge as zt };
24657
24765
 
24658
- //# sourceMappingURL=util-BKtHPLe4.js.map
24766
+ //# sourceMappingURL=util-C4qsI1QV.js.map