@firecms/core 3.0.0-rc.2 → 3.0.0-rc.4

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 (52) hide show
  1. package/dist/components/HomePage/HomePageDnD.d.ts +2 -1
  2. package/dist/components/PropertyCollectionView.d.ts +23 -0
  3. package/dist/core/EntityEditView.d.ts +10 -4
  4. package/dist/form/EntityForm.d.ts +5 -2
  5. package/dist/form/PropertyFieldBinding.d.ts +1 -1
  6. package/dist/form/components/LocalChangesMenu.d.ts +11 -0
  7. package/dist/form/index.d.ts +2 -1
  8. package/dist/index.es.js +1307 -384
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.umd.js +1306 -383
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/types/collections.d.ts +11 -0
  13. package/dist/types/fields.d.ts +8 -0
  14. package/dist/types/properties.d.ts +32 -6
  15. package/dist/util/collections.d.ts +1 -0
  16. package/dist/util/entity_cache.d.ts +6 -1
  17. package/dist/util/make_properties_editable.d.ts +1 -2
  18. package/dist/util/objects.d.ts +1 -0
  19. package/dist/util/useStorageUploadController.d.ts +1 -0
  20. package/package.json +6 -6
  21. package/src/components/EntityCollectionTable/EntityCollectionRowActions.tsx +47 -47
  22. package/src/components/EntityCollectionView/EntityCollectionView.tsx +6 -1
  23. package/src/components/EntityView.tsx +29 -40
  24. package/src/components/HomePage/DefaultHomePage.tsx +13 -9
  25. package/src/components/HomePage/HomePageDnD.tsx +140 -38
  26. package/src/components/PropertyCollectionView.tsx +329 -0
  27. package/src/components/SelectableTable/SelectableTable.tsx +0 -12
  28. package/src/components/SelectableTable/filters/DateTimeFilterField.tsx +2 -1
  29. package/src/components/SelectableTable/filters/StringNumberFilterField.tsx +0 -1
  30. package/src/core/EntityEditView.tsx +27 -14
  31. package/src/core/EntityEditViewFormActions.tsx +33 -18
  32. package/src/core/EntitySidePanel.tsx +9 -3
  33. package/src/form/EntityForm.tsx +173 -42
  34. package/src/form/EntityFormActions.tsx +30 -15
  35. package/src/form/PropertyFieldBinding.tsx +4 -4
  36. package/src/form/components/ErrorFocus.tsx +22 -29
  37. package/src/form/components/LocalChangesMenu.tsx +144 -0
  38. package/src/form/field_bindings/BlockFieldBinding.tsx +1 -0
  39. package/src/form/index.tsx +5 -1
  40. package/src/hooks/useBuildNavigationController.tsx +104 -31
  41. package/src/preview/property_previews/MapPropertyPreview.tsx +2 -2
  42. package/src/preview/property_previews/NumberPropertyPreview.tsx +2 -2
  43. package/src/types/collections.ts +12 -0
  44. package/src/types/fields.tsx +10 -0
  45. package/src/types/properties.ts +35 -6
  46. package/src/util/collections.ts +8 -0
  47. package/src/util/createFormexStub.tsx +4 -0
  48. package/src/util/entity_cache.ts +71 -52
  49. package/src/util/join_collections.ts +3 -3
  50. package/src/util/make_properties_editable.ts +0 -22
  51. package/src/util/objects.ts +40 -2
  52. package/src/util/useStorageUploadController.tsx +71 -34
package/dist/index.es.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
2
  import { c } from "react-compiler-runtime";
3
3
  import * as React from "react";
4
- import React__default, { useRef, useEffect, useContext, useCallback, useMemo, useState, createElement, createRef, createContext, forwardRef, useLayoutEffect, useDeferredValue } from "react";
5
- import { getColorSchemeForSeed, CHIP_COLORS, FunctionsIcon, CircleIcon, iconKeys, coolIconKeys, Icon, Tooltip, ErrorIcon, Typography, IconButton, ContentCopyIcon, OpenInNewIcon, DescriptionIcon, cls, Skeleton, Chip, defaultBorderMixin, KeyboardTabIcon, Checkbox, AccountCircleIcon, Markdown, TextareaAutosize, focusedDisabled, MultiSelect, MultiSelectItem, Select, SelectItem, BooleanSwitch, DateTimeField, paperMixin, EditIcon, DoNotDisturbOnIcon, Menu, MenuItem, MoreVertIcon, CircularProgress, SearchBar, Badge, ArrowUpwardIcon, Popover, FilterListIcon, Button, CenteredView, AssignmentIcon, Label, CloseIcon, TextField, BooleanSwitchWithLabel, useOutsideAlerter, Dialog, DialogTitle, DialogContent, DialogActions, FileCopyIcon, DeleteIcon, AddIcon, StarIcon, Collapse, ExpandablePanel, ArrowForwardIcon, Card, cardMixin, cardClickableMixin, Container, LoadingButton, Alert, CheckIcon, NotesIcon, InfoIcon, fieldBackgroundMixin, RemoveIcon, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, ArrowDropDownIcon, FilterListOffIcon, SearchIcon, Avatar, DarkModeIcon, LightModeIcon, BrightnessMediumIcon, LogoutIcon, HandleIcon, KeyboardArrowUpIcon, KeyboardArrowDownIcon, debounce, Sheet, Tab, Tabs, CodeIcon, OpenInFullIcon, ViewStreamIcon, RepeatIcon, BallotIcon, ScheduleIcon, AddLinkIcon, LinkIcon, DriveFolderUploadIcon, UploadFileIcon, FormatListNumberedIcon, NumbersIcon, PersonIcon, ListAltIcon, ListIcon, FlagIcon, MailIcon, HttpIcon, FormatQuoteIcon, SubjectIcon, ShortTextIcon, MenuIcon, ChevronLeftIcon } from "@firecms/ui";
4
+ import React__default, { useRef, useEffect, useContext, useCallback, useMemo, useState, createElement, createRef, createContext, forwardRef, useLayoutEffect } from "react";
5
+ import { getColorSchemeForSeed, CHIP_COLORS, FunctionsIcon, CircleIcon, iconKeys, coolIconKeys, Icon, Tooltip, ErrorIcon, Typography, IconButton, ContentCopyIcon, OpenInNewIcon, DescriptionIcon, cls, Skeleton, Chip, defaultBorderMixin, KeyboardTabIcon, Checkbox, AccountCircleIcon, Markdown, TextareaAutosize, focusedDisabled, MultiSelect, MultiSelectItem, Select, SelectItem, BooleanSwitch, DateTimeField, paperMixin, EditIcon, DoNotDisturbOnIcon, Menu, MenuItem, MoreVertIcon, Badge, CircularProgress, SearchBar, ArrowUpwardIcon, Popover, FilterListIcon, Button, CenteredView, AssignmentIcon, Label, CloseIcon, TextField, BooleanSwitchWithLabel, useOutsideAlerter, Dialog, DialogTitle, DialogContent, DialogActions, FileCopyIcon, DeleteIcon, AddIcon, StarIcon, Collapse, ExpandablePanel, ArrowForwardIcon, Card, cardMixin, cardClickableMixin, Container, LoadingButton, WarningIcon, KeyboardArrowDownIcon, VisibilityIcon, CheckIcon, CancelIcon, Alert, NotesIcon, InfoIcon, fieldBackgroundMixin, RemoveIcon, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, ArrowDropDownIcon, FilterListOffIcon, SearchIcon, Avatar, DarkModeIcon, LightModeIcon, BrightnessMediumIcon, LogoutIcon, HandleIcon, KeyboardArrowUpIcon, debounce, Sheet, Tab, Tabs, CodeIcon, OpenInFullIcon, ViewStreamIcon, RepeatIcon, BallotIcon, ScheduleIcon, AddLinkIcon, LinkIcon, DriveFolderUploadIcon, UploadFileIcon, FormatListNumberedIcon, NumbersIcon, PersonIcon, ListAltIcon, ListIcon, FlagIcon, MailIcon, HttpIcon, FormatQuoteIcon, SubjectIcon, ShortTextIcon, MenuIcon, ChevronLeftIcon } from "@firecms/ui";
6
6
  import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
7
7
  import hash from "object-hash";
8
8
  import { getIn, useFormex, setIn, useCreateFormex, Formex, Field } from "@firecms/formex";
@@ -19,7 +19,7 @@ import { useSortable, defaultAnimateLayoutChanges, arrayMove as arrayMove$1, Sor
19
19
  import { CSS } from "@dnd-kit/utilities";
20
20
  import { restrictToVerticalAxis, restrictToWindowEdges } from "@dnd-kit/modifiers";
21
21
  import { useDropzone } from "react-dropzone";
22
- import Resizer from "react-image-file-resizer";
22
+ import Compressor from "compressorjs";
23
23
  import { FireCMSEditor } from "@firecms/editor";
24
24
  import { themes, Highlight } from "prism-react-renderer";
25
25
  import { useLocation as useLocation$1, useBlocker } from "react-router";
@@ -255,6 +255,13 @@ const pick = (obj, ...args) => ({
255
255
  function isObject(item) {
256
256
  return item && typeof item === "object" && !Array.isArray(item);
257
257
  }
258
+ function isPlainObject(obj) {
259
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
260
+ return false;
261
+ }
262
+ const proto = Object.getPrototypeOf(obj);
263
+ return proto === Object.prototype;
264
+ }
258
265
  function mergeDeep(target, source, ignoreUndefined = false) {
259
266
  if (!isObject(target)) {
260
267
  return target;
@@ -275,7 +282,28 @@ function mergeDeep(target, source, ignoreUndefined = false) {
275
282
  if (sourceValue instanceof Date) {
276
283
  output[key] = new Date(sourceValue.getTime());
277
284
  } else if (Array.isArray(sourceValue)) {
278
- output[key] = [...sourceValue];
285
+ if (Array.isArray(outputValue)) {
286
+ const newArray = [];
287
+ const maxLength = Math.max(outputValue.length, sourceValue.length);
288
+ for (let i = 0; i < maxLength; i++) {
289
+ const sourceItem = sourceValue[i];
290
+ const targetItem = outputValue[i];
291
+ if (i >= sourceValue.length) {
292
+ newArray[i] = targetItem;
293
+ } else if (i >= outputValue.length) {
294
+ newArray[i] = sourceItem;
295
+ } else if (sourceItem === null) {
296
+ newArray[i] = targetItem;
297
+ } else if (isObject(sourceItem) && isObject(targetItem)) {
298
+ newArray[i] = mergeDeep(targetItem, sourceItem, ignoreUndefined);
299
+ } else {
300
+ newArray[i] = sourceItem;
301
+ }
302
+ }
303
+ output[key] = newArray;
304
+ } else {
305
+ output[key] = [...sourceValue];
306
+ }
279
307
  } else if (isObject(sourceValue)) {
280
308
  if (isObject(outputValue)) {
281
309
  output[key] = mergeDeep(outputValue, sourceValue, ignoreUndefined);
@@ -1054,6 +1082,12 @@ const applyPermissionsFunctionIfEmpty = (collections, permissionsBuilder) => {
1054
1082
  };
1055
1083
  });
1056
1084
  };
1085
+ function getLocalChangesBackup(collection) {
1086
+ if (!collection.localChangesBackup) {
1087
+ return "manual_apply";
1088
+ }
1089
+ return collection.localChangesBackup;
1090
+ }
1057
1091
  const kebabCaseRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;
1058
1092
  const toKebabCase = (str) => {
1059
1093
  const regExpMatchArray = str.match(kebabCaseRegex);
@@ -3510,26 +3544,6 @@ function makePropertiesEditable(properties) {
3510
3544
  });
3511
3545
  return properties;
3512
3546
  }
3513
- function makePropertiesNonEditable(properties) {
3514
- return Object.entries(properties).reduce((acc, [key, property]) => {
3515
- if (!isPropertyBuilder(property) && property.dataType === "map" && property.properties) {
3516
- const updated = {
3517
- ...property,
3518
- properties: makePropertiesNonEditable(property.properties)
3519
- };
3520
- acc[key] = updated;
3521
- }
3522
- if (isPropertyBuilder(property)) {
3523
- acc[key] = property;
3524
- } else {
3525
- acc[key] = {
3526
- ...property,
3527
- editable: false
3528
- };
3529
- }
3530
- return acc;
3531
- }, {});
3532
- }
3533
3547
  function applyModifyFunction(modifyCollection, collection, parentPaths) {
3534
3548
  if (modifyCollection) {
3535
3549
  const modified = modifyCollection({
@@ -3611,8 +3625,8 @@ function mergePropertyOrBuilder(target, source) {
3611
3625
  return target;
3612
3626
  } else {
3613
3627
  const mergedProperty = mergeDeep(target, source);
3614
- const targetEditable = Boolean(target.editable);
3615
- const sourceEditable = Boolean(source.editable);
3628
+ const targetEditable = target.editable === void 0 ? true : Boolean(target.editable);
3629
+ const sourceEditable = source.editable === void 0 ? true : Boolean(source.editable);
3616
3630
  if (source.dataType === "map" && source.properties) {
3617
3631
  const targetProperties = "properties" in target ? target.properties : {};
3618
3632
  const sourceProperties = "properties" in source ? source.properties : {};
@@ -6638,7 +6652,7 @@ function MapPropertyPreview(t0) {
6638
6652
  const isArrayOrMap = childProperty.dataType === "map" || childProperty === "array";
6639
6653
  return /* @__PURE__ */ jsxs("div", { className: cls(defaultBorderMixin, "last:border-b-0 border-b"), children: [
6640
6654
  /* @__PURE__ */ jsxs("div", { className: "flex flex-row pt-0.5 pb-0.5 gap-2", children: [
6641
- /* @__PURE__ */ jsx("div", { className: "min-w-[140px] w-[25%] py-1", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-mono break-words", color: "secondary", children: childProperty.name }) }),
6655
+ /* @__PURE__ */ jsx("div", { className: "min-w-[140px] w-[25%] py-1", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "break-words font-semibold", color: "secondary", children: childProperty.name }) }),
6642
6656
  /* @__PURE__ */ jsx("div", { className: "flex-grow max-w-[75%]", children: /* @__PURE__ */ jsx(ErrorBoundary, { children: !isArrayOrMap && /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: key_0, value: value[key_0], property: childProperty, size }) }) })
6643
6657
  ] }),
6644
6658
  isArrayOrMap && /* @__PURE__ */ jsx("div", { className: cls(defaultBorderMixin, "border-l pl-4 ml-2 my-2"), children: /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: key_0, value: value[key_0], property: childProperty, size }) })
@@ -6694,7 +6708,7 @@ function _temp$o(t0) {
6694
6708
  const [key, childValue] = t0;
6695
6709
  return /* @__PURE__ */ jsxs("div", { className: cls(defaultBorderMixin, "last:border-b-0 border-b"), children: [
6696
6710
  /* @__PURE__ */ jsxs("div", { className: "flex flex-row pt-0.5 pb-0.5 gap-2", children: [
6697
- /* @__PURE__ */ jsx("div", { className: "min-w-[140px] w-[25%] py-1", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-mono break-words", color: "secondary", children: key }) }, `table-cell-title-${key}-${key}`),
6711
+ /* @__PURE__ */ jsx("div", { className: "min-w-[140px] w-[25%] py-1", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "font-semibold break-words", color: "secondary", children: key }) }, `table-cell-title-${key}-${key}`),
6698
6712
  /* @__PURE__ */ jsx("div", { className: "flex-grow max-w-[75%]", children: childValue && typeof childValue !== "object" && /* @__PURE__ */ jsx(Typography, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: childValue.toString() }) }) })
6699
6713
  ] }),
6700
6714
  typeof childValue === "object" && /* @__PURE__ */ jsx("div", { className: cls(defaultBorderMixin, "border-l pl-4"), children: /* @__PURE__ */ jsx(KeyValuePreview, { value: childValue }) })
@@ -6771,7 +6785,7 @@ function BooleanPreview(t0) {
6771
6785
  return t3;
6772
6786
  }
6773
6787
  function NumberPropertyPreview(t0) {
6774
- const $ = c(10);
6788
+ const $ = c(12);
6775
6789
  const {
6776
6790
  value,
6777
6791
  property,
@@ -6789,38 +6803,42 @@ function NumberPropertyPreview(t0) {
6789
6803
  }
6790
6804
  const enumValues = t1;
6791
6805
  if (!enumValues) {
6792
- let t22;
6793
- if ($[2] !== value) {
6794
- t22 = /* @__PURE__ */ jsx(Fragment, { children: value });
6795
- $[2] = value;
6796
- $[3] = t22;
6806
+ const t22 = size === "small" ? "text-sm" : "";
6807
+ let t32;
6808
+ if ($[2] !== t22 || $[3] !== value) {
6809
+ t32 = /* @__PURE__ */ jsx("span", { className: t22, children: value });
6810
+ $[2] = t22;
6811
+ $[3] = value;
6812
+ $[4] = t32;
6797
6813
  } else {
6798
- t22 = $[3];
6814
+ t32 = $[4];
6799
6815
  }
6800
- return t22;
6816
+ return t32;
6801
6817
  }
6802
6818
  const t2 = size !== "medium" ? "small" : "medium";
6803
6819
  let t3;
6804
- if ($[4] !== enumKey || $[5] !== enumValues || $[6] !== t2) {
6820
+ if ($[5] !== enumKey || $[6] !== enumValues || $[7] !== t2) {
6805
6821
  t3 = /* @__PURE__ */ jsx(EnumValuesChip, { enumKey, enumValues, size: t2 });
6806
- $[4] = enumKey;
6807
- $[5] = enumValues;
6808
- $[6] = t2;
6809
- $[7] = t3;
6822
+ $[5] = enumKey;
6823
+ $[6] = enumValues;
6824
+ $[7] = t2;
6825
+ $[8] = t3;
6810
6826
  } else {
6811
- t3 = $[7];
6827
+ t3 = $[8];
6812
6828
  }
6813
6829
  return t3;
6814
6830
  } else {
6815
- let t1;
6816
- if ($[8] !== value) {
6817
- t1 = /* @__PURE__ */ jsx(Fragment, { children: value });
6818
- $[8] = value;
6831
+ const t1 = size === "small" ? "text-sm" : "";
6832
+ let t2;
6833
+ if ($[9] !== t1 || $[10] !== value) {
6834
+ t2 = /* @__PURE__ */ jsx("span", { className: t1, children: value });
6819
6835
  $[9] = t1;
6836
+ $[10] = value;
6837
+ $[11] = t2;
6820
6838
  } else {
6821
- t1 = $[9];
6839
+ t2 = $[11];
6822
6840
  }
6823
- return t1;
6841
+ return t2;
6824
6842
  }
6825
6843
  }
6826
6844
  function UserDisplay(t0) {
@@ -7369,6 +7387,389 @@ const AsyncPreviewComponent = React.memo(function AsyncPreviewComponentInternal(
7369
7387
  }
7370
7388
  return t3;
7371
7389
  });
7390
+ function buildPropertyLabelAndGetProperty(properties, key) {
7391
+ if (!key) return {
7392
+ label: "",
7393
+ property: void 0
7394
+ };
7395
+ const segments = [];
7396
+ const re = /([^[.\]]+)|\[(\d+)\]/g;
7397
+ let m;
7398
+ while ((m = re.exec(key)) !== null) {
7399
+ if (m[1] !== void 0) segments.push(m[1]);
7400
+ else if (m[2] !== void 0) segments.push(Number(m[2]));
7401
+ }
7402
+ let currentProps = properties;
7403
+ let currentProp;
7404
+ let lastLabel = "";
7405
+ const getArrayOfProp = (p) => {
7406
+ if (!p || p.dataType !== "array") return void 0;
7407
+ return Array.isArray(p.of) ? p.of[0] : p.of;
7408
+ };
7409
+ for (const seg of segments) {
7410
+ if (typeof seg === "number") {
7411
+ lastLabel = `[${seg}]`;
7412
+ if (currentProp?.dataType === "array") {
7413
+ currentProp = getArrayOfProp(currentProp);
7414
+ if (currentProp?.dataType === "map" && currentProp.properties) {
7415
+ currentProps = currentProp.properties;
7416
+ } else {
7417
+ currentProps = void 0;
7418
+ }
7419
+ } else {
7420
+ currentProp = void 0;
7421
+ currentProps = void 0;
7422
+ }
7423
+ continue;
7424
+ }
7425
+ if (currentProps && currentProps[seg]) {
7426
+ const nextProp = currentProps[seg];
7427
+ currentProp = nextProp;
7428
+ lastLabel = nextProp.name || String(seg);
7429
+ if (nextProp.dataType === "map" && nextProp.properties) {
7430
+ currentProps = nextProp.properties;
7431
+ } else if (nextProp.dataType === "array") {
7432
+ currentProps = void 0;
7433
+ } else {
7434
+ currentProps = void 0;
7435
+ }
7436
+ } else {
7437
+ currentProp = void 0;
7438
+ currentProps = void 0;
7439
+ lastLabel = String(seg);
7440
+ }
7441
+ }
7442
+ return {
7443
+ label: lastLabel,
7444
+ property: currentProp
7445
+ };
7446
+ }
7447
+ const pathEndsWithIndex = (p) => /\[\d+\]$/.test(p);
7448
+ const PropertyCollectionView = (t0) => {
7449
+ const $ = c(89);
7450
+ const {
7451
+ data,
7452
+ properties,
7453
+ baseKey: t1,
7454
+ suppressHeader: t2,
7455
+ size: t3
7456
+ } = t0;
7457
+ const baseKey = t1 === void 0 ? "" : t1;
7458
+ const suppressHeader = t2 === void 0 ? false : t2;
7459
+ const size = t3 === void 0 ? "small" : t3;
7460
+ const isTopLevel = !!baseKey && !baseKey.includes(".") && !baseKey.includes("[");
7461
+ if (Array.isArray(data)) {
7462
+ let t4;
7463
+ if ($[0] !== baseKey || $[1] !== properties) {
7464
+ t4 = baseKey ? buildPropertyLabelAndGetProperty(properties, baseKey) : {
7465
+ label: "",
7466
+ property: void 0
7467
+ };
7468
+ $[0] = baseKey;
7469
+ $[1] = properties;
7470
+ $[2] = t4;
7471
+ } else {
7472
+ t4 = $[2];
7473
+ }
7474
+ const {
7475
+ label: arrayLabel,
7476
+ property
7477
+ } = t4;
7478
+ const ofProp = property?.dataType === "array" ? Array.isArray(property.of) ? property.of[0] : property.of : void 0;
7479
+ const isArrayOfMaps = ofProp?.dataType === "map";
7480
+ const isArrayOfPrimitives = property?.dataType === "array" && ofProp && ofProp.dataType !== "map";
7481
+ if (baseKey && property && isArrayOfPrimitives) {
7482
+ const t52 = `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`;
7483
+ let t62;
7484
+ if ($[3] !== arrayLabel) {
7485
+ t62 = /* @__PURE__ */ jsx("div", { className: "col-span-4 pr-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", className: "break-words", children: arrayLabel }) });
7486
+ $[3] = arrayLabel;
7487
+ $[4] = t62;
7488
+ } else {
7489
+ t62 = $[4];
7490
+ }
7491
+ let t72;
7492
+ if ($[5] !== baseKey || $[6] !== data || $[7] !== property || $[8] !== size) {
7493
+ t72 = /* @__PURE__ */ jsx("div", { className: "col-span-8", children: /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: baseKey, value: data, property, size }) });
7494
+ $[5] = baseKey;
7495
+ $[6] = data;
7496
+ $[7] = property;
7497
+ $[8] = size;
7498
+ $[9] = t72;
7499
+ } else {
7500
+ t72 = $[9];
7501
+ }
7502
+ let t82;
7503
+ if ($[10] !== t52 || $[11] !== t62 || $[12] !== t72) {
7504
+ t82 = /* @__PURE__ */ jsxs("div", { className: t52, children: [
7505
+ t62,
7506
+ t72
7507
+ ] });
7508
+ $[10] = t52;
7509
+ $[11] = t62;
7510
+ $[12] = t72;
7511
+ $[13] = t82;
7512
+ } else {
7513
+ t82 = $[13];
7514
+ }
7515
+ return t82;
7516
+ }
7517
+ const t5 = `${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`;
7518
+ let t6;
7519
+ if ($[14] !== arrayLabel || $[15] !== baseKey || $[16] !== suppressHeader) {
7520
+ t6 = baseKey && arrayLabel && !suppressHeader && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", children: arrayLabel });
7521
+ $[14] = arrayLabel;
7522
+ $[15] = baseKey;
7523
+ $[16] = suppressHeader;
7524
+ $[17] = t6;
7525
+ } else {
7526
+ t6 = $[17];
7527
+ }
7528
+ const t7 = baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : "";
7529
+ let t8;
7530
+ if ($[18] !== baseKey || $[19] !== data || $[20] !== isArrayOfMaps || $[21] !== ofProp || $[22] !== properties || $[23] !== size) {
7531
+ let t92;
7532
+ if ($[25] !== baseKey || $[26] !== isArrayOfMaps || $[27] !== ofProp || $[28] !== properties || $[29] !== size) {
7533
+ t92 = (item, index) => {
7534
+ if (item === null || item === void 0) {
7535
+ return null;
7536
+ }
7537
+ const currentKey = baseKey ? `${baseKey}[${index}]` : `[${index}]`;
7538
+ const itemHeader = isArrayOfMaps && ofProp?.name ? `${ofProp.name} [${index}]` : `[${index}]`;
7539
+ return /* @__PURE__ */ jsxs("div", { className: "py-1", children: [
7540
+ /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", children: itemHeader }),
7541
+ /* @__PURE__ */ jsx("div", { className: `pl-4 mt-1 border-l ${defaultBorderMixin}`, children: /* @__PURE__ */ jsx(PropertyCollectionView, { data: item, properties, baseKey: currentKey, suppressHeader: true, size }) })
7542
+ ] }, currentKey);
7543
+ };
7544
+ $[25] = baseKey;
7545
+ $[26] = isArrayOfMaps;
7546
+ $[27] = ofProp;
7547
+ $[28] = properties;
7548
+ $[29] = size;
7549
+ $[30] = t92;
7550
+ } else {
7551
+ t92 = $[30];
7552
+ }
7553
+ t8 = data.map(t92);
7554
+ $[18] = baseKey;
7555
+ $[19] = data;
7556
+ $[20] = isArrayOfMaps;
7557
+ $[21] = ofProp;
7558
+ $[22] = properties;
7559
+ $[23] = size;
7560
+ $[24] = t8;
7561
+ } else {
7562
+ t8 = $[24];
7563
+ }
7564
+ let t9;
7565
+ if ($[31] !== t7 || $[32] !== t8) {
7566
+ t9 = /* @__PURE__ */ jsx("div", { className: t7, children: t8 });
7567
+ $[31] = t7;
7568
+ $[32] = t8;
7569
+ $[33] = t9;
7570
+ } else {
7571
+ t9 = $[33];
7572
+ }
7573
+ let t10;
7574
+ if ($[34] !== t5 || $[35] !== t6 || $[36] !== t9) {
7575
+ t10 = /* @__PURE__ */ jsxs("div", { className: t5, children: [
7576
+ t6,
7577
+ t9
7578
+ ] });
7579
+ $[34] = t5;
7580
+ $[35] = t6;
7581
+ $[36] = t9;
7582
+ $[37] = t10;
7583
+ } else {
7584
+ t10 = $[37];
7585
+ }
7586
+ return t10;
7587
+ }
7588
+ if (typeof data === "object" && data !== null) {
7589
+ let t4;
7590
+ if ($[38] !== baseKey || $[39] !== properties) {
7591
+ t4 = baseKey ? buildPropertyLabelAndGetProperty(properties, baseKey) : {
7592
+ label: "",
7593
+ property: void 0
7594
+ };
7595
+ $[38] = baseKey;
7596
+ $[39] = properties;
7597
+ $[40] = t4;
7598
+ } else {
7599
+ t4 = $[40];
7600
+ }
7601
+ const {
7602
+ label,
7603
+ property: property_0
7604
+ } = t4;
7605
+ if (baseKey && (!property_0 || property_0.dataType !== "map" || !property_0.properties)) {
7606
+ if (!property_0) {
7607
+ return null;
7608
+ }
7609
+ const t52 = `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`;
7610
+ let t62;
7611
+ if ($[41] !== label) {
7612
+ t62 = /* @__PURE__ */ jsx("div", { className: "col-span-4 pr-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", className: "break-words", children: label }) });
7613
+ $[41] = label;
7614
+ $[42] = t62;
7615
+ } else {
7616
+ t62 = $[42];
7617
+ }
7618
+ let t72;
7619
+ if ($[43] !== baseKey || $[44] !== data || $[45] !== property_0 || $[46] !== size) {
7620
+ t72 = /* @__PURE__ */ jsx("div", { className: "col-span-8", children: /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: baseKey, value: data, property: property_0, size }) });
7621
+ $[43] = baseKey;
7622
+ $[44] = data;
7623
+ $[45] = property_0;
7624
+ $[46] = size;
7625
+ $[47] = t72;
7626
+ } else {
7627
+ t72 = $[47];
7628
+ }
7629
+ let t82;
7630
+ if ($[48] !== t52 || $[49] !== t62 || $[50] !== t72) {
7631
+ t82 = /* @__PURE__ */ jsxs("div", { className: t52, children: [
7632
+ t62,
7633
+ t72
7634
+ ] });
7635
+ $[48] = t52;
7636
+ $[49] = t62;
7637
+ $[50] = t72;
7638
+ $[51] = t82;
7639
+ } else {
7640
+ t82 = $[51];
7641
+ }
7642
+ return t82;
7643
+ }
7644
+ let t5;
7645
+ if ($[52] !== baseKey || $[53] !== property_0 || $[54] !== suppressHeader) {
7646
+ t5 = baseKey && !suppressHeader && property_0?.dataType === "map" && (property_0.name || !pathEndsWithIndex(baseKey));
7647
+ $[52] = baseKey;
7648
+ $[53] = property_0;
7649
+ $[54] = suppressHeader;
7650
+ $[55] = t5;
7651
+ } else {
7652
+ t5 = $[55];
7653
+ }
7654
+ const showMapHeader = t5;
7655
+ const headerText = property_0?.name || label;
7656
+ const t6 = `${isTopLevel ? "py-4" : "py-1"} ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`;
7657
+ let t7;
7658
+ if ($[56] !== headerText || $[57] !== showMapHeader) {
7659
+ t7 = showMapHeader && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", children: headerText });
7660
+ $[56] = headerText;
7661
+ $[57] = showMapHeader;
7662
+ $[58] = t7;
7663
+ } else {
7664
+ t7 = $[58];
7665
+ }
7666
+ const t8 = baseKey ? `pl-4 mt-1 border-l ${defaultBorderMixin}` : "";
7667
+ let t9;
7668
+ if ($[59] !== baseKey || $[60] !== data || $[61] !== properties || $[62] !== size) {
7669
+ let t102;
7670
+ if ($[64] !== baseKey || $[65] !== properties || $[66] !== size) {
7671
+ t102 = (t112) => {
7672
+ const [key, value] = t112;
7673
+ if (value === null || value === void 0) {
7674
+ return null;
7675
+ }
7676
+ const currentKey_0 = baseKey ? `${baseKey}.${key}` : key;
7677
+ return /* @__PURE__ */ jsx(PropertyCollectionView, { data: value, properties, baseKey: currentKey_0, size }, currentKey_0);
7678
+ };
7679
+ $[64] = baseKey;
7680
+ $[65] = properties;
7681
+ $[66] = size;
7682
+ $[67] = t102;
7683
+ } else {
7684
+ t102 = $[67];
7685
+ }
7686
+ t9 = Object.entries(data).map(t102);
7687
+ $[59] = baseKey;
7688
+ $[60] = data;
7689
+ $[61] = properties;
7690
+ $[62] = size;
7691
+ $[63] = t9;
7692
+ } else {
7693
+ t9 = $[63];
7694
+ }
7695
+ let t10;
7696
+ if ($[68] !== t8 || $[69] !== t9) {
7697
+ t10 = /* @__PURE__ */ jsx("div", { className: t8, children: t9 });
7698
+ $[68] = t8;
7699
+ $[69] = t9;
7700
+ $[70] = t10;
7701
+ } else {
7702
+ t10 = $[70];
7703
+ }
7704
+ let t11;
7705
+ if ($[71] !== t10 || $[72] !== t6 || $[73] !== t7) {
7706
+ t11 = /* @__PURE__ */ jsxs("div", { className: t6, children: [
7707
+ t7,
7708
+ t10
7709
+ ] });
7710
+ $[71] = t10;
7711
+ $[72] = t6;
7712
+ $[73] = t7;
7713
+ $[74] = t11;
7714
+ } else {
7715
+ t11 = $[74];
7716
+ }
7717
+ return t11;
7718
+ }
7719
+ if (baseKey) {
7720
+ let t4;
7721
+ if ($[75] !== baseKey || $[76] !== properties) {
7722
+ t4 = buildPropertyLabelAndGetProperty(properties, baseKey);
7723
+ $[75] = baseKey;
7724
+ $[76] = properties;
7725
+ $[77] = t4;
7726
+ } else {
7727
+ t4 = $[77];
7728
+ }
7729
+ const {
7730
+ label: label_0,
7731
+ property: property_1
7732
+ } = t4;
7733
+ if (!property_1) {
7734
+ return null;
7735
+ }
7736
+ const t5 = `grid grid-cols-12 gap-x-4 ${isTopLevel ? "py-4" : "py-2"} items-start ${isTopLevel ? `border-b ${defaultBorderMixin} last:border-b-0` : ""}`;
7737
+ let t6;
7738
+ if ($[78] !== label_0) {
7739
+ t6 = /* @__PURE__ */ jsx("div", { className: "col-span-4 pr-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", className: "break-words", children: label_0 }) });
7740
+ $[78] = label_0;
7741
+ $[79] = t6;
7742
+ } else {
7743
+ t6 = $[79];
7744
+ }
7745
+ let t7;
7746
+ if ($[80] !== baseKey || $[81] !== data || $[82] !== property_1 || $[83] !== size) {
7747
+ t7 = /* @__PURE__ */ jsx("div", { className: "col-span-8", children: /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: baseKey, value: data, property: property_1, size }) });
7748
+ $[80] = baseKey;
7749
+ $[81] = data;
7750
+ $[82] = property_1;
7751
+ $[83] = size;
7752
+ $[84] = t7;
7753
+ } else {
7754
+ t7 = $[84];
7755
+ }
7756
+ let t8;
7757
+ if ($[85] !== t5 || $[86] !== t6 || $[87] !== t7) {
7758
+ t8 = /* @__PURE__ */ jsxs("div", { className: t5, children: [
7759
+ t6,
7760
+ t7
7761
+ ] });
7762
+ $[85] = t5;
7763
+ $[86] = t6;
7764
+ $[87] = t7;
7765
+ $[88] = t8;
7766
+ } else {
7767
+ t8 = $[88];
7768
+ }
7769
+ return t8;
7770
+ }
7771
+ return null;
7772
+ };
7372
7773
  function EntityView({
7373
7774
  entity,
7374
7775
  collection,
@@ -7386,31 +7787,17 @@ function EntityView({
7386
7787
  authController
7387
7788
  }), [collection, path, entity, customizationController.propertyConfigs]);
7388
7789
  const properties = resolvedCollection.properties;
7389
- return /* @__PURE__ */ jsx("div", { className: "w-full " + className, children: /* @__PURE__ */ jsxs("div", { className: "w-full mb-4", children: [
7390
- /* @__PURE__ */ jsxs("div", { className: cls(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0"), children: [
7391
- /* @__PURE__ */ jsx("div", { className: "flex items-center w-1/4", children: /* @__PURE__ */ jsx("span", { className: "pl-2 text-sm text-surface-600", children: "Id" }) }),
7392
- /* @__PURE__ */ jsxs("div", { className: "flex-grow p-2 ml-2 w-3/4 text-surface-900 dark:text-white min-h-[56px] flex items-center", children: [
7790
+ return /* @__PURE__ */ jsx("div", { className: "w-full " + className, children: /* @__PURE__ */ jsxs("div", { className: "w-full mb-4 p-4", children: [
7791
+ /* @__PURE__ */ jsxs("div", { className: `grid grid-cols-12 gap-x-4 py-4 items-start border-b ${defaultBorderMixin}`, children: [
7792
+ /* @__PURE__ */ jsx("div", { className: "col-span-4 pr-2", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "secondary", component: "span", className: "break-words", children: "Id" }) }),
7793
+ /* @__PURE__ */ jsx("div", { className: "col-span-8", children: /* @__PURE__ */ jsxs("div", { className: "flex-grow text-surface-900 dark:text-white flex items-center", children: [
7393
7794
  /* @__PURE__ */ jsx("span", { className: "flex-grow mr-2", children: entity.id }),
7394
7795
  customizationController?.entityLinkBuilder && /* @__PURE__ */ jsx("a", { href: customizationController.entityLinkBuilder({
7395
7796
  entity
7396
7797
  }), rel: "noopener noreferrer", target: "_blank", children: /* @__PURE__ */ jsx(IconButton, { children: /* @__PURE__ */ jsx(OpenInNewIcon, { size: "small" }) }) })
7397
- ] })
7798
+ ] }) })
7398
7799
  ] }),
7399
- Object.entries(properties).map(([key, property]) => {
7400
- const value = entity.values?.[key];
7401
- return /* @__PURE__ */ jsxs("div", { className: cls(defaultBorderMixin, "flex justify-between py-2 border-b last:border-b-0"), children: [
7402
- /* @__PURE__ */ jsx("div", { className: "flex items-center w-1/4", children: /* @__PURE__ */ jsx("span", { className: "pl-2 text-sm text-surface-600", children: property.name }) }),
7403
- /* @__PURE__ */ jsx("div", { className: "flex-grow p-2 ml-2 w-3/4 text-surface-900 dark:text-white min-h-[56px] flex items-center", children: /* @__PURE__ */ jsx(
7404
- PropertyPreview,
7405
- {
7406
- propertyKey: key,
7407
- value,
7408
- property,
7409
- size: "medium"
7410
- }
7411
- ) })
7412
- ] }, `reference_previews_${key}`);
7413
- })
7800
+ /* @__PURE__ */ jsx(PropertyCollectionView, { data: entity.values, properties, size: "medium" })
7414
7801
  ] }) });
7415
7802
  }
7416
7803
  function VirtualTableInput(props) {
@@ -7913,7 +8300,8 @@ function useStorageUploadController({
7913
8300
  const processFile = storage?.processFile;
7914
8301
  const metadata = storage?.metadata;
7915
8302
  const size = multipleFilesSupported ? "medium" : "large";
7916
- const compression = storage?.imageCompression;
8303
+ const imageResize = storage?.imageResize;
8304
+ const legacyCompression = storage?.imageCompression;
7917
8305
  const internalInitialValue = getInternalInitialValue(multipleFilesSupported, value, metadata, size);
7918
8306
  const [initialValue, setInitialValue] = useState(value);
7919
8307
  const [internalValue, setInternalValue] = useState(internalInitialValue);
@@ -7985,6 +8373,11 @@ function useStorageUploadController({
7985
8373
  onChange(fieldValue ? fieldValue[0] : null);
7986
8374
  }
7987
8375
  }, [internalValue, multipleFilesSupported, onChange, storage, storageSource]);
8376
+ const onFileUploadError = useCallback((entry_0) => {
8377
+ console.debug("onFileUploadError", entry_0);
8378
+ const newValue_0 = internalValue.filter((item) => item.id !== entry_0.id);
8379
+ setInternalValue(newValue_0);
8380
+ }, [internalValue]);
7988
8381
  const onFilesAdded = useCallback(async (acceptedFiles) => {
7989
8382
  if (!acceptedFiles.length || disabled) return;
7990
8383
  if (processFile) {
@@ -8003,8 +8396,8 @@ function useStorageUploadController({
8003
8396
  let newInternalValue;
8004
8397
  if (multipleFilesSupported) {
8005
8398
  newInternalValue = [...internalValue, ...await Promise.all(acceptedFiles.map(async (file_2) => {
8006
- if (compression && compressionFormat(file_2)) {
8007
- file_2 = await resizeAndCompressImage(file_2, compression);
8399
+ if ((imageResize || legacyCompression) && isImageFile(file_2)) {
8400
+ file_2 = await resizeImage(file_2, imageResize, legacyCompression);
8008
8401
  }
8009
8402
  return {
8010
8403
  id: getRandomId$2(),
@@ -8016,8 +8409,8 @@ function useStorageUploadController({
8016
8409
  }))];
8017
8410
  } else {
8018
8411
  let file_3 = acceptedFiles[0];
8019
- if (compression && compressionFormat(file_3)) {
8020
- file_3 = await resizeAndCompressImage(file_3, compression);
8412
+ if ((imageResize || legacyCompression) && isImageFile(file_3)) {
8413
+ file_3 = await resizeImage(file_3, imageResize, legacyCompression);
8021
8414
  }
8022
8415
  newInternalValue = [{
8023
8416
  id: getRandomId$2(),
@@ -8029,7 +8422,7 @@ function useStorageUploadController({
8029
8422
  }
8030
8423
  newInternalValue = removeDuplicates(newInternalValue);
8031
8424
  setInternalValue(newInternalValue);
8032
- }, [disabled, fileNameBuilder, internalValue, metadata, multipleFilesSupported, size]);
8425
+ }, [disabled, fileNameBuilder, internalValue, metadata, multipleFilesSupported, size, imageResize, legacyCompression]);
8033
8426
  return {
8034
8427
  internalValue,
8035
8428
  setInternalValue,
@@ -8037,6 +8430,7 @@ function useStorageUploadController({
8037
8430
  fileNameBuilder,
8038
8431
  storagePathBuilder,
8039
8432
  onFileUploadComplete,
8433
+ onFileUploadError,
8040
8434
  onFilesAdded,
8041
8435
  multipleFilesSupported
8042
8436
  };
@@ -8067,22 +8461,42 @@ function removeDuplicates(items) {
8067
8461
  function getRandomId$2() {
8068
8462
  return Math.floor(Math.random() * Math.floor(Number.MAX_SAFE_INTEGER));
8069
8463
  }
8070
- const supportedTypes = {
8071
- "image/jpeg": "JPEG",
8072
- "image/png": "PNG",
8073
- "image/webp": "WEBP"
8074
- };
8075
- const compressionFormat = (file) => supportedTypes[file.type] ? supportedTypes[file.type] : null;
8076
- const defaultQuality = 100;
8077
- const resizeAndCompressImage = (file, compression) => new Promise((resolve) => {
8078
- const inputQuality = compression.quality === void 0 ? defaultQuality : compression.quality;
8079
- const quality = inputQuality >= 0 ? inputQuality <= 100 ? inputQuality : 100 : 100;
8080
- const format2 = compressionFormat(file);
8081
- if (!format2) {
8082
- throw Error("resizeAndCompressImage: Unsupported image format");
8083
- }
8084
- Resizer.imageFileResizer(file, compression.maxWidth || Number.MAX_VALUE, compression.maxHeight || Number.MAX_VALUE, format2, quality, 0, (file2) => resolve(file2), "file");
8085
- });
8464
+ function isImageFile(file) {
8465
+ return file.type === "image/jpeg" || file.type === "image/png" || file.type === "image/webp";
8466
+ }
8467
+ async function resizeImage(file, imageResize, legacyCompression) {
8468
+ const maxWidth = imageResize?.maxWidth ?? legacyCompression?.maxWidth;
8469
+ const maxHeight = imageResize?.maxHeight ?? legacyCompression?.maxHeight;
8470
+ const quality = (imageResize?.quality ?? legacyCompression?.quality ?? 80) / 100;
8471
+ const mode = imageResize?.mode ?? "contain";
8472
+ let mimeType = file.type;
8473
+ if (imageResize?.format && imageResize.format !== "original") {
8474
+ mimeType = `image/${imageResize.format}`;
8475
+ }
8476
+ return new Promise((resolve, reject) => {
8477
+ new Compressor(file, {
8478
+ quality,
8479
+ maxWidth,
8480
+ maxHeight,
8481
+ mimeType,
8482
+ // Use cover mode if specified (crops to fit)
8483
+ // Otherwise use contain mode (scales to fit)
8484
+ ...mode === "cover" || mode === void 0 ? {
8485
+ width: maxWidth,
8486
+ height: maxHeight,
8487
+ resize: "cover"
8488
+ } : {},
8489
+ success: (result) => {
8490
+ const compressedFile = new File([result], file.name, {
8491
+ type: result.type,
8492
+ lastModified: Date.now()
8493
+ });
8494
+ resolve(compressedFile);
8495
+ },
8496
+ error: reject
8497
+ });
8498
+ });
8499
+ }
8086
8500
  function StorageUploadProgress({
8087
8501
  storagePath,
8088
8502
  entry,
@@ -9756,50 +10170,41 @@ function customReviver(key, value) {
9756
10170
  }
9757
10171
  return value;
9758
10172
  }
9759
- if (isLocalStorageAvailable) {
9760
- try {
9761
- for (let i = 0; i < localStorage.length; i++) {
9762
- const fullKey = localStorage.key(i);
9763
- if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
9764
- const path = fullKey.substring(LOCAL_STORAGE_PREFIX.length);
9765
- const entityString = localStorage.getItem(fullKey);
9766
- if (entityString) {
9767
- try {
9768
- const entity = JSON.parse(entityString, customReviver);
9769
- entityCache.set(path, entity);
9770
- } catch (parseError) {
9771
- console.error(`Failed to parse entity for path "${path}" from localStorage:`, parseError);
9772
- }
9773
- }
9774
- }
9775
- }
9776
- } catch (error) {
9777
- console.error("Error accessing localStorage during initialization:", error);
9778
- }
9779
- }
9780
10173
  function saveEntityToCache(path, data) {
9781
- entityCache.set(path, data);
9782
10174
  if (isLocalStorageAvailable) {
9783
10175
  try {
9784
10176
  const key = LOCAL_STORAGE_PREFIX + path;
9785
10177
  const entityString = JSON.stringify(data, customReplacer);
10178
+ console.log("Saving entity to localStorage:", {
10179
+ key,
10180
+ entityString
10181
+ });
9786
10182
  localStorage.setItem(key, entityString);
9787
10183
  } catch (error) {
9788
10184
  console.error(`Failed to save entity for path "${path}" to localStorage:`, error);
9789
10185
  }
9790
10186
  }
9791
10187
  }
10188
+ function removeEntityFromMemoryCache(path) {
10189
+ entityCache.delete(path);
10190
+ }
10191
+ function saveEntityToMemoryCache(path, data) {
10192
+ entityCache.set(path, data);
10193
+ }
10194
+ function getEntityFromMemoryCache(path) {
10195
+ return entityCache.get(path);
10196
+ }
9792
10197
  function getEntityFromCache(path) {
9793
- if (entityCache.has(path)) {
9794
- return entityCache.get(path);
9795
- }
9796
10198
  if (isLocalStorageAvailable) {
9797
10199
  try {
9798
10200
  const key = LOCAL_STORAGE_PREFIX + path;
9799
10201
  const entityString = localStorage.getItem(key);
9800
10202
  if (entityString) {
9801
10203
  const entity = JSON.parse(entityString, customReviver);
9802
- entityCache.set(path, entity);
10204
+ console.log("Loaded entity from localStorage:", {
10205
+ key,
10206
+ entity
10207
+ });
9803
10208
  return entity;
9804
10209
  }
9805
10210
  } catch (error) {
@@ -9808,12 +10213,7 @@ function getEntityFromCache(path) {
9808
10213
  }
9809
10214
  return void 0;
9810
10215
  }
9811
- function hasEntityInCache(path) {
9812
- return entityCache.has(path);
9813
- }
9814
10216
  function removeEntityFromCache(path) {
9815
- console.debug("Removing entity from cache", path);
9816
- entityCache.delete(path);
9817
10217
  if (isLocalStorageAvailable) {
9818
10218
  try {
9819
10219
  const key = LOCAL_STORAGE_PREFIX + path;
@@ -9823,6 +10223,26 @@ function removeEntityFromCache(path) {
9823
10223
  }
9824
10224
  }
9825
10225
  }
10226
+ function flattenKeys(obj, prefix = "", result = []) {
10227
+ if (isObject(obj) || Array.isArray(obj)) {
10228
+ const plainObject = isPlainObject(obj);
10229
+ if (!plainObject && prefix) {
10230
+ result.push(prefix);
10231
+ } else {
10232
+ for (const key in obj) {
10233
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
10234
+ const newKey = prefix ? Array.isArray(obj) ? `${prefix}[${key}]` : `${prefix}.${key}` : key;
10235
+ if (isObject(obj[key]) || Array.isArray(obj[key])) {
10236
+ flattenKeys(obj[key], newKey, result);
10237
+ } else {
10238
+ result.push(newKey);
10239
+ }
10240
+ }
10241
+ }
10242
+ }
10243
+ }
10244
+ return result;
10245
+ }
9826
10246
  const EntityCollectionRowActions = function EntityCollectionRowActions2({
9827
10247
  entity,
9828
10248
  collection,
@@ -9850,7 +10270,9 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9850
10270
  const hasCollapsedActions = actions.some((a) => a.collapsed || a.collapsed === void 0);
9851
10271
  const collapsedActions = actions.filter((a_0) => a_0.collapsed || a_0.collapsed === void 0);
9852
10272
  const uncollapsedActions = actions.filter((a_1) => a_1.collapsed === false);
9853
- const hasDraft = hasEntityInCache(fullPath + "/" + entity.id);
10273
+ const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
10274
+ const hasDraft = enableLocalChangesBackup ? getEntityFromCache(fullPath + "/" + entity.id) : false;
10275
+ const iconSize = largeLayout && (size === "m" || size === "l" || size == "xl") ? "medium" : "small";
9854
10276
  return /* @__PURE__ */ jsxs("div", { className: cls("h-full flex items-center justify-center flex-col bg-surface-50 dark:bg-surface-900 bg-opacity-90 dark:bg-opacity-90 z-10", frozen ? "sticky left-0" : ""), onClick: useCallback((event) => {
9855
10277
  event.stopPropagation();
9856
10278
  }, []), style: {
@@ -9860,23 +10282,31 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9860
10282
  contain: "strict"
9861
10283
  }, children: [
9862
10284
  (hasActions || selectionEnabled) && /* @__PURE__ */ jsxs("div", { className: "w-34 flex justify-center", children: [
9863
- uncollapsedActions.map((action, index) => /* @__PURE__ */ jsx(Tooltip, { title: action.name, asChild: true, children: /* @__PURE__ */ jsx(IconButton, { onClick: (event_0) => {
9864
- event_0.stopPropagation();
9865
- action.onClick({
9866
- view: "collection",
9867
- entity,
9868
- fullPath,
9869
- fullIdPath,
9870
- collection,
9871
- context,
9872
- selectionController,
9873
- highlightEntity,
9874
- unhighlightEntity,
9875
- onCollectionChange,
9876
- openEntityMode: openEntityMode ?? collection?.openEntityMode
9877
- });
9878
- }, size: largeLayout ? "medium" : "small", children: action.icon }) }, index)),
9879
- hasCollapsedActions && /* @__PURE__ */ jsx(Menu, { trigger: /* @__PURE__ */ jsx(IconButton, { size: largeLayout ? "medium" : "small", children: /* @__PURE__ */ jsx(MoreVertIcon, {}) }), children: collapsedActions.map((action_0, index_0) => /* @__PURE__ */ jsxs(MenuItem, { onClick: (e) => {
10285
+ uncollapsedActions.map((action, index) => {
10286
+ const isEditAction = action.key === "edit";
10287
+ const tooltip = isEditAction && hasDraft ? "Local unsaved changes" : action.name;
10288
+ let iconButton = /* @__PURE__ */ jsx(IconButton, { onClick: (event_0) => {
10289
+ event_0.stopPropagation();
10290
+ action.onClick({
10291
+ view: "collection",
10292
+ entity,
10293
+ fullPath,
10294
+ fullIdPath,
10295
+ collection,
10296
+ context,
10297
+ selectionController,
10298
+ highlightEntity,
10299
+ unhighlightEntity,
10300
+ onCollectionChange,
10301
+ openEntityMode: openEntityMode ?? collection?.openEntityMode
10302
+ });
10303
+ }, size: iconSize, children: action.icon });
10304
+ if (isEditAction && hasDraft) {
10305
+ iconButton = /* @__PURE__ */ jsx(Badge, { color: "warning", children: iconButton });
10306
+ }
10307
+ return /* @__PURE__ */ jsx(Tooltip, { title: tooltip, asChild: true, children: iconButton }, index);
10308
+ }),
10309
+ hasCollapsedActions && /* @__PURE__ */ jsx(Menu, { trigger: /* @__PURE__ */ jsx(IconButton, { size: iconSize, children: /* @__PURE__ */ jsx(MoreVertIcon, {}) }), children: collapsedActions.map((action_0, index_0) => /* @__PURE__ */ jsxs(MenuItem, { onClick: (e) => {
9880
10310
  e.stopPropagation();
9881
10311
  action_0.onClick({
9882
10312
  view: "collection",
@@ -9895,14 +10325,11 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9895
10325
  action_0.icon,
9896
10326
  action_0.name
9897
10327
  ] }, index_0)) }),
9898
- selectionEnabled && /* @__PURE__ */ jsx(Tooltip, { title: `Select ${entity.id}`, children: /* @__PURE__ */ jsx(Checkbox, { size: largeLayout ? "medium" : "small", checked: Boolean(isSelected), onCheckedChange }) })
10328
+ selectionEnabled && /* @__PURE__ */ jsx(Tooltip, { title: `Select ${entity.id}`, children: /* @__PURE__ */ jsx(Checkbox, { size: iconSize, checked: Boolean(isSelected), onCheckedChange }) })
9899
10329
  ] }),
9900
- !hideId && size !== "xs" && /* @__PURE__ */ jsxs("div", { className: "w-[138px] overflow-hidden truncate font-mono text-xs text-text-secondary dark:text-text-secondary-dark max-w-full text-ellipsis px-2 align-center justify-center flex items-center gap-1", onClick: (event_1) => {
10330
+ !hideId && size !== "xs" && /* @__PURE__ */ jsx("div", { className: "w-[138px] overflow-hidden truncate font-mono text-xs text-text-secondary dark:text-text-secondary-dark max-w-full text-ellipsis px-2 align-center justify-center flex items-center gap-1", onClick: (event_1) => {
9901
10331
  event_1.stopPropagation();
9902
- }, children: [
9903
- hasDraft && /* @__PURE__ */ jsx(Tooltip, { title: "Local unsaved changes", className: "inline", children: /* @__PURE__ */ jsx(Chip, { colorScheme: "orangeDarker", className: "p-0.5", children: /* @__PURE__ */ jsx(EditIcon, { size: 12 }) }) }),
9904
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-center", children: entity ? entity.id : /* @__PURE__ */ jsx(Skeleton, {}) })
9905
- ] })
10332
+ }, children: /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-center", children: entity ? entity.id : /* @__PURE__ */ jsx(Skeleton, {}) }) })
9906
10333
  ] });
9907
10334
  };
9908
10335
  function CollectionTableToolbar(t0) {
@@ -11414,7 +11841,7 @@ function StringNumberFilterField(t0) {
11414
11841
  if (value_1 !== "") {
11415
11842
  updateFilter(operation, dataType === "number" ? parseInt(value_1) : value_1);
11416
11843
  }
11417
- }, endAdornment: internalValue && /* @__PURE__ */ jsx(IconButton, { className: "absolute right-2 top-3", onClick: (e_0) => updateFilter(operation, void 0), children: /* @__PURE__ */ jsx(CloseIcon, {}) }), renderValue: (enumKey) => {
11844
+ }, endAdornment: internalValue && /* @__PURE__ */ jsx(IconButton, { onClick: (e_0) => updateFilter(operation, void 0), children: /* @__PURE__ */ jsx(CloseIcon, {}) }), renderValue: (enumKey) => {
11418
11845
  if (enumKey === null) {
11419
11846
  return "Filter for null values";
11420
11847
  }
@@ -11571,7 +11998,7 @@ const operationLabels = {
11571
11998
  };
11572
11999
  const multipleSelectOperations = ["array-contains-any", "in"];
11573
12000
  function DateTimeFilterField(t0) {
11574
- const $ = c(38);
12001
+ const $ = c(39);
11575
12002
  const {
11576
12003
  isArray,
11577
12004
  mode,
@@ -11612,7 +12039,7 @@ function DateTimeFilterField(t0) {
11612
12039
  newValue = newOpIsArray ? val ? [val] : [] : "";
11613
12040
  }
11614
12041
  setOperation(op);
11615
- setInternalValue(newValue === null ? void 0 : newValue);
12042
+ setInternalValue(newValue);
11616
12043
  const hasNewValue = newValue !== null && Array.isArray(newValue) ? newValue.length > 0 : newValue !== void 0;
11617
12044
  if (op && hasNewValue) {
11618
12045
  setValue([op, newValue]);
@@ -11656,83 +12083,85 @@ function DateTimeFilterField(t0) {
11656
12083
  } else {
11657
12084
  t6 = $[16];
11658
12085
  }
11659
- const t7 = internalValue ?? void 0;
11660
- let t8;
12086
+ const t7 = internalValue === null;
12087
+ const t8 = internalValue ?? void 0;
12088
+ let t9;
11661
12089
  if ($[17] !== operation || $[18] !== updateFilter) {
11662
- t8 = (dateValue) => {
12090
+ t9 = (dateValue) => {
11663
12091
  updateFilter(operation, dateValue === null ? void 0 : dateValue);
11664
12092
  };
11665
12093
  $[17] = operation;
11666
12094
  $[18] = updateFilter;
11667
- $[19] = t8;
12095
+ $[19] = t9;
11668
12096
  } else {
11669
- t8 = $[19];
12097
+ t9 = $[19];
11670
12098
  }
11671
- let t9;
11672
- if ($[20] !== locale || $[21] !== mode || $[22] !== t7 || $[23] !== t8) {
11673
- t9 = /* @__PURE__ */ jsx(DateTimeField, { mode, size: "large", locale, value: t7, onChange: t8, clearable: true });
12099
+ let t10;
12100
+ if ($[20] !== locale || $[21] !== mode || $[22] !== t7 || $[23] !== t8 || $[24] !== t9) {
12101
+ t10 = /* @__PURE__ */ jsx(DateTimeField, { mode, size: "large", locale, disabled: t7, value: t8, onChange: t9, clearable: true });
11674
12102
  $[20] = locale;
11675
12103
  $[21] = mode;
11676
12104
  $[22] = t7;
11677
12105
  $[23] = t8;
11678
12106
  $[24] = t9;
12107
+ $[25] = t10;
11679
12108
  } else {
11680
- t9 = $[24];
12109
+ t10 = $[25];
11681
12110
  }
11682
- const t10 = internalValue === null;
11683
- let t11;
11684
- if ($[25] !== internalValue || $[26] !== operation || $[27] !== updateFilter) {
11685
- t11 = (checked) => {
12111
+ const t11 = internalValue === null;
12112
+ let t12;
12113
+ if ($[26] !== internalValue || $[27] !== operation || $[28] !== updateFilter) {
12114
+ t12 = (checked) => {
11686
12115
  if (internalValue !== null) {
11687
12116
  updateFilter(operation, null);
11688
12117
  } else {
11689
12118
  updateFilter(operation, void 0);
11690
12119
  }
11691
12120
  };
11692
- $[25] = internalValue;
11693
- $[26] = operation;
11694
- $[27] = updateFilter;
11695
- $[28] = t11;
12121
+ $[26] = internalValue;
12122
+ $[27] = operation;
12123
+ $[28] = updateFilter;
12124
+ $[29] = t12;
11696
12125
  } else {
11697
- t11 = $[28];
12126
+ t12 = $[29];
11698
12127
  }
11699
- let t12;
11700
- if ($[29] !== t10 || $[30] !== t11) {
11701
- t12 = /* @__PURE__ */ jsxs(Label, { className: "border cursor-pointer rounded-md p-2 flex items-center gap-2 [&:has(:checked)]:bg-surface-100 dark:[&:has(:checked)]:bg-surface-800", htmlFor: "null-filter", children: [
11702
- /* @__PURE__ */ jsx(Checkbox, { id: "null-filter", checked: t10, size: "small", onCheckedChange: t11 }),
12128
+ let t13;
12129
+ if ($[30] !== t11 || $[31] !== t12) {
12130
+ t13 = /* @__PURE__ */ jsxs(Label, { className: "border cursor-pointer rounded-md p-2 flex items-center gap-2 [&:has(:checked)]:bg-surface-100 dark:[&:has(:checked)]:bg-surface-800", htmlFor: "null-filter", children: [
12131
+ /* @__PURE__ */ jsx(Checkbox, { id: "null-filter", checked: t11, size: "small", onCheckedChange: t12 }),
11703
12132
  "Filter for null values"
11704
12133
  ] });
11705
- $[29] = t10;
11706
12134
  $[30] = t11;
11707
12135
  $[31] = t12;
12136
+ $[32] = t13;
11708
12137
  } else {
11709
- t12 = $[31];
12138
+ t13 = $[32];
11710
12139
  }
11711
- let t13;
11712
- if ($[32] !== t12 || $[33] !== t9) {
11713
- t13 = /* @__PURE__ */ jsxs("div", { className: "flex-grow ml-2 flex flex-col gap-2", children: [
11714
- t9,
11715
- t12
12140
+ let t14;
12141
+ if ($[33] !== t10 || $[34] !== t13) {
12142
+ t14 = /* @__PURE__ */ jsxs("div", { className: "flex-grow ml-2 flex flex-col gap-2", children: [
12143
+ t10,
12144
+ t13
11716
12145
  ] });
11717
- $[32] = t12;
11718
- $[33] = t9;
12146
+ $[33] = t10;
11719
12147
  $[34] = t13;
12148
+ $[35] = t14;
11720
12149
  } else {
11721
- t13 = $[34];
12150
+ t14 = $[35];
11722
12151
  }
11723
- let t14;
11724
- if ($[35] !== t13 || $[36] !== t6) {
11725
- t14 = /* @__PURE__ */ jsxs("div", { className: "flex w-[440px]", children: [
12152
+ let t15;
12153
+ if ($[36] !== t14 || $[37] !== t6) {
12154
+ t15 = /* @__PURE__ */ jsxs("div", { className: "flex w-[440px]", children: [
11726
12155
  t6,
11727
- t13
12156
+ t14
11728
12157
  ] });
11729
- $[35] = t13;
11730
- $[36] = t6;
11731
- $[37] = t14;
12158
+ $[36] = t14;
12159
+ $[37] = t6;
12160
+ $[38] = t15;
11732
12161
  } else {
11733
- t14 = $[37];
12162
+ t15 = $[38];
11734
12163
  }
11735
- return t14;
12164
+ return t15;
11736
12165
  }
11737
12166
  function _temp2$5(op_1) {
11738
12167
  return /* @__PURE__ */ jsx(SelectItem, { value: op_1, children: operationLabels[op_1] }, op_1);
@@ -13877,14 +14306,16 @@ function SortableNavigationGroup(t0) {
13877
14306
  return t5;
13878
14307
  }
13879
14308
  function useHomePageDnd({
13880
- items: dndItems,
13881
- setItems: setDndItems,
14309
+ items,
14310
+ setItems,
13882
14311
  disabled,
13883
14312
  onCardMovedBetweenGroups,
13884
14313
  onGroupMoved,
13885
14314
  onNewGroupDrop,
13886
14315
  onPersist
13887
14316
  }) {
14317
+ const dndItems = items;
14318
+ const setDndItems = setItems;
13888
14319
  const [activeId, setActiveId] = useState(null);
13889
14320
  const [activeIsGroup, setActiveIsGroup] = useState(false);
13890
14321
  const [currentDraggingGroupId, setCurrentDraggingGroupId] = useState(null);
@@ -13892,6 +14323,9 @@ function useHomePageDnd({
13892
14323
  const [isDraggingCardOnly, setIsDraggingCardOnly] = useState(false);
13893
14324
  const [dialogOpenForGroup, setDialogOpenForGroup] = useState(null);
13894
14325
  const [isHoveringNewGroupDropZone, setIsHoveringNewGroupDropZone] = useState(false);
14326
+ const [pendingNewGroupName, setPendingNewGroupName] = useState(null);
14327
+ const [stateBeforeNewGroup, setStateBeforeNewGroup] = useState(null);
14328
+ const preDragItemsRef = useRef(null);
13895
14329
  const interimItemsRef = useRef(null);
13896
14330
  useEffect(() => {
13897
14331
  interimItemsRef.current = dndItems;
@@ -13983,6 +14417,7 @@ function useHomePageDnd({
13983
14417
  }) => {
13984
14418
  setDndKitActiveNode(active);
13985
14419
  if (disabled) return;
14420
+ preDragItemsRef.current = cloneItemsForDnd(dndItems);
13986
14421
  const isGroup = dndItems.some((g_7) => g_7.name === active.id);
13987
14422
  if (!active.data.current) active.data.current = {};
13988
14423
  active.data.current.type = isGroup ? "group" : "item";
@@ -14006,18 +14441,30 @@ function useHomePageDnd({
14006
14441
  if (!activeCont) return;
14007
14442
  if (overCont && activeCont !== overCont) {
14008
14443
  recentlyMovedToNewContainer.current = true;
14009
- const newState = cloneItemsForDnd(dndItems);
14010
- const srcIdx = newState.findIndex((g_8) => g_8.name === activeCont);
14011
- const tgtIdx = newState.findIndex((g_9) => g_9.name === overCont);
14012
- if (srcIdx === -1 || tgtIdx === -1) return;
14013
- const src = newState[srcIdx];
14014
- const tgt = newState[tgtIdx];
14015
- const idxInSrc = src.entries.findIndex((e_2) => e_2.url === activeIdNow);
14016
- if (idxInSrc === -1) return;
14017
- const [moved] = src.entries.splice(idxInSrc, 1);
14018
- tgt.entries.push(moved);
14019
- interimItemsRef.current = newState;
14020
- setDndItems(newState);
14444
+ lastOverId.current = overIdNow;
14445
+ setDndItems((current) => {
14446
+ const newState = cloneItemsForDnd(current);
14447
+ const srcIdx = newState.findIndex((g_8) => g_8.name === activeCont);
14448
+ const tgtIdx = newState.findIndex((g_9) => g_9.name === overCont);
14449
+ if (srcIdx === -1 || tgtIdx === -1) return current;
14450
+ const src = newState[srcIdx];
14451
+ const tgt = newState[tgtIdx];
14452
+ const idxInSrc = src.entries.findIndex((e_2) => e_2.url === activeIdNow);
14453
+ if (idxInSrc === -1) return current;
14454
+ const [moved] = src.entries.splice(idxInSrc, 1);
14455
+ const overIsContainer_0 = overIdNow === overCont;
14456
+ if (overIsContainer_0) {
14457
+ tgt.entries.push(moved);
14458
+ } else {
14459
+ const overIdx = tgt.entries.findIndex((e_3) => e_3.url === overIdNow);
14460
+ if (overIdx !== -1) {
14461
+ tgt.entries.splice(overIdx, 0, moved);
14462
+ } else {
14463
+ tgt.entries.push(moved);
14464
+ }
14465
+ }
14466
+ return newState;
14467
+ });
14021
14468
  } else if (activeCont === overCont) {
14022
14469
  recentlyMovedToNewContainer.current = false;
14023
14470
  }
@@ -14044,55 +14491,101 @@ function useHomePageDnd({
14044
14491
  }
14045
14492
  }
14046
14493
  } else {
14047
- const activeCont_0 = findDndContainer(activeIdNow_0);
14494
+ const findContainerInState = (id_0, state) => {
14495
+ const group_0 = state.find((g_13) => g_13.name === id_0);
14496
+ if (group_0) return group_0.name;
14497
+ for (const g_14 of state) {
14498
+ if (g_14.entries.some((e_4) => e_4.url === id_0)) return g_14.name;
14499
+ }
14500
+ return void 0;
14501
+ };
14502
+ const sourceState = preDragItemsRef.current || dndItems;
14503
+ const activeCont_0 = findContainerInState(activeIdNow_0, sourceState);
14504
+ findDndContainer(overIdNow_0);
14048
14505
  if (overIdNow_0 === "new-group-drop-zone") {
14049
14506
  if (activeCont_0) {
14507
+ setStateBeforeNewGroup(cloneItemsForDnd(dndItems));
14050
14508
  const newState_1 = cloneItemsForDnd(dndItems);
14051
- const srcIdx_0 = newState_1.findIndex((g_13) => g_13.name === activeCont_0);
14509
+ const srcIdx_0 = newState_1.findIndex((g_15) => g_15.name === activeCont_0);
14052
14510
  if (srcIdx_0 !== -1) {
14053
14511
  const src_0 = newState_1[srcIdx_0];
14054
- const idxInSrc_0 = src_0.entries.findIndex((e_3) => e_3.url === activeIdNow_0);
14512
+ const idxInSrc_0 = src_0.entries.findIndex((e_5) => e_5.url === activeIdNow_0);
14055
14513
  if (idxInSrc_0 !== -1) {
14056
14514
  const [dragged] = src_0.entries.splice(idxInSrc_0, 1);
14057
14515
  if (src_0.entries.length === 0) newState_1.splice(srcIdx_0, 1);
14058
14516
  let tentative = "New Group";
14059
14517
  let counter = 1;
14060
- while (newState_1.some((g_14) => g_14.name === tentative)) tentative = `New Group ${counter++}`;
14518
+ while (newState_1.some((g_16) => g_16.name === tentative)) tentative = `New Group ${counter++}`;
14061
14519
  newState_1.push({
14062
14520
  name: tentative,
14063
14521
  entries: [dragged]
14064
14522
  });
14065
14523
  setDndItems(newState_1);
14066
- onPersist?.(newState_1);
14524
+ setPendingNewGroupName(tentative);
14067
14525
  setDialogOpenForGroup(tentative);
14068
14526
  onNewGroupDrop?.();
14069
14527
  }
14070
14528
  }
14071
14529
  }
14072
14530
  } else {
14073
- const overCont_0 = findDndContainer(overIdNow_0);
14074
- if (activeCont_0 === overCont_0) {
14075
- const grpIdx = dndItems.findIndex((g_15) => g_15.name === activeCont_0);
14531
+ const overCont_1 = findDndContainer(overIdNow_0);
14532
+ if (activeCont_0 === overCont_1) {
14533
+ const grpIdx = dndItems.findIndex((g_17) => g_17.name === activeCont_0);
14076
14534
  if (grpIdx !== -1) {
14077
- const group_0 = dndItems[grpIdx];
14078
- const oldIdx = group_0.entries.findIndex((e_4) => e_4.url === activeIdNow_0);
14079
- let newIdx = group_0.entries.findIndex((e_5) => e_5.url === overIdNow_0);
14080
- if (newIdx === -1 && overIdNow_0 === activeCont_0) newIdx = group_0.entries.length - 1;
14535
+ const group_1 = dndItems[grpIdx];
14536
+ const oldIdx = group_1.entries.findIndex((e_6) => e_6.url === activeIdNow_0);
14537
+ let newIdx = group_1.entries.findIndex((e_7) => e_7.url === overIdNow_0);
14538
+ if (newIdx === -1 && overIdNow_0 === activeCont_0) newIdx = group_1.entries.length - 1;
14081
14539
  if (oldIdx !== -1 && newIdx !== -1 && oldIdx !== newIdx) {
14082
- const reordered = arrayMove$1(group_0.entries, oldIdx, newIdx);
14540
+ const reordered = arrayMove$1(group_1.entries, oldIdx, newIdx);
14083
14541
  const newState_2 = [...dndItems];
14084
14542
  newState_2[grpIdx] = {
14085
- ...group_0,
14543
+ ...group_1,
14086
14544
  entries: reordered
14087
14545
  };
14088
14546
  setDndItems(newState_2);
14089
14547
  onPersist?.(newState_2);
14090
14548
  }
14091
14549
  }
14092
- } else if (recentlyMovedToNewContainer.current && interimItemsRef.current) {
14093
- onPersist?.(interimItemsRef.current);
14550
+ } else if (overCont_1 && activeCont_0 !== overCont_1) {
14551
+ const finalState = cloneItemsForDnd(sourceState);
14552
+ const finalOverId = lastOverId.current || overIdNow_0;
14553
+ const cleanOverCont = findContainerInState(finalOverId, sourceState) || overCont_1;
14554
+ const srcIdx_1 = finalState.findIndex((g_18) => g_18.name === activeCont_0);
14555
+ const tgtIdx_0 = finalState.findIndex((g_19) => g_19.name === cleanOverCont);
14556
+ if (srcIdx_1 !== -1 && tgtIdx_0 !== -1) {
14557
+ const src_1 = finalState[srcIdx_1];
14558
+ const tgt_0 = finalState[tgtIdx_0];
14559
+ const idxInSrc_1 = src_1.entries.findIndex((e_8) => e_8.url === activeIdNow_0);
14560
+ if (idxInSrc_1 !== -1) {
14561
+ const [moved_0] = src_1.entries.splice(idxInSrc_1, 1);
14562
+ const overIsContainer_1 = finalOverId === cleanOverCont;
14563
+ if (overIsContainer_1) {
14564
+ tgt_0.entries.push(moved_0);
14565
+ } else {
14566
+ const overIdx_0 = tgt_0.entries.findIndex((e_9) => e_9.url === finalOverId);
14567
+ if (overIdx_0 !== -1) {
14568
+ tgt_0.entries.splice(overIdx_0, 0, moved_0);
14569
+ } else {
14570
+ tgt_0.entries.push(moved_0);
14571
+ }
14572
+ }
14573
+ if (src_1.entries.length === 0) {
14574
+ finalState.splice(srcIdx_1, 1);
14575
+ }
14576
+ setDndItems(finalState);
14577
+ onPersist?.(finalState);
14578
+ onCardMovedBetweenGroups?.(moved_0);
14579
+ }
14580
+ }
14581
+ } else if (recentlyMovedToNewContainer.current) {
14582
+ console.error("Move between containers detected but conditions not met", {
14583
+ activeCont: activeCont_0,
14584
+ overCont: overCont_1,
14585
+ activeIdNow: activeIdNow_0,
14586
+ overIdNow: overIdNow_0
14587
+ });
14094
14588
  }
14095
- onCardMovedBetweenGroups?.(dndItems.flatMap((g_16) => g_16.entries).find((e_6) => e_6.url === activeIdNow_0));
14096
14589
  }
14097
14590
  }
14098
14591
  resetDragState();
@@ -14107,11 +14600,11 @@ function useHomePageDnd({
14107
14600
  };
14108
14601
  const handleDragCancel = () => resetDragState();
14109
14602
  const handleRenameGroup = (oldName, newName) => {
14110
- setDndItems((current) => {
14111
- const idx = current.findIndex((g_17) => g_17.name === oldName);
14112
- if (idx === -1) return current;
14113
- if (current.some((g_18) => g_18.name === newName && g_18.name !== oldName)) return current;
14114
- const updated = [...current];
14603
+ setDndItems((current_0) => {
14604
+ const idx = current_0.findIndex((g_20) => g_20.name === oldName);
14605
+ if (idx === -1) return current_0;
14606
+ if (current_0.some((g_21) => g_21.name === newName && g_21.name !== oldName)) return current_0;
14607
+ const updated = [...current_0];
14115
14608
  updated[idx] = {
14116
14609
  ...updated[idx],
14117
14610
  name: newName
@@ -14119,10 +14612,20 @@ function useHomePageDnd({
14119
14612
  onPersist?.(updated);
14120
14613
  return updated;
14121
14614
  });
14615
+ setPendingNewGroupName(null);
14616
+ setStateBeforeNewGroup(null);
14617
+ setDialogOpenForGroup(null);
14618
+ };
14619
+ const handleDialogClose = () => {
14620
+ if (pendingNewGroupName && dialogOpenForGroup === pendingNewGroupName && stateBeforeNewGroup) {
14621
+ setDndItems(stateBeforeNewGroup);
14622
+ }
14623
+ setPendingNewGroupName(null);
14624
+ setStateBeforeNewGroup(null);
14122
14625
  setDialogOpenForGroup(null);
14123
14626
  };
14124
- const activeItemForOverlay = disabled || !activeId || activeIsGroup ? null : dndItems.flatMap((g_19) => g_19.entries).find((e_7) => e_7.url === activeId) || null;
14125
- const activeGroupData = disabled || !activeId || !activeIsGroup ? null : dndItems.find((g_20) => g_20.name === activeId) || null;
14627
+ const activeItemForOverlay = disabled || !activeId || activeIsGroup ? null : dndItems.flatMap((g_22) => g_22.entries).find((e_10) => e_10.url === activeId) || null;
14628
+ const activeGroupData = disabled || !activeId || !activeIsGroup ? null : dndItems.find((g_23) => g_23.name === activeId) || null;
14126
14629
  return {
14127
14630
  sensors,
14128
14631
  collisionDetection,
@@ -14140,6 +14643,7 @@ function useHomePageDnd({
14140
14643
  dialogOpenForGroup,
14141
14644
  setDialogOpenForGroup,
14142
14645
  handleRenameGroup,
14646
+ handleDialogClose,
14143
14647
  isHoveringNewGroupDropZone,
14144
14648
  setIsHoveringNewGroupDropZone
14145
14649
  };
@@ -14517,7 +15021,7 @@ function DefaultHomePage({
14517
15021
  entries: []
14518
15022
  });
14519
15023
  }
14520
- allProcessed = allProcessed.filter((g_4) => g_4.entries.length || groupOrderFromNavController.includes(g_4.name) || g_4.name === DEFAULT_GROUP_NAME && hasPluginAdditionalCards);
15024
+ allProcessed = allProcessed.filter((g_4) => g_4.entries.length || g_4.name === DEFAULT_GROUP_NAME && hasPluginAdditionalCards);
14521
15025
  }
14522
15026
  const admin = allProcessed.find((g_5) => g_5.name === ADMIN_GROUP_NAME);
14523
15027
  return {
@@ -14565,6 +15069,7 @@ function DefaultHomePage({
14565
15069
  dialogOpenForGroup,
14566
15070
  setDialogOpenForGroup,
14567
15071
  handleRenameGroup,
15072
+ handleDialogClose,
14568
15073
  isHoveringNewGroupDropZone,
14569
15074
  setIsHoveringNewGroupDropZone
14570
15075
  } = useHomePageDnd({
@@ -14572,7 +15077,6 @@ function DefaultHomePage({
14572
15077
  setItems: updateItems,
14573
15078
  disabled: !allowDragAndDrop || performingSearch,
14574
15079
  onPersist: persistNavigationGroups,
14575
- // ——► persistence here
14576
15080
  onGroupMoved: (g_8) => context.analyticsController?.onAnalyticsEvent?.("home_move_group", {
14577
15081
  name: g_8
14578
15082
  }),
@@ -14618,7 +15122,7 @@ function DefaultHomePage({
14618
15122
  frequency: 500
14619
15123
  }
14620
15124
  }, onDragStart, onDragOver, onDragEnd, onDragCancel, modifiers: dndModifiers, children: [
14621
- /* @__PURE__ */ jsx(SortableContext, { items: containers, strategy: verticalListSortingStrategy, children: items.map((groupData) => {
15125
+ /* @__PURE__ */ jsx(SortableContext, { items: containers, strategy: verticalListSortingStrategy, children: items.map((groupData, groupIndex) => {
14622
15126
  const groupKey = groupData.name;
14623
15127
  const entriesInGroup = groupData.entries;
14624
15128
  const AdditionalCards = [];
@@ -14629,7 +15133,7 @@ function DefaultHomePage({
14629
15133
  group: groupKey === DEFAULT_GROUP_NAME ? void 0 : groupKey,
14630
15134
  context
14631
15135
  };
14632
- if (entriesInGroup.length === 0 && (AdditionalCards.length === 0 || performingSearch) && !groupOrderFromNavController.includes(groupKey)) return null;
15136
+ if (entriesInGroup.length === 0 && (AdditionalCards.length === 0 || performingSearch)) return null;
14633
15137
  return /* @__PURE__ */ jsx(SortableNavigationGroup, { groupName: groupKey, disabled: dndDisabled, children: /* @__PURE__ */ jsx(NavigationGroup, { group: groupKey === DEFAULT_GROUP_NAME ? void 0 : groupKey, minimised: draggingGroupId === groupKey && !isDraggingCardOnly, isPotentialCardDropTarget: isDraggingCardOnly, dndDisabled, onEditGroup: () => {
14634
15138
  if (dndDisabled) return;
14635
15139
  setDialogOpenForGroup(groupKey);
@@ -14644,7 +15148,7 @@ function DefaultHomePage({
14644
15148
  });
14645
15149
  } }, entry.url)),
14646
15150
  !performingSearch && groupKey.toLowerCase() !== ADMIN_GROUP_NAME.toLowerCase() && AdditionalCards.map((C, i_1) => /* @__PURE__ */ jsx(C, { ...actionProps }, `extra_${groupKey}_${i_1}`))
14647
- ] }) }) }) }, groupKey);
15151
+ ] }) }) }) }, `group-${groupIndex}`);
14648
15152
  }) }, JSON.stringify(containers)),
14649
15153
  /* @__PURE__ */ jsx(NewGroupDropZone, { disabled: dndDisabled, setIsHovering: setIsHoveringNewGroupDropZone }),
14650
15154
  /* @__PURE__ */ jsx(DragOverlay, { adjustScale: false, dropAnimation, children: activeGroupData && draggingGroupId === activeGroupData.name ? /* @__PURE__ */ jsx("div", { className: "rounded-lg bg-transparent", style: {
@@ -14665,7 +15169,7 @@ function DefaultHomePage({
14665
15169
  additionalPluginChildrenEnd,
14666
15170
  additionalChildrenEnd
14667
15171
  ] }),
14668
- dialogOpenForGroup && /* @__PURE__ */ jsx(RenameGroupDialog, { open: true, initialName: dialogOpenForGroup, existingGroupNames: items.map((g_9) => g_9.name).filter((n) => n !== dialogOpenForGroup), onClose: () => setDialogOpenForGroup(null), onRename: (newName) => {
15172
+ dialogOpenForGroup && /* @__PURE__ */ jsx(RenameGroupDialog, { open: true, initialName: dialogOpenForGroup, existingGroupNames: items.map((g_9) => g_9.name).filter((n) => n !== dialogOpenForGroup), onClose: handleDialogClose, onRename: (newName) => {
14669
15173
  handleRenameGroup(dialogOpenForGroup, newName);
14670
15174
  } })
14671
15175
  ] });
@@ -14989,61 +15493,60 @@ function CustomIdField({
14989
15493
  ] });
14990
15494
  }
14991
15495
  const ErrorFocus = (t0) => {
14992
- const $ = c(6);
15496
+ const $ = c(10);
14993
15497
  const {
14994
15498
  containerRef
14995
15499
  } = t0;
14996
15500
  const {
14997
- isSubmitting,
14998
15501
  isValidating,
14999
- errors
15502
+ errors,
15503
+ version
15000
15504
  } = useFormex();
15505
+ const prevVersion = useRef(version);
15001
15506
  let t1;
15002
- let t2;
15003
- if ($[0] !== containerRef || $[1] !== errors || $[2] !== isSubmitting || $[3] !== isValidating) {
15507
+ if ($[0] !== containerRef?.current || $[1] !== errors || $[2] !== isValidating || $[3] !== version) {
15004
15508
  t1 = () => {
15509
+ if (version === prevVersion.current) {
15510
+ return;
15511
+ }
15005
15512
  const keys = Object.keys(errors);
15006
- if (keys.length > 0 && isSubmitting && !isValidating) {
15513
+ if (!isValidating && keys.length > 0) {
15007
15514
  const errorElement = containerRef?.current?.querySelector(`#form_field_${keys[0]}`);
15008
- if (errorElement && containerRef?.current) {
15009
- const scrollableParent = getScrollableParent(containerRef.current);
15010
- if (scrollableParent) {
15011
- const top = errorElement.getBoundingClientRect().top;
15012
- scrollableParent.scrollTo({
15013
- top: scrollableParent.scrollTop + top - 196,
15014
- behavior: "smooth"
15015
- });
15016
- }
15515
+ if (errorElement) {
15516
+ errorElement.scrollIntoView({
15517
+ behavior: "smooth",
15518
+ block: "center"
15519
+ });
15017
15520
  const input = errorElement.querySelector("input");
15018
15521
  if (input) {
15019
15522
  input.focus();
15020
15523
  }
15021
15524
  }
15525
+ prevVersion.current = version;
15022
15526
  }
15023
15527
  };
15024
- t2 = [isSubmitting, isValidating, errors, containerRef];
15025
- $[0] = containerRef;
15528
+ $[0] = containerRef?.current;
15026
15529
  $[1] = errors;
15027
- $[2] = isSubmitting;
15028
- $[3] = isValidating;
15530
+ $[2] = isValidating;
15531
+ $[3] = version;
15029
15532
  $[4] = t1;
15030
- $[5] = t2;
15031
15533
  } else {
15032
15534
  t1 = $[4];
15033
- t2 = $[5];
15535
+ }
15536
+ let t2;
15537
+ if ($[5] !== containerRef || $[6] !== errors || $[7] !== isValidating || $[8] !== version) {
15538
+ t2 = [isValidating, errors, containerRef, version];
15539
+ $[5] = containerRef;
15540
+ $[6] = errors;
15541
+ $[7] = isValidating;
15542
+ $[8] = version;
15543
+ $[9] = t2;
15544
+ } else {
15545
+ t2 = $[9];
15034
15546
  }
15035
15547
  useEffect(t1, t2);
15036
15548
  return null;
15037
15549
  };
15038
- const isScrollable = (ele) => {
15039
- const hasScrollableContent = ele && ele.scrollHeight > ele.clientHeight;
15040
- const overflowYStyle = ele ? window.getComputedStyle(ele).overflowY : null;
15041
- const isOverflowHidden = overflowYStyle && overflowYStyle.indexOf("hidden") !== -1;
15042
- return hasScrollableContent && !isOverflowHidden;
15043
- };
15044
- const getScrollableParent = (ele) => {
15045
- return !ele || ele === document.body ? document.body : isScrollable(ele) ? ele : getScrollableParent(ele.parentNode);
15046
- };
15047
15550
  function EntityFormActions(t0) {
15048
15551
  const $ = c(16);
15049
15552
  const {
@@ -15064,7 +15567,7 @@ function EntityFormActions(t0) {
15064
15567
  const context = useFireCMSContext();
15065
15568
  const sideEntityController = useSideEntityController();
15066
15569
  let t1;
15067
- if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formContext || $[5] !== formex.isSubmitting || $[6] !== fullIdPath || $[7] !== fullPath || $[8] !== layout || $[9] !== navigateBack || $[10] !== openEntityMode || $[11] !== pluginActions || $[12] !== savingError || $[13] !== sideEntityController || $[14] !== status) {
15570
+ if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formContext || $[5] !== formex || $[6] !== fullIdPath || $[7] !== fullPath || $[8] !== layout || $[9] !== navigateBack || $[10] !== openEntityMode || $[11] !== pluginActions || $[12] !== savingError || $[13] !== sideEntityController || $[14] !== status) {
15068
15571
  t1 = layout === "bottom" ? buildBottomActions$1({
15069
15572
  fullPath,
15070
15573
  fullIdPath,
@@ -15073,13 +15576,13 @@ function EntityFormActions(t0) {
15073
15576
  collection,
15074
15577
  context,
15075
15578
  sideEntityController,
15076
- isSubmitting: formex.isSubmitting,
15077
15579
  disabled,
15078
15580
  status,
15079
15581
  pluginActions,
15080
15582
  openEntityMode,
15081
15583
  navigateBack,
15082
- formContext
15584
+ formContext,
15585
+ formex
15083
15586
  }) : buildSideActions$1({
15084
15587
  fullPath,
15085
15588
  fullIdPath,
@@ -15088,18 +15591,18 @@ function EntityFormActions(t0) {
15088
15591
  collection,
15089
15592
  context,
15090
15593
  sideEntityController,
15091
- isSubmitting: formex.isSubmitting,
15092
15594
  disabled,
15093
15595
  status,
15094
15596
  pluginActions,
15095
- openEntityMode
15597
+ openEntityMode,
15598
+ formex
15096
15599
  });
15097
15600
  $[0] = collection;
15098
15601
  $[1] = context;
15099
15602
  $[2] = disabled;
15100
15603
  $[3] = entity;
15101
15604
  $[4] = formContext;
15102
- $[5] = formex.isSubmitting;
15605
+ $[5] = formex;
15103
15606
  $[6] = fullIdPath;
15104
15607
  $[7] = fullPath;
15105
15608
  $[8] = layout;
@@ -15124,14 +15627,15 @@ function buildBottomActions$1({
15124
15627
  collection,
15125
15628
  context,
15126
15629
  sideEntityController,
15127
- isSubmitting,
15128
15630
  disabled,
15129
15631
  status,
15130
15632
  pluginActions,
15131
15633
  openEntityMode,
15132
15634
  navigateBack,
15133
- formContext
15635
+ formContext,
15636
+ formex
15134
15637
  }) {
15638
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
15135
15639
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
15136
15640
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
15137
15641
  entity && (formActions ?? []).length > 0 && /* @__PURE__ */ jsx("div", { className: "flex-grow flex overflow-auto no-scrollbar", children: (formActions ?? []).map((action) => /* @__PURE__ */ jsx(IconButton, { color: "primary", onClick: (event) => {
@@ -15150,8 +15654,8 @@ function buildBottomActions$1({
15150
15654
  });
15151
15655
  }, children: action.icon }, action.name)) }),
15152
15656
  pluginActions,
15153
- /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15154
- /* @__PURE__ */ jsxs(Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, children: [
15657
+ /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || formex.isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15658
+ /* @__PURE__ */ jsxs(Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || formex.isSubmitting, startIcon: hasErrors ? /* @__PURE__ */ jsx(ErrorIcon, {}) : void 0, children: [
15155
15659
  status === "existing" && "Save",
15156
15660
  status === "copy" && "Create copy",
15157
15661
  status === "new" && "Create"
@@ -15168,22 +15672,344 @@ function buildSideActions$1({
15168
15672
  collection,
15169
15673
  context,
15170
15674
  sideEntityController,
15171
- isSubmitting,
15172
15675
  disabled,
15173
15676
  status,
15174
- pluginActions
15677
+ pluginActions,
15678
+ formex
15175
15679
  }) {
15680
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
15176
15681
  return /* @__PURE__ */ jsxs("div", { className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin), children: [
15177
- /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, children: [
15682
+ /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", startIcon: hasErrors ? /* @__PURE__ */ jsx(ErrorIcon, {}) : void 0, disabled: disabled || formex.isSubmitting, children: [
15178
15683
  status === "existing" && "Save",
15179
15684
  status === "copy" && "Create copy",
15180
15685
  status === "new" && "Create"
15181
15686
  ] }),
15182
- /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15687
+ /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || formex.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15183
15688
  pluginActions,
15184
15689
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) })
15185
15690
  ] });
15186
15691
  }
15692
+ function LocalChangesMenu(t0) {
15693
+ const $ = c(42);
15694
+ const {
15695
+ localChangesData,
15696
+ formex,
15697
+ onClearLocalChanges,
15698
+ cacheKey,
15699
+ properties
15700
+ } = t0;
15701
+ const snackbarController = useSnackbarController();
15702
+ const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
15703
+ const [open, setOpen] = useState(false);
15704
+ let t1;
15705
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
15706
+ t1 = () => setOpen(true);
15707
+ $[0] = t1;
15708
+ } else {
15709
+ t1 = $[0];
15710
+ }
15711
+ const handleOpenMenu = t1;
15712
+ let t2;
15713
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
15714
+ t2 = () => setOpen(false);
15715
+ $[1] = t2;
15716
+ } else {
15717
+ t2 = $[1];
15718
+ }
15719
+ const handleCloseMenu = t2;
15720
+ let t3;
15721
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
15722
+ t3 = () => {
15723
+ setPreviewDialogOpen(true);
15724
+ handleCloseMenu();
15725
+ };
15726
+ $[2] = t3;
15727
+ } else {
15728
+ t3 = $[2];
15729
+ }
15730
+ const handlePreview = t3;
15731
+ let t4;
15732
+ if ($[3] !== formex || $[4] !== localChangesData || $[5] !== onClearLocalChanges || $[6] !== snackbarController) {
15733
+ t4 = () => {
15734
+ const mergedValues = mergeDeep(formex.values, localChangesData);
15735
+ const touched = {
15736
+ ...formex.touched
15737
+ };
15738
+ const previewKeys = flattenKeys(localChangesData);
15739
+ previewKeys.forEach((key) => {
15740
+ touched[key] = true;
15741
+ });
15742
+ formex.setTouched(touched);
15743
+ formex.setValues(mergedValues);
15744
+ snackbarController.open({
15745
+ type: "info",
15746
+ message: "Local changes applied to the form"
15747
+ });
15748
+ handleCloseMenu();
15749
+ onClearLocalChanges?.();
15750
+ };
15751
+ $[3] = formex;
15752
+ $[4] = localChangesData;
15753
+ $[5] = onClearLocalChanges;
15754
+ $[6] = snackbarController;
15755
+ $[7] = t4;
15756
+ } else {
15757
+ t4 = $[7];
15758
+ }
15759
+ const handleApply = t4;
15760
+ let t5;
15761
+ if ($[8] !== cacheKey || $[9] !== onClearLocalChanges || $[10] !== snackbarController) {
15762
+ t5 = () => {
15763
+ removeEntityFromCache(cacheKey);
15764
+ snackbarController.open({
15765
+ type: "info",
15766
+ message: "Local changes discarded"
15767
+ });
15768
+ handleCloseMenu();
15769
+ onClearLocalChanges?.();
15770
+ };
15771
+ $[8] = cacheKey;
15772
+ $[9] = onClearLocalChanges;
15773
+ $[10] = snackbarController;
15774
+ $[11] = t5;
15775
+ } else {
15776
+ t5 = $[11];
15777
+ }
15778
+ const handleDiscard = t5;
15779
+ let t6;
15780
+ if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
15781
+ t6 = /* @__PURE__ */ jsx(WarningIcon, { size: "smallest", className: "mr-1 text-yellow-600 dark:text-yellow-400" });
15782
+ $[12] = t6;
15783
+ } else {
15784
+ t6 = $[12];
15785
+ }
15786
+ let t7;
15787
+ if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
15788
+ t7 = /* @__PURE__ */ jsxs(Button, { size: "small", className: "font-semibold text-xs rounded-full px-4 py-1 bg-yellow-200 dark:bg-yellow-900 hover:bg-yellow-300 dark:hover:bg-yellow-800 text-yellow-800 dark:text-yellow-200", onClick: handleOpenMenu, children: [
15789
+ t6,
15790
+ "Unsaved Local changes",
15791
+ /* @__PURE__ */ jsx(KeyboardArrowDownIcon, { size: "smallest" })
15792
+ ] });
15793
+ $[13] = t7;
15794
+ } else {
15795
+ t7 = $[13];
15796
+ }
15797
+ let t8;
15798
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
15799
+ t8 = /* @__PURE__ */ jsx("div", { className: "max-w-xs px-4 py-4 text-sm text-gray-700 dark:text-gray-300", children: "This document was edited locally and has unsaved changes. These local changes will be lost if you don't apply them." });
15800
+ $[14] = t8;
15801
+ } else {
15802
+ t8 = $[14];
15803
+ }
15804
+ let t9;
15805
+ if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
15806
+ t9 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handlePreview, children: [
15807
+ /* @__PURE__ */ jsx(VisibilityIcon, { size: "small" }),
15808
+ "Preview Changes"
15809
+ ] });
15810
+ $[15] = t9;
15811
+ } else {
15812
+ t9 = $[15];
15813
+ }
15814
+ let t10;
15815
+ if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
15816
+ t10 = /* @__PURE__ */ jsx(CheckIcon, { size: "small" });
15817
+ $[16] = t10;
15818
+ } else {
15819
+ t10 = $[16];
15820
+ }
15821
+ let t11;
15822
+ if ($[17] !== handleApply) {
15823
+ t11 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handleApply, children: [
15824
+ t10,
15825
+ "Apply Changes"
15826
+ ] });
15827
+ $[17] = handleApply;
15828
+ $[18] = t11;
15829
+ } else {
15830
+ t11 = $[18];
15831
+ }
15832
+ let t12;
15833
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
15834
+ t12 = /* @__PURE__ */ jsx(CancelIcon, { size: "small" });
15835
+ $[19] = t12;
15836
+ } else {
15837
+ t12 = $[19];
15838
+ }
15839
+ let t13;
15840
+ if ($[20] !== handleDiscard) {
15841
+ t13 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handleDiscard, children: [
15842
+ t12,
15843
+ "Discard Local Changes"
15844
+ ] });
15845
+ $[20] = handleDiscard;
15846
+ $[21] = t13;
15847
+ } else {
15848
+ t13 = $[21];
15849
+ }
15850
+ let t14;
15851
+ if ($[22] !== open || $[23] !== t11 || $[24] !== t13) {
15852
+ t14 = /* @__PURE__ */ jsxs(Menu, { trigger: t7, open, onOpenChange: setOpen, children: [
15853
+ t8,
15854
+ t9,
15855
+ t11,
15856
+ t13
15857
+ ] });
15858
+ $[22] = open;
15859
+ $[23] = t11;
15860
+ $[24] = t13;
15861
+ $[25] = t14;
15862
+ } else {
15863
+ t14 = $[25];
15864
+ }
15865
+ let t15;
15866
+ if ($[26] === Symbol.for("react.memo_cache_sentinel")) {
15867
+ t15 = /* @__PURE__ */ jsx(DialogTitle, { variant: "h6", children: "Preview Local Changes" });
15868
+ $[26] = t15;
15869
+ } else {
15870
+ t15 = $[26];
15871
+ }
15872
+ let t16;
15873
+ if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
15874
+ t16 = /* @__PURE__ */ jsx(Typography, { variant: "body2", className: "mb-4", children: "These are the local changes that will be applied to the form." });
15875
+ $[27] = t16;
15876
+ } else {
15877
+ t16 = $[27];
15878
+ }
15879
+ let t17;
15880
+ if ($[28] === Symbol.for("react.memo_cache_sentinel")) {
15881
+ t17 = {
15882
+ maxHeight: 520,
15883
+ overflow: "auto"
15884
+ };
15885
+ $[28] = t17;
15886
+ } else {
15887
+ t17 = $[28];
15888
+ }
15889
+ const t18 = properties;
15890
+ let t19;
15891
+ if ($[29] !== localChangesData || $[30] !== t18) {
15892
+ t19 = /* @__PURE__ */ jsxs(DialogContent, { className: "my-4", children: [
15893
+ t16,
15894
+ /* @__PURE__ */ jsx("div", { className: `border rounded-lg ${defaultBorderMixin}`, style: t17, children: /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(PropertyCollectionView, { data: localChangesData, properties: t18 }) }) })
15895
+ ] });
15896
+ $[29] = localChangesData;
15897
+ $[30] = t18;
15898
+ $[31] = t19;
15899
+ } else {
15900
+ t19 = $[31];
15901
+ }
15902
+ let t20;
15903
+ if ($[32] === Symbol.for("react.memo_cache_sentinel")) {
15904
+ t20 = /* @__PURE__ */ jsx(Button, { onClick: () => setPreviewDialogOpen(false), children: "Close" });
15905
+ $[32] = t20;
15906
+ } else {
15907
+ t20 = $[32];
15908
+ }
15909
+ let t21;
15910
+ if ($[33] !== handleApply) {
15911
+ t21 = /* @__PURE__ */ jsxs(DialogActions, { children: [
15912
+ t20,
15913
+ /* @__PURE__ */ jsx(Button, { variant: "filled", onClick: () => {
15914
+ handleApply();
15915
+ setPreviewDialogOpen(false);
15916
+ }, children: "Apply changes" })
15917
+ ] });
15918
+ $[33] = handleApply;
15919
+ $[34] = t21;
15920
+ } else {
15921
+ t21 = $[34];
15922
+ }
15923
+ let t22;
15924
+ if ($[35] !== previewDialogOpen || $[36] !== t19 || $[37] !== t21) {
15925
+ t22 = /* @__PURE__ */ jsxs(Dialog, { open: previewDialogOpen, onOpenChange: setPreviewDialogOpen, maxWidth: "4xl", children: [
15926
+ t15,
15927
+ t19,
15928
+ t21
15929
+ ] });
15930
+ $[35] = previewDialogOpen;
15931
+ $[36] = t19;
15932
+ $[37] = t21;
15933
+ $[38] = t22;
15934
+ } else {
15935
+ t22 = $[38];
15936
+ }
15937
+ let t23;
15938
+ if ($[39] !== t14 || $[40] !== t22) {
15939
+ t23 = /* @__PURE__ */ jsxs(Fragment, { children: [
15940
+ t14,
15941
+ t22
15942
+ ] });
15943
+ $[39] = t14;
15944
+ $[40] = t22;
15945
+ $[41] = t23;
15946
+ } else {
15947
+ t23 = $[41];
15948
+ }
15949
+ return t23;
15950
+ }
15951
+ function extractTouchedValues(values, touched) {
15952
+ let acc = {};
15953
+ if (!touched || typeof touched !== "object") {
15954
+ return acc;
15955
+ }
15956
+ Object.entries(touched).forEach(([key, value]) => {
15957
+ if (value) {
15958
+ acc = setIn(acc, key, getIn(values, key));
15959
+ }
15960
+ });
15961
+ return acc;
15962
+ }
15963
+ function getChanges(source, comparison) {
15964
+ const changes = {};
15965
+ if (!source) {
15966
+ return {};
15967
+ }
15968
+ if (!comparison) {
15969
+ return source;
15970
+ }
15971
+ const allKeys = Array.from(/* @__PURE__ */ new Set([...Object.keys(source), ...Object.keys(comparison)]));
15972
+ for (const key of allKeys) {
15973
+ const sourceValue = source[key];
15974
+ const comparisonValue = comparison[key];
15975
+ if (equal(sourceValue, comparisonValue)) {
15976
+ continue;
15977
+ }
15978
+ const sourceHasKey = source && typeof source === "object" && Object.prototype.hasOwnProperty.call(source, key);
15979
+ const comparisonHasKey = comparison && typeof comparison === "object" && Object.prototype.hasOwnProperty.call(comparison, key);
15980
+ if (comparisonHasKey && !sourceHasKey) {
15981
+ changes[key] = void 0;
15982
+ } else if (Array.isArray(sourceValue)) {
15983
+ const comparisonArray = Array.isArray(comparisonValue) ? comparisonValue : [];
15984
+ if (sourceValue.length < comparisonArray.length) {
15985
+ changes[key] = sourceValue;
15986
+ continue;
15987
+ }
15988
+ const changedArray = sourceValue.map((item, index) => {
15989
+ const comparisonItem = comparisonArray[index];
15990
+ if (equal(item, comparisonItem)) {
15991
+ return null;
15992
+ }
15993
+ if (isObject(item) && item && isObject(comparisonItem) && comparisonItem) {
15994
+ const nestedChanges = getChanges(item, comparisonItem);
15995
+ return Object.keys(nestedChanges).length > 0 ? nestedChanges : item;
15996
+ }
15997
+ return item;
15998
+ });
15999
+ if (changedArray.some((item) => item !== null) || sourceValue.length > comparisonArray.length) {
16000
+ changes[key] = changedArray;
16001
+ }
16002
+ } else if (isObject(sourceValue) && sourceValue && isObject(comparisonValue) && comparisonValue) {
16003
+ const nestedChanges = getChanges(sourceValue, comparisonValue);
16004
+ if (Object.keys(nestedChanges).length > 0) {
16005
+ changes[key] = nestedChanges;
16006
+ }
16007
+ } else {
16008
+ changes[key] = sourceValue;
16009
+ }
16010
+ }
16011
+ return changes;
16012
+ }
15187
16013
  function EntityForm({
15188
16014
  path,
15189
16015
  fullIdPath,
@@ -15241,7 +16067,7 @@ function EntityForm({
15241
16067
  const customizationController = useCustomizationController();
15242
16068
  const context = useFireCMSContext();
15243
16069
  const analyticsController = useAnalyticsController();
15244
- const [underlyingChanges, setUnderlyingChanges] = useState({});
16070
+ const [underlyingChanges] = useState({});
15245
16071
  const [customIdLoading, setCustomIdLoading] = useState(false);
15246
16072
  const mustSetCustomId = (status === "new" || status === "copy") && Boolean(collection.customId) && collection.customId !== "optional";
15247
16073
  const initialEntityId = useMemo(() => {
@@ -15259,6 +16085,12 @@ function EntityForm({
15259
16085
  const [entityIdError, setEntityIdError] = useState(false);
15260
16086
  const [savingError, setSavingError] = useState();
15261
16087
  const autoSave = collection.formAutoSave && !collection.customId;
16088
+ const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [authController, collection, path, status, entity, customizationController.propertyConfigs]);
16089
+ const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
16090
+ const [localChangesCleared, setLocalChangesCleared] = useState(false);
16091
+ const localChangesBackup = getLocalChangesBackup(collection);
16092
+ const autoApplyLocalChanges = localChangesBackup === "auto_apply";
16093
+ const manualApplyLocalChanges = localChangesBackup === "manual_apply";
15262
16094
  const onSubmit = (values, formexController) => {
15263
16095
  if (mustSetCustomId && !entityId) {
15264
16096
  console.error("Missing custom Id");
@@ -15289,16 +16121,40 @@ function EntityForm({
15289
16121
  formexController.setSubmitting(false);
15290
16122
  });
15291
16123
  };
16124
+ const [initialValues_0, initialDirty_0] = useMemo(() => {
16125
+ const initialValuesWithLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
16126
+ const initialValues = initialDirtyValues ? mergeDeep(initialValuesWithLocalChanges, initialDirtyValues) : initialValuesWithLocalChanges;
16127
+ const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
16128
+ return [initialValues, initialDirty];
16129
+ }, [autoApplyLocalChanges, localChangesDataRaw, baseInitialValues, initialDirtyValues]);
16130
+ const localChangesData = useMemo(() => {
16131
+ if (!localChangesDataRaw) {
16132
+ return void 0;
16133
+ }
16134
+ return getChanges(localChangesDataRaw, initialValues_0);
16135
+ }, [localChangesDataRaw, initialValues_0]);
16136
+ const hasLocalChanges = !localChangesCleared && localChangesData && Object.keys(localChangesData).length > 0;
15292
16137
  const formex = formexProp ?? useCreateFormex({
15293
- initialValues: initialDirtyValues ?? getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs),
15294
- initialDirty: Boolean(initialDirtyValues),
16138
+ initialValues: initialValues_0,
16139
+ initialDirty: initialDirty_0,
16140
+ initialTouched: initialDirtyValues ? flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
16141
+ ...previousValue,
16142
+ [currentValue]: true
16143
+ }), {}) : {},
15295
16144
  onSubmit,
15296
16145
  onReset: () => {
15297
16146
  clearDirtyCache();
15298
- onValuesModified?.(false);
16147
+ onValuesModified?.(false, initialValues_0);
15299
16148
  },
15300
- validation: (values_0) => {
15301
- return validationSchema?.validate(values_0, {
16149
+ onValuesChangeDeferred: (values_0, controller) => {
16150
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
16151
+ if (controller.dirty) {
16152
+ const touchedValues = extractTouchedValues(values_0, controller.touched);
16153
+ saveEntityToCache(key, touchedValues);
16154
+ }
16155
+ },
16156
+ validation: (values_1) => {
16157
+ return validationSchema?.validate(values_1, {
15302
16158
  abortEarly: false
15303
16159
  }).then(() => {
15304
16160
  return {};
@@ -15347,14 +16203,16 @@ function EntityForm({
15347
16203
  }, [snackbarController]);
15348
16204
  function clearDirtyCache() {
15349
16205
  if (status === "new" || status === "copy") {
16206
+ removeEntityFromMemoryCache(path + "#new");
15350
16207
  removeEntityFromCache(path + "#new");
15351
16208
  } else {
16209
+ removeEntityFromMemoryCache(path + "/" + entityId);
15352
16210
  removeEntityFromCache(path + "/" + entityId);
15353
16211
  }
15354
16212
  }
15355
16213
  const onSaveSuccess = (updatedEntity) => {
15356
16214
  clearDirtyCache();
15357
- onValuesModified?.(false);
16215
+ onValuesModified?.(false, updatedEntity.values);
15358
16216
  if (!autoSave) snackbarController.open({
15359
16217
  type: "success",
15360
16218
  message: `${collection.singularName ?? collection.name}: Saved correctly`
@@ -15381,7 +16239,7 @@ function EntityForm({
15381
16239
  console.error(e_3);
15382
16240
  }, [entityId, path, snackbarController]);
15383
16241
  const saveEntity = ({
15384
- values: values_1,
16242
+ values: values_2,
15385
16243
  previousValues,
15386
16244
  entityId: entityId_0,
15387
16245
  collection: collection_0,
@@ -15390,7 +16248,7 @@ function EntityForm({
15390
16248
  return saveEntityWithCallbacks({
15391
16249
  path: path_0,
15392
16250
  entityId: entityId_0,
15393
- values: values_1,
16251
+ values: values_2,
15394
16252
  previousValues,
15395
16253
  collection: collection_0,
15396
16254
  status,
@@ -15406,34 +16264,34 @@ function EntityForm({
15406
16264
  collection: collection_1,
15407
16265
  path: path_1,
15408
16266
  entityId: entityId_1,
15409
- values: values_2,
16267
+ values: values_3,
15410
16268
  previousValues: previousValues_0,
15411
16269
  autoSave: autoSave_0
15412
16270
  }) => {
15413
16271
  if (!status) return;
15414
16272
  if (autoSave_0) {
15415
- setValuesToBeSaved(values_2);
16273
+ setValuesToBeSaved(values_3);
15416
16274
  } else {
15417
16275
  return saveEntity({
15418
16276
  collection: collection_1,
15419
16277
  path: path_1,
15420
16278
  entityId: entityId_1,
15421
- values: values_2,
16279
+ values: values_3,
15422
16280
  previousValues: previousValues_0
15423
16281
  });
15424
16282
  }
15425
16283
  };
15426
16284
  const lastSavedValues = useRef(entity?.values);
15427
- const save = (values_3) => {
15428
- lastSavedValues.current = values_3;
16285
+ const save = (values_4) => {
16286
+ lastSavedValues.current = values_4;
15429
16287
  return onSaveEntityRequest({
15430
16288
  collection: resolvedCollection,
15431
16289
  path,
15432
16290
  entityId,
15433
- values: values_3,
16291
+ values: values_4,
15434
16292
  previousValues: entity?.values,
15435
16293
  autoSave: autoSave ?? false
15436
- }).then((res) => {
16294
+ }).then(() => {
15437
16295
  const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
15438
16296
  analyticsController.onAnalyticsEvent?.(eventName, {
15439
16297
  path
@@ -15467,7 +16325,8 @@ function EntityForm({
15467
16325
  type: "error",
15468
16326
  message: "Error updating id, check the console"
15469
16327
  });
15470
- }, []);
16328
+ console.error(error);
16329
+ }, [snackbarController]);
15471
16330
  const pluginActions = [];
15472
16331
  const plugins = customizationController.plugins;
15473
16332
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
@@ -15513,23 +16372,15 @@ function EntityForm({
15513
16372
  }, [doOnIdUpdate]);
15514
16373
  useEffect(() => {
15515
16374
  if (!autoSave) {
15516
- onValuesModified?.(modified);
16375
+ onValuesModified?.(modified, formex.values);
15517
16376
  }
15518
16377
  }, [formex.dirty]);
15519
- const deferredValues = useDeferredValue(formex.values);
15520
16378
  const modified = formex.dirty;
15521
16379
  const uniqueFieldValidator = useCallback(({
15522
16380
  name,
15523
- value,
15524
- property
16381
+ value
15525
16382
  }) => dataSource.checkUniqueField(path, name, value, entityId, collection), [dataSource, path, entityId]);
15526
16383
  const validationSchema = useMemo(() => entityId ? getYupEntitySchema(entityId, resolvedCollection.properties, uniqueFieldValidator) : void 0, [entityId, resolvedCollection.properties, uniqueFieldValidator]);
15527
- useEffect(() => {
15528
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15529
- if (modified) {
15530
- saveEntityToCache(key, deferredValues);
15531
- }
15532
- }, [deferredValues, modified, path, entityId, status]);
15533
16384
  useOnAutoSave(autoSave, formex, lastSavedValues, save);
15534
16385
  useEffect(() => {
15535
16386
  if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
@@ -15548,18 +16399,18 @@ function EntityForm({
15548
16399
  return /* @__PURE__ */ jsx(Builder, { collection, entity, modifiedValues: formex.values, formContext });
15549
16400
  }
15550
16401
  return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15551
- const property_0 = resolvedCollection.properties[key_1];
15552
- if (property_0) {
16402
+ const property = resolvedCollection.properties[key_1];
16403
+ if (property) {
15553
16404
  const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_1) && formex.touched[key_1];
15554
- const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property_0) || Boolean(property_0.disabled);
15555
- const hidden = isHidden(property_0);
16405
+ const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
16406
+ const hidden = isHidden(property);
15556
16407
  if (hidden) return null;
15557
- const widthPercentage = property_0.widthPercentage ?? 100;
16408
+ const widthPercentage = property.widthPercentage ?? 100;
15558
16409
  const cmsFormFieldProps = {
15559
16410
  propertyKey: key_1,
15560
16411
  disabled: disabled_0,
15561
- property: property_0,
15562
- includeDescription: property_0.description || property_0.longDescription,
16412
+ property,
16413
+ includeDescription: property.description || property.longDescription,
15563
16414
  underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
15564
16415
  context: formContext,
15565
16416
  partOfArray: false,
@@ -15615,10 +16466,13 @@ function EntityForm({
15615
16466
  }
15616
16467
  const dialogActions = /* @__PURE__ */ jsx(EntityFormActionsComponent, { collection: resolvedCollection, path, fullPath: path, fullIdPath, entity, layout: forceActionsAtTheBottom ? "bottom" : "side", savingError, formex, disabled: actionsDisabled, status, pluginActions: pluginActions ?? [], openEntityMode, showDefaultActions, navigateBack, formContext });
15617
16468
  return /* @__PURE__ */ jsx(Formex, { value: formex, children: /* @__PURE__ */ jsxs("form", { onSubmit: formex.handleSubmit, onReset: () => formex.resetForm({
15618
- values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs)
16469
+ values: baseInitialValues
15619
16470
  }), noValidate: true, className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15620
16471
  /* @__PURE__ */ jsx("div", { id: `form_${path}`, className: cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit"), children: /* @__PURE__ */ jsxs("div", { className: cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10"), children: [
15621
- formex.dirty ? /* @__PURE__ */ jsx(Tooltip, { title: "Local unsaved changes", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsx(Chip, { size: "small", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsx(EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsx(Tooltip, { title: "In sync with the database", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsx(Chip, { size: "small", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" }) }) }),
16472
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-4 self-end sticky top-4 z-10", children: [
16473
+ manualApplyLocalChanges && hasLocalChanges && /* @__PURE__ */ jsx(LocalChangesMenu, { cacheKey: status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId, properties: resolvedCollection.properties, localChangesData, formex, onClearLocalChanges: () => setLocalChangesCleared(true) }),
16474
+ formex.dirty ? /* @__PURE__ */ jsx(Tooltip, { title: "This form has been modified", children: /* @__PURE__ */ jsx(Chip, { size: "small", className: "py-1", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsx(EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsx(Tooltip, { title: "The current form is in sync with the database", children: /* @__PURE__ */ jsx(Chip, { size: "small", className: "py-1", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" }) }) })
16475
+ ] }),
15622
16476
  formView
15623
16477
  ] }) }),
15624
16478
  dialogActions
@@ -17782,7 +18636,7 @@ const PropertyFieldBinding = React__default.memo(PropertyFieldBindingInternal, (
17782
18636
  return false;
17783
18637
  });
17784
18638
  function PropertyFieldBindingInternal(t0) {
17785
- const $ = c(18);
18639
+ const $ = c(19);
17786
18640
  const {
17787
18641
  propertyKey,
17788
18642
  property,
@@ -17791,6 +18645,7 @@ function PropertyFieldBindingInternal(t0) {
17791
18645
  underlyingValueHasChanged,
17792
18646
  disabled: disabledProp,
17793
18647
  partOfArray,
18648
+ partOfBlock,
17794
18649
  minimalistView,
17795
18650
  autoFocus,
17796
18651
  index,
@@ -17799,15 +18654,8 @@ function PropertyFieldBindingInternal(t0) {
17799
18654
  } = t0;
17800
18655
  const authController = useAuthController();
17801
18656
  const customizationController = useCustomizationController();
17802
- if (propertyKey === "created_by") {
17803
- console.log("Rendering field for created_by", {
17804
- propertyKey,
17805
- property,
17806
- context
17807
- });
17808
- }
17809
18657
  let t1;
17810
- if ($[0] !== authController || $[1] !== autoFocus || $[2] !== context || $[3] !== customizationController.propertyConfigs || $[4] !== disabledProp || $[5] !== includeDescription || $[6] !== index || $[7] !== minimalistView || $[8] !== onPropertyChange || $[9] !== partOfArray || $[10] !== property || $[11] !== propertyKey || $[12] !== size || $[13] !== underlyingValueHasChanged) {
18658
+ if ($[0] !== authController || $[1] !== autoFocus || $[2] !== context || $[3] !== customizationController.propertyConfigs || $[4] !== disabledProp || $[5] !== includeDescription || $[6] !== index || $[7] !== minimalistView || $[8] !== onPropertyChange || $[9] !== partOfArray || $[10] !== partOfBlock || $[11] !== property || $[12] !== propertyKey || $[13] !== size || $[14] !== underlyingValueHasChanged) {
17811
18659
  t1 = (fieldProps) => {
17812
18660
  let Component;
17813
18661
  const resolvedProperty = resolveProperty({
@@ -17870,6 +18718,7 @@ function PropertyFieldBindingInternal(t0) {
17870
18718
  context,
17871
18719
  disabled,
17872
18720
  partOfArray,
18721
+ partOfBlock,
17873
18722
  minimalistView,
17874
18723
  autoFocus,
17875
18724
  size,
@@ -17887,22 +18736,23 @@ function PropertyFieldBindingInternal(t0) {
17887
18736
  $[7] = minimalistView;
17888
18737
  $[8] = onPropertyChange;
17889
18738
  $[9] = partOfArray;
17890
- $[10] = property;
17891
- $[11] = propertyKey;
17892
- $[12] = size;
17893
- $[13] = underlyingValueHasChanged;
17894
- $[14] = t1;
18739
+ $[10] = partOfBlock;
18740
+ $[11] = property;
18741
+ $[12] = propertyKey;
18742
+ $[13] = size;
18743
+ $[14] = underlyingValueHasChanged;
18744
+ $[15] = t1;
17895
18745
  } else {
17896
- t1 = $[14];
18746
+ t1 = $[15];
17897
18747
  }
17898
18748
  let t2;
17899
- if ($[15] !== propertyKey || $[16] !== t1) {
18749
+ if ($[16] !== propertyKey || $[17] !== t1) {
17900
18750
  t2 = /* @__PURE__ */ jsx(Field, { name: propertyKey, children: t1 }, propertyKey);
17901
- $[15] = propertyKey;
17902
- $[16] = t1;
17903
- $[17] = t2;
18751
+ $[16] = propertyKey;
18752
+ $[17] = t1;
18753
+ $[18] = t2;
17904
18754
  } else {
17905
- t2 = $[17];
18755
+ t2 = $[18];
17906
18756
  }
17907
18757
  return t2;
17908
18758
  }
@@ -17914,6 +18764,7 @@ function FieldInternal({
17914
18764
  includeDescription,
17915
18765
  underlyingValueHasChanged,
17916
18766
  partOfArray,
18767
+ partOfBlock,
17917
18768
  minimalistView,
17918
18769
  autoFocus,
17919
18770
  context,
@@ -17963,6 +18814,7 @@ function FieldInternal({
17963
18814
  disabled: disabled ?? false,
17964
18815
  underlyingValueHasChanged: underlyingValueHasChanged ?? false,
17965
18816
  partOfArray: partOfArray ?? false,
18817
+ partOfBlock: partOfBlock ?? false,
17966
18818
  minimalistView: minimalistView ?? false,
17967
18819
  autoFocus: autoFocus ?? false,
17968
18820
  customProps: customFieldProps,
@@ -19274,6 +20126,7 @@ function BlockEntry(t0) {
19274
20126
  context,
19275
20127
  autoFocus,
19276
20128
  partOfArray: false,
20129
+ partOfBlock: true,
19277
20130
  minimalistView: true,
19278
20131
  onPropertyChange: storeProps
19279
20132
  } : void 0;
@@ -20285,6 +21138,11 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
20285
21138
  console.error("Save failure");
20286
21139
  console.error(e_0);
20287
21140
  setError(e_0);
21141
+ },
21142
+ onPreSaveHookError: (e_1) => {
21143
+ console.error("Pre-save hook error");
21144
+ console.error(e_1);
21145
+ setError(e_1);
20288
21146
  }
20289
21147
  });
20290
21148
  };
@@ -20382,7 +21240,7 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
20382
21240
  width: width_0,
20383
21241
  frozen
20384
21242
  }) => {
20385
- const isSelected = Boolean(usedSelectionController.selectedEntities.find((e_1) => e_1.id == entity_6.id && e_1.path == entity_6.path));
21243
+ const isSelected = Boolean(usedSelectionController.selectedEntities.find((e_2) => e_2.id == entity_6.id && e_2.path == entity_6.path));
20386
21244
  const customEntityActions_0 = (collection.entityActions ?? []).map((action) => resolveEntityAction(action, customizationController.entityActions)).filter(Boolean);
20387
21245
  const actions_0 = getActionsForEntity({
20388
21246
  entity: entity_6,
@@ -20391,9 +21249,9 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
20391
21249
  return /* @__PURE__ */ jsx(EntityCollectionRowActions, { entity: entity_6, width: width_0, frozen, isSelected, selectionEnabled, size: size_0, highlightEntity: setHighlightedEntity, unhighlightEntity: unselectNavigatedEntity, collection, fullPath, fullIdPath, actions: actions_0, hideId: collection?.hideIdFromCollection, onCollectionChange: updateLastDeleteTimestamp, selectionController: usedSelectionController, openEntityMode });
20392
21250
  }, [updateLastDeleteTimestamp, usedSelectionController]);
20393
21251
  const title = /* @__PURE__ */ jsx(Popover, { open: popOverOpen, onOpenChange: setPopOverOpen, enabled: Boolean(collection.description), trigger: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-start", children: [
20394
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", className: `leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`, onClick: collection.description ? (e_2) => {
21252
+ /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", className: `leading-none truncate max-w-[160px] lg:max-w-[240px] ${collection.description ? "cursor-pointer" : "cursor-auto"}`, onClick: collection.description ? (e_3) => {
20395
21253
  setPopOverOpen(true);
20396
- e_2.stopPropagation();
21254
+ e_3.stopPropagation();
20397
21255
  } : void 0, children: `${collection.name}` }),
20398
21256
  /* @__PURE__ */ jsx(EntitiesCount, { fullPath, collection, filter: tableController.filterValues, sortBy: tableController.sortBy, onCountChange: setDocsCount })
20399
21257
  ] }), children: collection.description && /* @__PURE__ */ jsx("div", { className: "m-4 text-surface-900 dark:text-white", children: /* @__PURE__ */ jsx(Markdown, { source: collection.description }) }) });
@@ -20428,7 +21286,7 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
20428
21286
  /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: "So empty..." }),
20429
21287
  /* @__PURE__ */ jsxs(Button, { color: "primary", variant: "outlined", onClick: onNewClick, className: "mt-4", children: [
20430
21288
  /* @__PURE__ */ jsx(AddIcon, {}),
20431
- "Create your first entity"
21289
+ "Create your first entry"
20432
21290
  ] })
20433
21291
  ] }) : /* @__PURE__ */ jsx(Typography, { variant: "label", children: "No results with the applied filter/sort" }), hoverRow, inlineEditing: checkInlineEditing(), AdditionalHeaderWidget: buildAdditionalHeaderWidget, AddColumnComponent: addColumnComponentInternal, getIdColumnWidth, additionalIDHeaderWidget: /* @__PURE__ */ jsx(EntityIdHeaderWidget, { path: fullPath, fullIdPath: fullIdPath ?? fullPath, collection }), openEntityMode }, `collection_table_${fullPath}`),
20434
21292
  popupCell && /* @__PURE__ */ jsx(PopupFormField, { open: Boolean(popupCell), onClose: onPopupClose, cellRect: popupCell?.cellRect, propertyKey: popupCell?.propertyKey, collection, entityId: popupCell.entityId, tableKey: tableKey.current, customFieldValidator: uniqueFieldValidator, path: resolvedFullPath, onCellValueChange: onValueChange, container: containerRef.current }, `popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`),
@@ -21807,22 +22665,9 @@ function useBuildNavigationController(props) {
21807
22665
  const buildUrlCollectionPath = useCallback((path_0) => `${removeInitialAndTrailingSlashes(baseCollectionPath)}/${encodePath(path_0)}`, [baseCollectionPath]);
21808
22666
  const allPluginGroups = plugins?.flatMap((plugin) => plugin.homePage?.navigationEntries ? plugin.homePage.navigationEntries.map((e) => e.name) : []) ?? [];
21809
22667
  const pluginGroups = [...new Set(allPluginGroups)];
21810
- const onNavigationEntriesOrderUpdate = useCallback((entries) => {
21811
- if (!plugins) {
21812
- return;
21813
- }
21814
- const filteredEntries = entries.filter((entry) => entry.entries.length > 0);
21815
- if (plugins.some((plugin_1) => plugin_1.homePage?.onNavigationEntriesUpdate)) {
21816
- plugins.forEach((plugin_0) => {
21817
- if (plugin_0.homePage?.onNavigationEntriesUpdate) {
21818
- plugin_0.homePage.onNavigationEntriesUpdate(filteredEntries);
21819
- }
21820
- });
21821
- }
21822
- }, [plugins]);
21823
- const computeTopNavigation = useCallback((collections, views, adminViews, viewsOrder_0) => {
22668
+ const computeTopNavigation = useCallback((collections, views, adminViews, viewsOrder_0, navigationGroupMappingsOverride, onNavigationEntriesUpdateCallback) => {
21824
22669
  const finalNavigationGroupMappings = computeNavigationGroups({
21825
- navigationGroupMappings,
22670
+ navigationGroupMappings: navigationGroupMappingsOverride ?? navigationGroupMappings,
21826
22671
  collections,
21827
22672
  views,
21828
22673
  plugins
@@ -21854,7 +22699,7 @@ function useBuildNavigationController(props) {
21854
22699
  return acc;
21855
22700
  }, []), ...(views ?? []).reduce((acc_0, view) => {
21856
22701
  if (view.hideFromNavigation) return acc_0;
21857
- const pathKey_0 = Array.isArray(view.path) ? view.path[0] : view.path;
22702
+ const pathKey_0 = view.path;
21858
22703
  let groupName_0 = getGroup(view);
21859
22704
  if (finalNavigationGroupMappings) {
21860
22705
  for (const pluginGroupDef_0 of finalNavigationGroupMappings) {
@@ -21877,7 +22722,7 @@ function useBuildNavigationController(props) {
21877
22722
  return acc_0;
21878
22723
  }, []), ...(adminViews ?? []).reduce((acc_1, view_0) => {
21879
22724
  if (view_0.hideFromNavigation) return acc_1;
21880
- const pathKey_1 = Array.isArray(view_0.path) ? view_0.path[0] : view_0.path;
22725
+ const pathKey_1 = view_0.path;
21881
22726
  const groupName_1 = NAVIGATION_ADMIN_GROUP_NAME;
21882
22727
  acc_1.push({
21883
22728
  id: `admin:${pathKey_1}`,
@@ -21911,21 +22756,43 @@ function useBuildNavigationController(props) {
21911
22756
  });
21912
22757
  }
21913
22758
  const collectedGroupsFromEntries = navigationEntries.map((e_0) => e_0.group).filter(Boolean);
21914
- const allDefinedGroups = [...pluginGroups ?? [], ...collectedGroupsFromEntries];
21915
- const uniqueGroups = [...new Set(allDefinedGroups)].sort((a_1, b_1) => groupOrderValue(a_1) - groupOrderValue(b_1));
22759
+ const groupsFromMappings = finalNavigationGroupMappings.map((g_0) => g_0.name);
22760
+ const additionalGroups = collectedGroupsFromEntries.filter((g_1) => !groupsFromMappings.includes(g_1));
22761
+ const allDefinedGroups = [...pluginGroups ?? [], ...groupsFromMappings, ...additionalGroups];
22762
+ const uniqueGroupsArray = [...new Set(allDefinedGroups)];
22763
+ const adminGroups = uniqueGroupsArray.filter((g_2) => g_2 === NAVIGATION_ADMIN_GROUP_NAME);
22764
+ const nonAdminGroups = uniqueGroupsArray.filter((g_3) => g_3 !== NAVIGATION_ADMIN_GROUP_NAME);
22765
+ const uniqueGroups = [...nonAdminGroups, ...adminGroups];
21916
22766
  return {
21917
- allowDragAndDrop: plugins?.some((plugin_2) => plugin_2.homePage?.allowDragAndDrop) ?? false,
22767
+ allowDragAndDrop: plugins?.some((plugin_0) => plugin_0.homePage?.allowDragAndDrop) ?? false,
21918
22768
  navigationEntries,
21919
22769
  groups: uniqueGroups,
21920
- onNavigationEntriesUpdate: onNavigationEntriesOrderUpdate
22770
+ onNavigationEntriesUpdate: onNavigationEntriesUpdateCallback
21921
22771
  };
21922
- }, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups, onNavigationEntriesOrderUpdate]);
22772
+ }, [navigationGroupMappings, buildCMSUrlPath, buildUrlCollectionPath, pluginGroups]);
22773
+ const onNavigationEntriesOrderUpdate = useCallback((entries) => {
22774
+ if (!plugins) {
22775
+ return;
22776
+ }
22777
+ const filteredEntries = entries.filter((entry) => entry.entries.length > 0);
22778
+ if (collectionsRef.current && viewsRef.current) {
22779
+ const updatedNav = computeTopNavigation(collectionsRef.current, viewsRef.current, adminViewsRef.current ?? [], viewsOrder, filteredEntries, onNavigationEntriesOrderUpdate);
22780
+ setTopLevelNavigation(updatedNav);
22781
+ }
22782
+ if (plugins.some((plugin_2) => plugin_2.homePage?.onNavigationEntriesUpdate)) {
22783
+ plugins.forEach((plugin_1) => {
22784
+ if (plugin_1.homePage?.onNavigationEntriesUpdate) {
22785
+ plugin_1.homePage.onNavigationEntriesUpdate(filteredEntries);
22786
+ }
22787
+ });
22788
+ }
22789
+ }, [plugins, computeTopNavigation, viewsOrder]);
21923
22790
  const refreshNavigation = useCallback(async () => {
21924
22791
  if (disabled || authController.initialLoading) return;
21925
22792
  console.debug("Refreshing navigation");
21926
22793
  try {
21927
22794
  const [resolvedCollections = [], resolvedViews, resolvedAdminViews = []] = await Promise.all([resolveCollections(collectionsProp, collectionPermissions, authController, dataSourceDelegate, plugins), resolveCMSViews(viewsProp, authController, dataSourceDelegate), resolveCMSViews(adminViewsProp, authController, dataSourceDelegate)]);
21928
- const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder);
22795
+ const computedTopLevelNav = computeTopNavigation(resolvedCollections, resolvedViews, resolvedAdminViews, viewsOrder, void 0, onNavigationEntriesOrderUpdate);
21929
22796
  let shouldUpdateTopLevelNav = false;
21930
22797
  if (!areCollectionListsEqual(collectionsRef.current ?? [], resolvedCollections)) {
21931
22798
  collectionsRef.current = resolvedCollections;
@@ -22021,7 +22888,8 @@ function useBuildNavigationController(props) {
22021
22888
  }, []);
22022
22889
  const isUrlCollectionPath = useCallback((path_1) => removeInitialAndTrailingSlashes(path_1 + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"), [fullCollectionPath]);
22023
22890
  const urlPathToDataPath = useCallback((path_2) => {
22024
- if (path_2.startsWith(fullCollectionPath)) return path_2.replace(fullCollectionPath, "");
22891
+ const decodedPath = decodeURIComponent(path_2);
22892
+ if (decodedPath.startsWith(fullCollectionPath)) return decodedPath.replace(fullCollectionPath, "");
22025
22893
  throw Error("Expected path starting with " + fullCollectionPath);
22026
22894
  }, [fullCollectionPath]);
22027
22895
  const resolveIdsFrom = useCallback((path_3) => {
@@ -22209,6 +23077,42 @@ function computeNavigationGroups({
22209
23077
  }
22210
23078
  return acc;
22211
23079
  }, [...result ?? []]) : result;
23080
+ const assignedEntries = /* @__PURE__ */ new Set();
23081
+ if (result) {
23082
+ result.forEach((group) => {
23083
+ group.entries.forEach((entry) => assignedEntries.add(entry));
23084
+ });
23085
+ }
23086
+ const unassignedGroupMap = {};
23087
+ (collections ?? []).forEach((collection) => {
23088
+ const entry = collection.id ?? collection.path;
23089
+ if (!assignedEntries.has(entry)) {
23090
+ const groupName = getGroup(collection);
23091
+ if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
23092
+ unassignedGroupMap[groupName].push(entry);
23093
+ }
23094
+ });
23095
+ (views ?? []).forEach((view) => {
23096
+ const entry = view.path;
23097
+ if (!assignedEntries.has(entry)) {
23098
+ const groupName = getGroup(view);
23099
+ if (!unassignedGroupMap[groupName]) unassignedGroupMap[groupName] = [];
23100
+ unassignedGroupMap[groupName].push(entry);
23101
+ }
23102
+ });
23103
+ Object.entries(unassignedGroupMap).forEach(([groupName, entries]) => {
23104
+ if (result) {
23105
+ const existingGroup = result.find((g) => g.name === groupName);
23106
+ if (existingGroup) {
23107
+ existingGroup.entries.push(...entries);
23108
+ } else {
23109
+ result.push({
23110
+ name: groupName,
23111
+ entries
23112
+ });
23113
+ }
23114
+ }
23115
+ });
22212
23116
  if (!result) {
22213
23117
  result = [];
22214
23118
  const groupMap = {};
@@ -22220,7 +23124,7 @@ function computeNavigationGroups({
22220
23124
  });
22221
23125
  (views ?? []).forEach((view) => {
22222
23126
  const name = getGroup(view);
22223
- const entry = Array.isArray(view.path) ? view.path[0] : view.path;
23127
+ const entry = view.path;
22224
23128
  if (!groupMap[name]) groupMap[name] = [];
22225
23129
  groupMap[name].push(entry);
22226
23130
  });
@@ -22827,14 +23731,14 @@ function EntityEditViewFormActions({
22827
23731
  collection,
22828
23732
  context,
22829
23733
  sideEntityController,
22830
- isSubmitting: formex.isSubmitting,
22831
23734
  disabled,
22832
23735
  status,
22833
23736
  sideDialogContext,
22834
23737
  pluginActions,
22835
23738
  openEntityMode,
22836
23739
  navigateBack,
22837
- formContext
23740
+ formContext,
23741
+ formex
22838
23742
  }) : buildSideActions({
22839
23743
  savingError,
22840
23744
  entity,
@@ -22842,14 +23746,14 @@ function EntityEditViewFormActions({
22842
23746
  collection,
22843
23747
  context,
22844
23748
  sideEntityController,
22845
- isSubmitting: formex.isSubmitting,
22846
23749
  sideDialogContext,
22847
23750
  disabled,
22848
23751
  status,
22849
23752
  pluginActions,
22850
23753
  openEntityMode,
22851
23754
  navigateBack,
22852
- formContext
23755
+ formContext,
23756
+ formex
22853
23757
  });
22854
23758
  }
22855
23759
  function buildBottomActions({
@@ -22859,15 +23763,16 @@ function buildBottomActions({
22859
23763
  collection,
22860
23764
  context,
22861
23765
  sideEntityController,
22862
- isSubmitting,
22863
23766
  disabled,
22864
23767
  status,
22865
23768
  sideDialogContext,
22866
23769
  pluginActions,
22867
23770
  openEntityMode,
22868
23771
  navigateBack,
22869
- formContext
23772
+ formContext,
23773
+ formex
22870
23774
  }) {
23775
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22871
23776
  const canClose = openEntityMode === "side_panel";
22872
23777
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
22873
23778
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
@@ -22887,15 +23792,16 @@ function buildBottomActions({
22887
23792
  return /* @__PURE__ */ jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22888
23793
  }) }),
22889
23794
  pluginActions,
22890
- /* @__PURE__ */ jsx(Button, { variant: "text", color: "primary", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22891
- /* @__PURE__ */ jsxs(Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, onClick: () => {
23795
+ hasErrors ? /* @__PURE__ */ jsx(ErrorTooltip, { title: "This form has errors", children: /* @__PURE__ */ jsx(ErrorIcon, { className: "ml-4", color: "error", size: "smallest" }) }) : null,
23796
+ /* @__PURE__ */ jsx(Button, { variant: "text", color: "primary", disabled: disabled || formex.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
23797
+ /* @__PURE__ */ jsxs(Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || formex.isSubmitting, onClick: () => {
22892
23798
  sideDialogContext.setPendingClose(false);
22893
23799
  }, children: [
22894
23800
  status === "existing" && "Save",
22895
23801
  status === "copy" && "Create copy",
22896
23802
  status === "new" && "Create"
22897
23803
  ] }),
22898
- canClose && /* @__PURE__ */ jsxs(LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: isSubmitting, disabled, onClick: () => {
23804
+ canClose && /* @__PURE__ */ jsxs(LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: formex.isSubmitting, disabled, onClick: () => {
22899
23805
  sideDialogContext.setPendingClose?.(true);
22900
23806
  }, children: [
22901
23807
  status === "existing" && "Save and close",
@@ -22911,24 +23817,25 @@ function buildSideActions({
22911
23817
  collection,
22912
23818
  context,
22913
23819
  sideEntityController,
22914
- isSubmitting,
22915
23820
  disabled,
22916
23821
  status,
22917
23822
  sideDialogContext,
22918
23823
  pluginActions,
22919
23824
  openEntityMode,
22920
23825
  navigateBack,
22921
- formContext
23826
+ formContext,
23827
+ formex
22922
23828
  }) {
23829
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22923
23830
  return /* @__PURE__ */ jsxs("div", { className: cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", defaultBorderMixin), children: [
22924
- /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, onClick: () => {
23831
+ /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", startIcon: hasErrors ? /* @__PURE__ */ jsx(ErrorIcon, {}) : void 0, disabled: disabled || formex.isSubmitting, onClick: () => {
22925
23832
  sideDialogContext.setPendingClose?.(false);
22926
23833
  }, children: [
22927
23834
  status === "existing" && "Save",
22928
23835
  status === "copy" && "Create copy",
22929
23836
  status === "new" && "Create"
22930
23837
  ] }),
22931
- /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
23838
+ /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || formex.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22932
23839
  pluginActions,
22933
23840
  formActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-wrap mt-2", children: formActions.map((action) => {
22934
23841
  const props = {
@@ -23083,6 +23990,7 @@ function EntityJsonPreview(t0) {
23083
23990
  function createFormexStub(values) {
23084
23991
  const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
23085
23992
  return {
23993
+ debugId: "",
23086
23994
  values,
23087
23995
  initialValues: values,
23088
23996
  touched: {},
@@ -23097,6 +24005,9 @@ function createFormexStub(values) {
23097
24005
  setValues: () => {
23098
24006
  throw new Error(errorMessage);
23099
24007
  },
24008
+ setTouched(touched) {
24009
+ throw new Error(errorMessage);
24010
+ },
23100
24011
  setFieldValue: () => {
23101
24012
  throw new Error(errorMessage);
23102
24013
  },
@@ -23156,7 +24067,7 @@ function EntityEditView({
23156
24067
  databaseId: props.databaseId,
23157
24068
  useCache: false
23158
24069
  });
23159
- const cachedValues = entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new");
24070
+ const initialDirtyValues = entityId ? getEntityFromMemoryCache(props.path + "/" + entityId) : getEntityFromMemoryCache(props.path + "#new");
23160
24071
  const authController = useAuthController();
23161
24072
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23162
24073
  const [status, setStatus] = useState(initialStatus);
@@ -23167,13 +24078,13 @@ function EntityEditView({
23167
24078
  return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : void 0;
23168
24079
  }
23169
24080
  }, [authController, entity, status]);
23170
- if (dataLoading && !cachedValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
24081
+ if (dataLoading && !initialDirtyValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23171
24082
  return /* @__PURE__ */ jsx(CircularProgressCenter, {});
23172
24083
  }
23173
- if (entityId && !entity && !cachedValues) {
24084
+ if (entityId && !entity && !initialDirtyValues) {
23174
24085
  console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
23175
24086
  }
23176
- return /* @__PURE__ */ jsx(EntityEditViewInner, { ...props, entityId, entity, cachedDirtyValues: cachedValues, dataLoading, status, setStatus, canEdit });
24087
+ return /* @__PURE__ */ jsx(EntityEditViewInner, { ...props, entityId, entity, initialDirtyValues, dataLoading, status, setStatus, canEdit });
23177
24088
  }
23178
24089
  function EntityEditViewInner({
23179
24090
  path,
@@ -23186,7 +24097,7 @@ function EntityEditViewInner({
23186
24097
  onSaved,
23187
24098
  onTabChange,
23188
24099
  entity,
23189
- cachedDirtyValues,
24100
+ initialDirtyValues,
23190
24101
  dataLoading,
23191
24102
  layout = "side_panel",
23192
24103
  barActions,
@@ -23314,7 +24225,8 @@ function EntityEditViewInner({
23314
24225
  /* @__PURE__ */ jsx(EntityView, { className: "px-8 h-full overflow-auto", entity, path, collection }),
23315
24226
  /* @__PURE__ */ jsx("div", { className: "h-16" })
23316
24227
  ] }) }) : null;
23317
- const entityView = /* @__PURE__ */ jsx(EntityForm, { fullIdPath, collection, path, entityId: entityId ?? usedEntity?.id, onValuesModified, entity, initialDirtyValues: cachedDirtyValues, openEntityMode: layout, forceActionsAtTheBottom: actionsAtTheBottom, initialStatus: status, className: cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className), EntityFormActionsComponent: EntityEditViewFormActions, disabled: !canEdit, ...formProps, onEntityChange: (entity_0) => {
24228
+ const entityView = /* @__PURE__ */ jsx(EntityForm, { fullIdPath, collection, path, entityId: entityId ?? usedEntity?.id, onValuesModified, entity, initialDirtyValues, openEntityMode: layout, forceActionsAtTheBottom: actionsAtTheBottom, initialStatus: status, className: cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className), EntityFormActionsComponent: EntityEditViewFormActions, disabled: !canEdit, ...formProps, onEntityChange: (entity_0) => {
24229
+ console.log("333 EntityEditView onEntityChange:", entity_0);
23318
24230
  setUsedEntity(entity_0);
23319
24231
  formProps?.onEntityChange?.(entity_0);
23320
24232
  }, onStatusChange: (status_0) => {
@@ -23337,7 +24249,12 @@ function EntityEditViewInner({
23337
24249
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
23338
24250
  let result = /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-900", children: [
23339
24251
  shouldShowTopBar && /* @__PURE__ */ jsxs("div", { className: cls("h-14 items-center flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin), children: [
23340
- barActions,
24252
+ barActions?.({
24253
+ path: fullIdPath ?? path,
24254
+ entityId,
24255
+ values: formContext?.values ?? usedEntity?.values ?? {},
24256
+ status
24257
+ }),
23341
24258
  /* @__PURE__ */ jsx("div", { className: "flex-grow" }),
23342
24259
  pluginActionsTop,
23343
24260
  globalLoading && /* @__PURE__ */ jsx("div", { className: "self-center", children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) }),
@@ -23440,9 +24357,14 @@ function EntitySidePanel(props) {
23440
24357
  if (!props || !collection) {
23441
24358
  return /* @__PURE__ */ jsx("div", { className: "w-full" });
23442
24359
  }
23443
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(EntityEditView, { ...props, fullIdPath, layout: "side_panel", collection, parentCollectionIds, onValuesModified, onSaved: onUpdate, barActions: /* @__PURE__ */ jsxs(Fragment, { children: [
24360
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(EntityEditView, { ...props, fullIdPath, layout: "side_panel", collection, parentCollectionIds, onValuesModified, onSaved: onUpdate, barActions: ({
24361
+ status,
24362
+ values
24363
+ }) => /* @__PURE__ */ jsxs(Fragment, { children: [
23444
24364
  /* @__PURE__ */ jsx(IconButton, { className: "self-center", onClick: onClose, children: /* @__PURE__ */ jsx(CloseIcon, { size: "small" }) }),
23445
24365
  allowFullScreen && /* @__PURE__ */ jsx(IconButton, { className: "self-center", onClick: () => {
24366
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
24367
+ saveEntityToMemoryCache(key, values);
23446
24368
  if (entityId) navigate(location.pathname);
23447
24369
  else navigate(location.pathname + "#new");
23448
24370
  }, children: /* @__PURE__ */ jsx(OpenInFullIcon, { size: "small" }) })
@@ -26223,6 +27145,7 @@ export {
26223
27145
  getIdIcon,
26224
27146
  getLabelOrConfigFrom,
26225
27147
  getLastSegment,
27148
+ getLocalChangesBackup,
26226
27149
  getPropertiesWithPropertiesOrder,
26227
27150
  getPropertyInPath,
26228
27151
  getRandomId,
@@ -26237,13 +27160,13 @@ export {
26237
27160
  isEnumValueDisabled,
26238
27161
  isHidden,
26239
27162
  isObject,
27163
+ isPlainObject,
26240
27164
  isPropertyBuilder,
26241
27165
  isReadOnly,
26242
27166
  isReferenceProperty,
26243
27167
  isValidRegExp,
26244
27168
  joinCollectionLists,
26245
27169
  makePropertiesEditable,
26246
- makePropertiesNonEditable,
26247
27170
  mergeCallbacks,
26248
27171
  mergeCollection,
26249
27172
  mergeDeep,