@firecms/core 3.0.0-canary.282 → 3.0.0-canary.284

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.es.js CHANGED
@@ -1,11 +1,11 @@
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";
4
+ import React__default, { useRef, useEffect, useContext, useCallback, useMemo, useState, createElement, createRef, createContext, forwardRef, useLayoutEffect } from "react";
5
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";
6
6
  import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
7
7
  import hash from "object-hash";
8
- import { getIn, useFormex, setIn, useCreateFormex, Formex, Field } from "@firecms/formex";
8
+ import { getIn, useFormex, setIn, useCreateFormex, flattenKeys, Formex, Field } from "@firecms/formex";
9
9
  import { useNavigate, useLocation, Link, NavLink, Routes, Route, createBrowserRouter, RouterProvider } from "react-router-dom";
10
10
  import Fuse from "fuse.js";
11
11
  import equal from "react-fast-compare";
@@ -5850,7 +5850,7 @@ function ArrayPropertyPreview(t0) {
5850
5850
  }
5851
5851
  let t3;
5852
5852
  if ($[11] !== t2) {
5853
- t3 = /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: t2 });
5853
+ t3 = /* @__PURE__ */ jsx("div", { className: "w-full flex flex-col gap-2", children: t2 });
5854
5854
  $[11] = t2;
5855
5855
  $[12] = t3;
5856
5856
  } else {
@@ -6831,7 +6831,7 @@ function UserDisplay(t0) {
6831
6831
  if (!user) {
6832
6832
  let t12;
6833
6833
  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
6834
- t12 = /* @__PURE__ */ jsx("span", { className: "text-text-secondary dark:text-text-secondary-dark", children: "Select a user" });
6834
+ t12 = /* @__PURE__ */ jsx(EmptyValue, {});
6835
6835
  $[0] = t12;
6836
6836
  } else {
6837
6837
  t12 = $[0];
@@ -6907,7 +6907,7 @@ function UserDisplay(t0) {
6907
6907
  return t8;
6908
6908
  }
6909
6909
  function UserPreview(t0) {
6910
- const $ = c(5);
6910
+ const $ = c(8);
6911
6911
  const {
6912
6912
  value
6913
6913
  } = t0;
@@ -6915,25 +6915,46 @@ function UserPreview(t0) {
6915
6915
  getUser
6916
6916
  } = useInternalUserManagementController();
6917
6917
  if (!value) {
6918
- return null;
6918
+ let t12;
6919
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
6920
+ t12 = /* @__PURE__ */ jsx(EmptyValue, {});
6921
+ $[0] = t12;
6922
+ } else {
6923
+ t12 = $[0];
6924
+ }
6925
+ return t12;
6919
6926
  }
6920
6927
  let t1;
6921
- if ($[0] !== getUser || $[1] !== value) {
6928
+ if ($[1] !== getUser || $[2] !== value) {
6922
6929
  t1 = getUser(value);
6923
- $[0] = getUser;
6924
- $[1] = value;
6925
- $[2] = t1;
6930
+ $[1] = getUser;
6931
+ $[2] = value;
6932
+ $[3] = t1;
6926
6933
  } else {
6927
- t1 = $[2];
6934
+ t1 = $[3];
6928
6935
  }
6929
6936
  const user = t1;
6937
+ if (!user) {
6938
+ let t22;
6939
+ if ($[4] !== value) {
6940
+ t22 = /* @__PURE__ */ jsxs(Typography, { variant: "caption", color: "secondary", children: [
6941
+ "User not found: ",
6942
+ value
6943
+ ] });
6944
+ $[4] = value;
6945
+ $[5] = t22;
6946
+ } else {
6947
+ t22 = $[5];
6948
+ }
6949
+ return t22;
6950
+ }
6930
6951
  let t2;
6931
- if ($[3] !== user) {
6952
+ if ($[6] !== user) {
6932
6953
  t2 = /* @__PURE__ */ jsx(UserDisplay, { user });
6933
- $[3] = user;
6934
- $[4] = t2;
6954
+ $[6] = user;
6955
+ $[7] = t2;
6935
6956
  } else {
6936
- t2 = $[4];
6957
+ t2 = $[7];
6937
6958
  }
6938
6959
  return t2;
6939
6960
  }
@@ -14968,61 +14989,60 @@ function CustomIdField({
14968
14989
  ] });
14969
14990
  }
14970
14991
  const ErrorFocus = (t0) => {
14971
- const $ = c(6);
14992
+ const $ = c(10);
14972
14993
  const {
14973
14994
  containerRef
14974
14995
  } = t0;
14975
14996
  const {
14976
- isSubmitting,
14977
14997
  isValidating,
14978
- errors
14998
+ errors,
14999
+ version
14979
15000
  } = useFormex();
15001
+ const prevVersion = useRef(version);
14980
15002
  let t1;
14981
- let t2;
14982
- if ($[0] !== containerRef || $[1] !== errors || $[2] !== isSubmitting || $[3] !== isValidating) {
15003
+ if ($[0] !== containerRef?.current || $[1] !== errors || $[2] !== isValidating || $[3] !== version) {
14983
15004
  t1 = () => {
15005
+ if (version === prevVersion.current) {
15006
+ return;
15007
+ }
14984
15008
  const keys = Object.keys(errors);
14985
- if (keys.length > 0 && isSubmitting && !isValidating) {
15009
+ if (!isValidating && keys.length > 0) {
14986
15010
  const errorElement = containerRef?.current?.querySelector(`#form_field_${keys[0]}`);
14987
- if (errorElement && containerRef?.current) {
14988
- const scrollableParent = getScrollableParent(containerRef.current);
14989
- if (scrollableParent) {
14990
- const top = errorElement.getBoundingClientRect().top;
14991
- scrollableParent.scrollTo({
14992
- top: scrollableParent.scrollTop + top - 196,
14993
- behavior: "smooth"
14994
- });
14995
- }
15011
+ if (errorElement) {
15012
+ errorElement.scrollIntoView({
15013
+ behavior: "smooth",
15014
+ block: "center"
15015
+ });
14996
15016
  const input = errorElement.querySelector("input");
14997
15017
  if (input) {
14998
15018
  input.focus();
14999
15019
  }
15000
15020
  }
15021
+ prevVersion.current = version;
15001
15022
  }
15002
15023
  };
15003
- t2 = [isSubmitting, isValidating, errors, containerRef];
15004
- $[0] = containerRef;
15024
+ $[0] = containerRef?.current;
15005
15025
  $[1] = errors;
15006
- $[2] = isSubmitting;
15007
- $[3] = isValidating;
15026
+ $[2] = isValidating;
15027
+ $[3] = version;
15008
15028
  $[4] = t1;
15009
- $[5] = t2;
15010
15029
  } else {
15011
15030
  t1 = $[4];
15012
- t2 = $[5];
15031
+ }
15032
+ let t2;
15033
+ if ($[5] !== containerRef || $[6] !== errors || $[7] !== isValidating || $[8] !== version) {
15034
+ t2 = [isValidating, errors, containerRef, version];
15035
+ $[5] = containerRef;
15036
+ $[6] = errors;
15037
+ $[7] = isValidating;
15038
+ $[8] = version;
15039
+ $[9] = t2;
15040
+ } else {
15041
+ t2 = $[9];
15013
15042
  }
15014
15043
  useEffect(t1, t2);
15015
15044
  return null;
15016
15045
  };
15017
- const isScrollable = (ele) => {
15018
- const hasScrollableContent = ele && ele.scrollHeight > ele.clientHeight;
15019
- const overflowYStyle = ele ? window.getComputedStyle(ele).overflowY : null;
15020
- const isOverflowHidden = overflowYStyle && overflowYStyle.indexOf("hidden") !== -1;
15021
- return hasScrollableContent && !isOverflowHidden;
15022
- };
15023
- const getScrollableParent = (ele) => {
15024
- return !ele || ele === document.body ? document.body : isScrollable(ele) ? ele : getScrollableParent(ele.parentNode);
15025
- };
15026
15046
  function EntityFormActions(t0) {
15027
15047
  const $ = c(16);
15028
15048
  const {
@@ -15043,7 +15063,7 @@ function EntityFormActions(t0) {
15043
15063
  const context = useFireCMSContext();
15044
15064
  const sideEntityController = useSideEntityController();
15045
15065
  let t1;
15046
- 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) {
15066
+ 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) {
15047
15067
  t1 = layout === "bottom" ? buildBottomActions$1({
15048
15068
  fullPath,
15049
15069
  fullIdPath,
@@ -15052,13 +15072,13 @@ function EntityFormActions(t0) {
15052
15072
  collection,
15053
15073
  context,
15054
15074
  sideEntityController,
15055
- isSubmitting: formex.isSubmitting,
15056
15075
  disabled,
15057
15076
  status,
15058
15077
  pluginActions,
15059
15078
  openEntityMode,
15060
15079
  navigateBack,
15061
- formContext
15080
+ formContext,
15081
+ formex
15062
15082
  }) : buildSideActions$1({
15063
15083
  fullPath,
15064
15084
  fullIdPath,
@@ -15067,18 +15087,18 @@ function EntityFormActions(t0) {
15067
15087
  collection,
15068
15088
  context,
15069
15089
  sideEntityController,
15070
- isSubmitting: formex.isSubmitting,
15071
15090
  disabled,
15072
15091
  status,
15073
15092
  pluginActions,
15074
- openEntityMode
15093
+ openEntityMode,
15094
+ formex
15075
15095
  });
15076
15096
  $[0] = collection;
15077
15097
  $[1] = context;
15078
15098
  $[2] = disabled;
15079
15099
  $[3] = entity;
15080
15100
  $[4] = formContext;
15081
- $[5] = formex.isSubmitting;
15101
+ $[5] = formex;
15082
15102
  $[6] = fullIdPath;
15083
15103
  $[7] = fullPath;
15084
15104
  $[8] = layout;
@@ -15103,14 +15123,15 @@ function buildBottomActions$1({
15103
15123
  collection,
15104
15124
  context,
15105
15125
  sideEntityController,
15106
- isSubmitting,
15107
15126
  disabled,
15108
15127
  status,
15109
15128
  pluginActions,
15110
15129
  openEntityMode,
15111
15130
  navigateBack,
15112
- formContext
15131
+ formContext,
15132
+ formex
15113
15133
  }) {
15134
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
15114
15135
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
15115
15136
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
15116
15137
  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) => {
@@ -15129,8 +15150,8 @@ function buildBottomActions$1({
15129
15150
  });
15130
15151
  }, children: action.icon }, action.name)) }),
15131
15152
  pluginActions,
15132
- /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15133
- /* @__PURE__ */ jsxs(Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, children: [
15153
+ /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || formex.isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15154
+ /* @__PURE__ */ jsxs(Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || formex.isSubmitting, startIcon: hasErrors ? /* @__PURE__ */ jsx(ErrorIcon, {}) : void 0, children: [
15134
15155
  status === "existing" && "Save",
15135
15156
  status === "copy" && "Create copy",
15136
15157
  status === "new" && "Create"
@@ -15147,22 +15168,35 @@ function buildSideActions$1({
15147
15168
  collection,
15148
15169
  context,
15149
15170
  sideEntityController,
15150
- isSubmitting,
15151
15171
  disabled,
15152
15172
  status,
15153
- pluginActions
15173
+ pluginActions,
15174
+ formex
15154
15175
  }) {
15176
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
15155
15177
  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: [
15156
- /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, children: [
15178
+ /* @__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: [
15157
15179
  status === "existing" && "Save",
15158
15180
  status === "copy" && "Create copy",
15159
15181
  status === "new" && "Create"
15160
15182
  ] }),
15161
- /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15183
+ /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || formex.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15162
15184
  pluginActions,
15163
15185
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) })
15164
15186
  ] });
15165
15187
  }
15188
+ function extractTouchedValues(values, touched) {
15189
+ let acc = {};
15190
+ if (!touched || typeof touched !== "object") {
15191
+ return acc;
15192
+ }
15193
+ Object.entries(touched).forEach(([key, value]) => {
15194
+ if (value) {
15195
+ acc = setIn(acc, key, getIn(values, key));
15196
+ }
15197
+ });
15198
+ return acc;
15199
+ }
15166
15200
  function EntityForm({
15167
15201
  path,
15168
15202
  fullIdPath,
@@ -15220,7 +15254,7 @@ function EntityForm({
15220
15254
  const customizationController = useCustomizationController();
15221
15255
  const context = useFireCMSContext();
15222
15256
  const analyticsController = useAnalyticsController();
15223
- const [underlyingChanges, setUnderlyingChanges] = useState({});
15257
+ const [underlyingChanges] = useState({});
15224
15258
  const [customIdLoading, setCustomIdLoading] = useState(false);
15225
15259
  const mustSetCustomId = (status === "new" || status === "copy") && Boolean(collection.customId) && collection.customId !== "optional";
15226
15260
  const initialEntityId = useMemo(() => {
@@ -15268,16 +15302,30 @@ function EntityForm({
15268
15302
  formexController.setSubmitting(false);
15269
15303
  });
15270
15304
  };
15305
+ const baseInitialValues = getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs);
15306
+ const initialValues = initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues;
15307
+ const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15271
15308
  const formex = formexProp ?? useCreateFormex({
15272
- initialValues: initialDirtyValues ?? getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs),
15273
- initialDirty: Boolean(initialDirtyValues),
15309
+ initialValues,
15310
+ initialDirty,
15311
+ initialTouched: initialDirtyValues ? flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
15312
+ ...previousValue,
15313
+ [currentValue]: true
15314
+ }), {}) : {},
15274
15315
  onSubmit,
15275
15316
  onReset: () => {
15276
15317
  clearDirtyCache();
15277
15318
  onValuesModified?.(false);
15278
15319
  },
15279
- validation: (values_0) => {
15280
- return validationSchema?.validate(values_0, {
15320
+ onValuesChangeDeferred: (values_0, controller) => {
15321
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15322
+ if (controller.dirty) {
15323
+ const touchedValues = extractTouchedValues(values_0, controller.touched);
15324
+ saveEntityToCache(key, touchedValues);
15325
+ }
15326
+ },
15327
+ validation: (values_1) => {
15328
+ return validationSchema?.validate(values_1, {
15281
15329
  abortEarly: false
15282
15330
  }).then(() => {
15283
15331
  return {};
@@ -15360,7 +15408,7 @@ function EntityForm({
15360
15408
  console.error(e_3);
15361
15409
  }, [entityId, path, snackbarController]);
15362
15410
  const saveEntity = ({
15363
- values: values_1,
15411
+ values: values_2,
15364
15412
  previousValues,
15365
15413
  entityId: entityId_0,
15366
15414
  collection: collection_0,
@@ -15369,7 +15417,7 @@ function EntityForm({
15369
15417
  return saveEntityWithCallbacks({
15370
15418
  path: path_0,
15371
15419
  entityId: entityId_0,
15372
- values: values_1,
15420
+ values: values_2,
15373
15421
  previousValues,
15374
15422
  collection: collection_0,
15375
15423
  status,
@@ -15385,34 +15433,34 @@ function EntityForm({
15385
15433
  collection: collection_1,
15386
15434
  path: path_1,
15387
15435
  entityId: entityId_1,
15388
- values: values_2,
15436
+ values: values_3,
15389
15437
  previousValues: previousValues_0,
15390
15438
  autoSave: autoSave_0
15391
15439
  }) => {
15392
15440
  if (!status) return;
15393
15441
  if (autoSave_0) {
15394
- setValuesToBeSaved(values_2);
15442
+ setValuesToBeSaved(values_3);
15395
15443
  } else {
15396
15444
  return saveEntity({
15397
15445
  collection: collection_1,
15398
15446
  path: path_1,
15399
15447
  entityId: entityId_1,
15400
- values: values_2,
15448
+ values: values_3,
15401
15449
  previousValues: previousValues_0
15402
15450
  });
15403
15451
  }
15404
15452
  };
15405
15453
  const lastSavedValues = useRef(entity?.values);
15406
- const save = (values_3) => {
15407
- lastSavedValues.current = values_3;
15454
+ const save = (values_4) => {
15455
+ lastSavedValues.current = values_4;
15408
15456
  return onSaveEntityRequest({
15409
15457
  collection: resolvedCollection,
15410
15458
  path,
15411
15459
  entityId,
15412
- values: values_3,
15460
+ values: values_4,
15413
15461
  previousValues: entity?.values,
15414
15462
  autoSave: autoSave ?? false
15415
- }).then((res) => {
15463
+ }).then(() => {
15416
15464
  const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
15417
15465
  analyticsController.onAnalyticsEvent?.(eventName, {
15418
15466
  path
@@ -15446,7 +15494,8 @@ function EntityForm({
15446
15494
  type: "error",
15447
15495
  message: "Error updating id, check the console"
15448
15496
  });
15449
- }, []);
15497
+ console.error(error);
15498
+ }, [snackbarController]);
15450
15499
  const pluginActions = [];
15451
15500
  const plugins = customizationController.plugins;
15452
15501
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
@@ -15495,20 +15544,12 @@ function EntityForm({
15495
15544
  onValuesModified?.(modified);
15496
15545
  }
15497
15546
  }, [formex.dirty]);
15498
- const deferredValues = useDeferredValue(formex.values);
15499
15547
  const modified = formex.dirty;
15500
15548
  const uniqueFieldValidator = useCallback(({
15501
15549
  name,
15502
- value,
15503
- property
15550
+ value
15504
15551
  }) => dataSource.checkUniqueField(path, name, value, entityId, collection), [dataSource, path, entityId]);
15505
15552
  const validationSchema = useMemo(() => entityId ? getYupEntitySchema(entityId, resolvedCollection.properties, uniqueFieldValidator) : void 0, [entityId, resolvedCollection.properties, uniqueFieldValidator]);
15506
- useEffect(() => {
15507
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15508
- if (modified) {
15509
- saveEntityToCache(key, deferredValues);
15510
- }
15511
- }, [deferredValues, modified, path, entityId, status]);
15512
15553
  useOnAutoSave(autoSave, formex, lastSavedValues, save);
15513
15554
  useEffect(() => {
15514
15555
  if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
@@ -15527,18 +15568,18 @@ function EntityForm({
15527
15568
  return /* @__PURE__ */ jsx(Builder, { collection, entity, modifiedValues: formex.values, formContext });
15528
15569
  }
15529
15570
  return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15530
- const property_0 = resolvedCollection.properties[key_1];
15531
- if (property_0) {
15571
+ const property = resolvedCollection.properties[key_1];
15572
+ if (property) {
15532
15573
  const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_1) && formex.touched[key_1];
15533
- const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property_0) || Boolean(property_0.disabled);
15534
- const hidden = isHidden(property_0);
15574
+ const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
15575
+ const hidden = isHidden(property);
15535
15576
  if (hidden) return null;
15536
- const widthPercentage = property_0.widthPercentage ?? 100;
15577
+ const widthPercentage = property.widthPercentage ?? 100;
15537
15578
  const cmsFormFieldProps = {
15538
15579
  propertyKey: key_1,
15539
15580
  disabled: disabled_0,
15540
- property: property_0,
15541
- includeDescription: property_0.description || property_0.longDescription,
15581
+ property,
15582
+ includeDescription: property.description || property.longDescription,
15542
15583
  underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
15543
15584
  context: formContext,
15544
15585
  partOfArray: false,
@@ -15594,7 +15635,7 @@ function EntityForm({
15594
15635
  }
15595
15636
  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 });
15596
15637
  return /* @__PURE__ */ jsx(Formex, { value: formex, children: /* @__PURE__ */ jsxs("form", { onSubmit: formex.handleSubmit, onReset: () => formex.resetForm({
15597
- values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs)
15638
+ values: baseInitialValues
15598
15639
  }), noValidate: true, className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15599
15640
  /* @__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: [
15600
15641
  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" }) }) }),
@@ -17778,6 +17819,13 @@ function PropertyFieldBindingInternal(t0) {
17778
17819
  } = t0;
17779
17820
  const authController = useAuthController();
17780
17821
  const customizationController = useCustomizationController();
17822
+ if (propertyKey === "created_by") {
17823
+ console.log("Rendering field for created_by", {
17824
+ propertyKey,
17825
+ property,
17826
+ context
17827
+ });
17828
+ }
17781
17829
  let t1;
17782
17830
  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) {
17783
17831
  t1 = (fieldProps) => {
@@ -20642,7 +20690,7 @@ function PropertyConfigBadge(t0) {
20642
20690
  propertyConfig,
20643
20691
  disabled
20644
20692
  } = t0;
20645
- const classes = "h-8 w-8 p-1 rounded-full shadow text-white " + (disabled ? "bg-surface-400 dark:bg-surface-600" : "");
20693
+ const classes = "h-8 w-8 flex items-center justify-center rounded-full shadow text-white " + (disabled ? "bg-surface-400 dark:bg-surface-600" : "");
20646
20694
  let t1;
20647
20695
  if ($[0] !== classes || $[1] !== disabled || $[2] !== propertyConfig) {
20648
20696
  const defaultPropertyConfig = typeof propertyConfig?.property === "object" ? getDefaultFieldConfig(propertyConfig.property) : void 0;
@@ -20657,7 +20705,7 @@ function PropertyConfigBadge(t0) {
20657
20705
  } else {
20658
20706
  t3 = $[5];
20659
20707
  }
20660
- t1 = /* @__PURE__ */ jsx("div", { className: classes, style: t3, children: propertyConfig?.Icon ? getIconForWidget(propertyConfig, "medium") : getIconForWidget(defaultPropertyConfig, "medium") });
20708
+ t1 = /* @__PURE__ */ jsx("div", { className: classes, style: t3, children: propertyConfig?.Icon ? getIconForWidget(propertyConfig, "small") : getIconForWidget(defaultPropertyConfig, "small") });
20661
20709
  $[0] = classes;
20662
20710
  $[1] = disabled;
20663
20711
  $[2] = propertyConfig;
@@ -21993,7 +22041,8 @@ function useBuildNavigationController(props) {
21993
22041
  }, []);
21994
22042
  const isUrlCollectionPath = useCallback((path_1) => removeInitialAndTrailingSlashes(path_1 + "/").startsWith(removeInitialAndTrailingSlashes(fullCollectionPath) + "/"), [fullCollectionPath]);
21995
22043
  const urlPathToDataPath = useCallback((path_2) => {
21996
- if (path_2.startsWith(fullCollectionPath)) return path_2.replace(fullCollectionPath, "");
22044
+ const decodedPath = decodeURIComponent(path_2);
22045
+ if (decodedPath.startsWith(fullCollectionPath)) return decodedPath.replace(fullCollectionPath, "");
21997
22046
  throw Error("Expected path starting with " + fullCollectionPath);
21998
22047
  }, [fullCollectionPath]);
21999
22048
  const resolveIdsFrom = useCallback((path_3) => {
@@ -22799,14 +22848,14 @@ function EntityEditViewFormActions({
22799
22848
  collection,
22800
22849
  context,
22801
22850
  sideEntityController,
22802
- isSubmitting: formex.isSubmitting,
22803
22851
  disabled,
22804
22852
  status,
22805
22853
  sideDialogContext,
22806
22854
  pluginActions,
22807
22855
  openEntityMode,
22808
22856
  navigateBack,
22809
- formContext
22857
+ formContext,
22858
+ formex
22810
22859
  }) : buildSideActions({
22811
22860
  savingError,
22812
22861
  entity,
@@ -22814,14 +22863,14 @@ function EntityEditViewFormActions({
22814
22863
  collection,
22815
22864
  context,
22816
22865
  sideEntityController,
22817
- isSubmitting: formex.isSubmitting,
22818
22866
  sideDialogContext,
22819
22867
  disabled,
22820
22868
  status,
22821
22869
  pluginActions,
22822
22870
  openEntityMode,
22823
22871
  navigateBack,
22824
- formContext
22872
+ formContext,
22873
+ formex
22825
22874
  });
22826
22875
  }
22827
22876
  function buildBottomActions({
@@ -22831,15 +22880,16 @@ function buildBottomActions({
22831
22880
  collection,
22832
22881
  context,
22833
22882
  sideEntityController,
22834
- isSubmitting,
22835
22883
  disabled,
22836
22884
  status,
22837
22885
  sideDialogContext,
22838
22886
  pluginActions,
22839
22887
  openEntityMode,
22840
22888
  navigateBack,
22841
- formContext
22889
+ formContext,
22890
+ formex
22842
22891
  }) {
22892
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22843
22893
  const canClose = openEntityMode === "side_panel";
22844
22894
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
22845
22895
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
@@ -22859,15 +22909,16 @@ function buildBottomActions({
22859
22909
  return /* @__PURE__ */ jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22860
22910
  }) }),
22861
22911
  pluginActions,
22862
- /* @__PURE__ */ jsx(Button, { variant: "text", color: "primary", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22863
- /* @__PURE__ */ jsxs(Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, onClick: () => {
22912
+ hasErrors ? /* @__PURE__ */ jsx(ErrorTooltip, { title: "This form has errors", children: /* @__PURE__ */ jsx(ErrorIcon, { className: "ml-4", color: "error", size: "smallest" }) }) : null,
22913
+ /* @__PURE__ */ jsx(Button, { variant: "text", color: "primary", disabled: disabled || formex.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22914
+ /* @__PURE__ */ jsxs(Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || formex.isSubmitting, onClick: () => {
22864
22915
  sideDialogContext.setPendingClose(false);
22865
22916
  }, children: [
22866
22917
  status === "existing" && "Save",
22867
22918
  status === "copy" && "Create copy",
22868
22919
  status === "new" && "Create"
22869
22920
  ] }),
22870
- canClose && /* @__PURE__ */ jsxs(LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: isSubmitting, disabled, onClick: () => {
22921
+ canClose && /* @__PURE__ */ jsxs(LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: formex.isSubmitting, disabled, onClick: () => {
22871
22922
  sideDialogContext.setPendingClose?.(true);
22872
22923
  }, children: [
22873
22924
  status === "existing" && "Save and close",
@@ -22883,24 +22934,25 @@ function buildSideActions({
22883
22934
  collection,
22884
22935
  context,
22885
22936
  sideEntityController,
22886
- isSubmitting,
22887
22937
  disabled,
22888
22938
  status,
22889
22939
  sideDialogContext,
22890
22940
  pluginActions,
22891
22941
  openEntityMode,
22892
22942
  navigateBack,
22893
- formContext
22943
+ formContext,
22944
+ formex
22894
22945
  }) {
22946
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22895
22947
  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: [
22896
- /* @__PURE__ */ jsxs(LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, onClick: () => {
22948
+ /* @__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: () => {
22897
22949
  sideDialogContext.setPendingClose?.(false);
22898
22950
  }, children: [
22899
22951
  status === "existing" && "Save",
22900
22952
  status === "copy" && "Create copy",
22901
22953
  status === "new" && "Create"
22902
22954
  ] }),
22903
- /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22955
+ /* @__PURE__ */ jsx(Button, { fullWidth: true, variant: "text", disabled: disabled || formex.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22904
22956
  pluginActions,
22905
22957
  formActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-wrap mt-2", children: formActions.map((action) => {
22906
22958
  const props = {
@@ -23128,7 +23180,8 @@ function EntityEditView({
23128
23180
  databaseId: props.databaseId,
23129
23181
  useCache: false
23130
23182
  });
23131
- const cachedValues = entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new");
23183
+ const enableLocalChangesBackup = props.collection.enableLocalChangesBackup !== void 0 ? props.collection.enableLocalChangesBackup : true;
23184
+ const initialDirtyValues = enableLocalChangesBackup ? entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new") : void 0;
23132
23185
  const authController = useAuthController();
23133
23186
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23134
23187
  const [status, setStatus] = useState(initialStatus);
@@ -23139,13 +23192,13 @@ function EntityEditView({
23139
23192
  return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : void 0;
23140
23193
  }
23141
23194
  }, [authController, entity, status]);
23142
- if (dataLoading && !cachedValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23195
+ if (dataLoading && !initialDirtyValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23143
23196
  return /* @__PURE__ */ jsx(CircularProgressCenter, {});
23144
23197
  }
23145
- if (entityId && !entity && !cachedValues) {
23198
+ if (entityId && !entity && !initialDirtyValues) {
23146
23199
  console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
23147
23200
  }
23148
- return /* @__PURE__ */ jsx(EntityEditViewInner, { ...props, entityId, entity, cachedDirtyValues: cachedValues, dataLoading, status, setStatus, canEdit });
23201
+ return /* @__PURE__ */ jsx(EntityEditViewInner, { ...props, entityId, entity, initialDirtyValues, dataLoading, status, setStatus, canEdit });
23149
23202
  }
23150
23203
  function EntityEditViewInner({
23151
23204
  path,
@@ -23158,7 +23211,7 @@ function EntityEditViewInner({
23158
23211
  onSaved,
23159
23212
  onTabChange,
23160
23213
  entity,
23161
- cachedDirtyValues,
23214
+ initialDirtyValues,
23162
23215
  dataLoading,
23163
23216
  layout = "side_panel",
23164
23217
  barActions,
@@ -23286,7 +23339,7 @@ function EntityEditViewInner({
23286
23339
  /* @__PURE__ */ jsx(EntityView, { className: "px-8 h-full overflow-auto", entity, path, collection }),
23287
23340
  /* @__PURE__ */ jsx("div", { className: "h-16" })
23288
23341
  ] }) }) : null;
23289
- 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) => {
23342
+ 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) => {
23290
23343
  setUsedEntity(entity_0);
23291
23344
  formProps?.onEntityChange?.(entity_0);
23292
23345
  }, onStatusChange: (status_0) => {