@firecms/core 3.0.0-canary.283 → 3.0.0-canary.285

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.umd.js CHANGED
@@ -9754,27 +9754,6 @@
9754
9754
  }
9755
9755
  return value;
9756
9756
  }
9757
- if (isLocalStorageAvailable) {
9758
- try {
9759
- for (let i = 0; i < localStorage.length; i++) {
9760
- const fullKey = localStorage.key(i);
9761
- if (fullKey && fullKey.startsWith(LOCAL_STORAGE_PREFIX)) {
9762
- const path = fullKey.substring(LOCAL_STORAGE_PREFIX.length);
9763
- const entityString = localStorage.getItem(fullKey);
9764
- if (entityString) {
9765
- try {
9766
- const entity = JSON.parse(entityString, customReviver);
9767
- entityCache.set(path, entity);
9768
- } catch (parseError) {
9769
- console.error(`Failed to parse entity for path "${path}" from localStorage:`, parseError);
9770
- }
9771
- }
9772
- }
9773
- }
9774
- } catch (error) {
9775
- console.error("Error accessing localStorage during initialization:", error);
9776
- }
9777
- }
9778
9757
  function saveEntityToCache(path, data) {
9779
9758
  entityCache.set(path, data);
9780
9759
  if (isLocalStorageAvailable) {
@@ -9787,11 +9766,11 @@
9787
9766
  }
9788
9767
  }
9789
9768
  }
9790
- function getEntityFromCache(path) {
9769
+ function getEntityFromCache(path, useLocalStorage = true) {
9791
9770
  if (entityCache.has(path)) {
9792
9771
  return entityCache.get(path);
9793
9772
  }
9794
- if (isLocalStorageAvailable) {
9773
+ if (isLocalStorageAvailable && useLocalStorage) {
9795
9774
  try {
9796
9775
  const key = LOCAL_STORAGE_PREFIX + path;
9797
9776
  const entityString = localStorage.getItem(key);
@@ -14987,61 +14966,60 @@
14987
14966
  ] });
14988
14967
  }
14989
14968
  const ErrorFocus = (t0) => {
14990
- const $ = reactCompilerRuntime.c(6);
14969
+ const $ = reactCompilerRuntime.c(10);
14991
14970
  const {
14992
14971
  containerRef
14993
14972
  } = t0;
14994
14973
  const {
14995
- isSubmitting,
14996
14974
  isValidating,
14997
- errors
14975
+ errors,
14976
+ version
14998
14977
  } = formex.useFormex();
14978
+ const prevVersion = React.useRef(version);
14999
14979
  let t1;
15000
- let t2;
15001
- if ($[0] !== containerRef || $[1] !== errors || $[2] !== isSubmitting || $[3] !== isValidating) {
14980
+ if ($[0] !== containerRef?.current || $[1] !== errors || $[2] !== isValidating || $[3] !== version) {
15002
14981
  t1 = () => {
14982
+ if (version === prevVersion.current) {
14983
+ return;
14984
+ }
15003
14985
  const keys = Object.keys(errors);
15004
- if (keys.length > 0 && isSubmitting && !isValidating) {
14986
+ if (!isValidating && keys.length > 0) {
15005
14987
  const errorElement = containerRef?.current?.querySelector(`#form_field_${keys[0]}`);
15006
- if (errorElement && containerRef?.current) {
15007
- const scrollableParent = getScrollableParent(containerRef.current);
15008
- if (scrollableParent) {
15009
- const top = errorElement.getBoundingClientRect().top;
15010
- scrollableParent.scrollTo({
15011
- top: scrollableParent.scrollTop + top - 196,
15012
- behavior: "smooth"
15013
- });
15014
- }
14988
+ if (errorElement) {
14989
+ errorElement.scrollIntoView({
14990
+ behavior: "smooth",
14991
+ block: "center"
14992
+ });
15015
14993
  const input = errorElement.querySelector("input");
15016
14994
  if (input) {
15017
14995
  input.focus();
15018
14996
  }
15019
14997
  }
14998
+ prevVersion.current = version;
15020
14999
  }
15021
15000
  };
15022
- t2 = [isSubmitting, isValidating, errors, containerRef];
15023
- $[0] = containerRef;
15001
+ $[0] = containerRef?.current;
15024
15002
  $[1] = errors;
15025
- $[2] = isSubmitting;
15026
- $[3] = isValidating;
15003
+ $[2] = isValidating;
15004
+ $[3] = version;
15027
15005
  $[4] = t1;
15028
- $[5] = t2;
15029
15006
  } else {
15030
15007
  t1 = $[4];
15031
- t2 = $[5];
15008
+ }
15009
+ let t2;
15010
+ if ($[5] !== containerRef || $[6] !== errors || $[7] !== isValidating || $[8] !== version) {
15011
+ t2 = [isValidating, errors, containerRef, version];
15012
+ $[5] = containerRef;
15013
+ $[6] = errors;
15014
+ $[7] = isValidating;
15015
+ $[8] = version;
15016
+ $[9] = t2;
15017
+ } else {
15018
+ t2 = $[9];
15032
15019
  }
15033
15020
  React.useEffect(t1, t2);
15034
15021
  return null;
15035
15022
  };
15036
- const isScrollable = (ele) => {
15037
- const hasScrollableContent = ele && ele.scrollHeight > ele.clientHeight;
15038
- const overflowYStyle = ele ? window.getComputedStyle(ele).overflowY : null;
15039
- const isOverflowHidden = overflowYStyle && overflowYStyle.indexOf("hidden") !== -1;
15040
- return hasScrollableContent && !isOverflowHidden;
15041
- };
15042
- const getScrollableParent = (ele) => {
15043
- return !ele || ele === document.body ? document.body : isScrollable(ele) ? ele : getScrollableParent(ele.parentNode);
15044
- };
15045
15023
  function EntityFormActions(t0) {
15046
15024
  const $ = reactCompilerRuntime.c(16);
15047
15025
  const {
@@ -15062,7 +15040,7 @@
15062
15040
  const context = useFireCMSContext();
15063
15041
  const sideEntityController = useSideEntityController();
15064
15042
  let t1;
15065
- if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formContext || $[5] !== formex2.isSubmitting || $[6] !== fullIdPath || $[7] !== fullPath || $[8] !== layout || $[9] !== navigateBack || $[10] !== openEntityMode || $[11] !== pluginActions || $[12] !== savingError || $[13] !== sideEntityController || $[14] !== status) {
15043
+ if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formContext || $[5] !== formex2 || $[6] !== fullIdPath || $[7] !== fullPath || $[8] !== layout || $[9] !== navigateBack || $[10] !== openEntityMode || $[11] !== pluginActions || $[12] !== savingError || $[13] !== sideEntityController || $[14] !== status) {
15066
15044
  t1 = layout === "bottom" ? buildBottomActions$1({
15067
15045
  fullPath,
15068
15046
  fullIdPath,
@@ -15071,13 +15049,13 @@
15071
15049
  collection,
15072
15050
  context,
15073
15051
  sideEntityController,
15074
- isSubmitting: formex2.isSubmitting,
15075
15052
  disabled,
15076
15053
  status,
15077
15054
  pluginActions,
15078
15055
  openEntityMode,
15079
15056
  navigateBack,
15080
- formContext
15057
+ formContext,
15058
+ formex: formex2
15081
15059
  }) : buildSideActions$1({
15082
15060
  fullPath,
15083
15061
  fullIdPath,
@@ -15086,18 +15064,18 @@
15086
15064
  collection,
15087
15065
  context,
15088
15066
  sideEntityController,
15089
- isSubmitting: formex2.isSubmitting,
15090
15067
  disabled,
15091
15068
  status,
15092
15069
  pluginActions,
15093
- openEntityMode
15070
+ openEntityMode,
15071
+ formex: formex2
15094
15072
  });
15095
15073
  $[0] = collection;
15096
15074
  $[1] = context;
15097
15075
  $[2] = disabled;
15098
15076
  $[3] = entity;
15099
15077
  $[4] = formContext;
15100
- $[5] = formex2.isSubmitting;
15078
+ $[5] = formex2;
15101
15079
  $[6] = fullIdPath;
15102
15080
  $[7] = fullPath;
15103
15081
  $[8] = layout;
@@ -15122,14 +15100,15 @@
15122
15100
  collection,
15123
15101
  context,
15124
15102
  sideEntityController,
15125
- isSubmitting,
15126
15103
  disabled,
15127
15104
  status,
15128
15105
  pluginActions,
15129
15106
  openEntityMode,
15130
15107
  navigateBack,
15131
- formContext
15108
+ formContext,
15109
+ formex: formex2
15132
15110
  }) {
15111
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
15133
15112
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogActions, { position: "absolute", children: [
15134
15113
  savingError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { color: "error", children: savingError.message }) }),
15135
15114
  entity && (formActions ?? []).length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-grow flex overflow-auto no-scrollbar", children: (formActions ?? []).map((action) => /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { color: "primary", onClick: (event) => {
@@ -15148,8 +15127,8 @@
15148
15127
  });
15149
15128
  }, children: action.icon }, action.name)) }),
15150
15129
  pluginActions,
15151
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", disabled: disabled || isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15152
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, children: [
15130
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", disabled: disabled || formex2.isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15131
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || formex2.isSubmitting, startIcon: hasErrors ? /* @__PURE__ */ jsxRuntime.jsx(ui.ErrorIcon, {}) : void 0, children: [
15153
15132
  status === "existing" && "Save",
15154
15133
  status === "copy" && "Create copy",
15155
15134
  status === "new" && "Create"
@@ -15166,22 +15145,35 @@
15166
15145
  collection,
15167
15146
  context,
15168
15147
  sideEntityController,
15169
- isSubmitting,
15170
15148
  disabled,
15171
15149
  status,
15172
- pluginActions
15150
+ pluginActions,
15151
+ formex: formex2
15173
15152
  }) {
15153
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
15174
15154
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", ui.defaultBorderMixin), children: [
15175
- /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, children: [
15155
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", startIcon: hasErrors ? /* @__PURE__ */ jsxRuntime.jsx(ui.ErrorIcon, {}) : void 0, disabled: disabled || formex2.isSubmitting, children: [
15176
15156
  status === "existing" && "Save",
15177
15157
  status === "copy" && "Create copy",
15178
15158
  status === "new" && "Create"
15179
15159
  ] }),
15180
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15160
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || formex2.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15181
15161
  pluginActions,
15182
15162
  savingError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { color: "error", children: savingError.message }) })
15183
15163
  ] });
15184
15164
  }
15165
+ function extractTouchedValues(values, touched) {
15166
+ let acc = {};
15167
+ if (!touched || typeof touched !== "object") {
15168
+ return acc;
15169
+ }
15170
+ Object.entries(touched).forEach(([key, value]) => {
15171
+ if (value) {
15172
+ acc = formex.setIn(acc, key, formex.getIn(values, key));
15173
+ }
15174
+ });
15175
+ return acc;
15176
+ }
15185
15177
  function EntityForm({
15186
15178
  path,
15187
15179
  fullIdPath,
@@ -15239,7 +15231,7 @@
15239
15231
  const customizationController = useCustomizationController();
15240
15232
  const context = useFireCMSContext();
15241
15233
  const analyticsController = useAnalyticsController();
15242
- const [underlyingChanges, setUnderlyingChanges] = React.useState({});
15234
+ const [underlyingChanges] = React.useState({});
15243
15235
  const [customIdLoading, setCustomIdLoading] = React.useState(false);
15244
15236
  const mustSetCustomId = (status === "new" || status === "copy") && Boolean(collection.customId) && collection.customId !== "optional";
15245
15237
  const initialEntityId = React.useMemo(() => {
@@ -15287,16 +15279,30 @@
15287
15279
  formexController.setSubmitting(false);
15288
15280
  });
15289
15281
  };
15282
+ const baseInitialValues = getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs);
15283
+ const initialValues = initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues;
15284
+ const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15290
15285
  const formex$1 = formexProp ?? formex.useCreateFormex({
15291
- initialValues: initialDirtyValues ?? getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs),
15292
- initialDirty: Boolean(initialDirtyValues),
15286
+ initialValues,
15287
+ initialDirty,
15288
+ initialTouched: initialDirtyValues ? formex.flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
15289
+ ...previousValue,
15290
+ [currentValue]: true
15291
+ }), {}) : {},
15293
15292
  onSubmit,
15294
15293
  onReset: () => {
15295
15294
  clearDirtyCache();
15296
15295
  onValuesModified?.(false);
15297
15296
  },
15298
- validation: (values_0) => {
15299
- return validationSchema?.validate(values_0, {
15297
+ onValuesChangeDeferred: (values_0, controller) => {
15298
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15299
+ if (controller.dirty) {
15300
+ const touchedValues = extractTouchedValues(values_0, controller.touched);
15301
+ saveEntityToCache(key, touchedValues);
15302
+ }
15303
+ },
15304
+ validation: (values_1) => {
15305
+ return validationSchema?.validate(values_1, {
15300
15306
  abortEarly: false
15301
15307
  }).then(() => {
15302
15308
  return {};
@@ -15379,7 +15385,7 @@
15379
15385
  console.error(e_3);
15380
15386
  }, [entityId, path, snackbarController]);
15381
15387
  const saveEntity = ({
15382
- values: values_1,
15388
+ values: values_2,
15383
15389
  previousValues,
15384
15390
  entityId: entityId_0,
15385
15391
  collection: collection_0,
@@ -15388,7 +15394,7 @@
15388
15394
  return saveEntityWithCallbacks({
15389
15395
  path: path_0,
15390
15396
  entityId: entityId_0,
15391
- values: values_1,
15397
+ values: values_2,
15392
15398
  previousValues,
15393
15399
  collection: collection_0,
15394
15400
  status,
@@ -15404,34 +15410,34 @@
15404
15410
  collection: collection_1,
15405
15411
  path: path_1,
15406
15412
  entityId: entityId_1,
15407
- values: values_2,
15413
+ values: values_3,
15408
15414
  previousValues: previousValues_0,
15409
15415
  autoSave: autoSave_0
15410
15416
  }) => {
15411
15417
  if (!status) return;
15412
15418
  if (autoSave_0) {
15413
- setValuesToBeSaved(values_2);
15419
+ setValuesToBeSaved(values_3);
15414
15420
  } else {
15415
15421
  return saveEntity({
15416
15422
  collection: collection_1,
15417
15423
  path: path_1,
15418
15424
  entityId: entityId_1,
15419
- values: values_2,
15425
+ values: values_3,
15420
15426
  previousValues: previousValues_0
15421
15427
  });
15422
15428
  }
15423
15429
  };
15424
15430
  const lastSavedValues = React.useRef(entity?.values);
15425
- const save = (values_3) => {
15426
- lastSavedValues.current = values_3;
15431
+ const save = (values_4) => {
15432
+ lastSavedValues.current = values_4;
15427
15433
  return onSaveEntityRequest({
15428
15434
  collection: resolvedCollection,
15429
15435
  path,
15430
15436
  entityId,
15431
- values: values_3,
15437
+ values: values_4,
15432
15438
  previousValues: entity?.values,
15433
15439
  autoSave: autoSave ?? false
15434
- }).then((res) => {
15440
+ }).then(() => {
15435
15441
  const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
15436
15442
  analyticsController.onAnalyticsEvent?.(eventName, {
15437
15443
  path
@@ -15465,7 +15471,8 @@
15465
15471
  type: "error",
15466
15472
  message: "Error updating id, check the console"
15467
15473
  });
15468
- }, []);
15474
+ console.error(error);
15475
+ }, [snackbarController]);
15469
15476
  const pluginActions = [];
15470
15477
  const plugins = customizationController.plugins;
15471
15478
  const actionsDisabled = disabled || formex$1.isSubmitting || status === "existing" && !formex$1.dirty || Boolean(disabledProp);
@@ -15514,20 +15521,12 @@
15514
15521
  onValuesModified?.(modified);
15515
15522
  }
15516
15523
  }, [formex$1.dirty]);
15517
- const deferredValues = React.useDeferredValue(formex$1.values);
15518
15524
  const modified = formex$1.dirty;
15519
15525
  const uniqueFieldValidator = React.useCallback(({
15520
15526
  name,
15521
- value,
15522
- property
15527
+ value
15523
15528
  }) => dataSource.checkUniqueField(path, name, value, entityId, collection), [dataSource, path, entityId]);
15524
15529
  const validationSchema = React.useMemo(() => entityId ? getYupEntitySchema(entityId, resolvedCollection.properties, uniqueFieldValidator) : void 0, [entityId, resolvedCollection.properties, uniqueFieldValidator]);
15525
- React.useEffect(() => {
15526
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15527
- if (modified) {
15528
- saveEntityToCache(key, deferredValues);
15529
- }
15530
- }, [deferredValues, modified, path, entityId, status]);
15531
15530
  useOnAutoSave(autoSave, formex$1, lastSavedValues, save);
15532
15531
  React.useEffect(() => {
15533
15532
  if (!autoSave && !formex$1.isSubmitting && underlyingChanges && entity) {
@@ -15546,18 +15545,18 @@
15546
15545
  return /* @__PURE__ */ jsxRuntime.jsx(Builder, { collection, entity, modifiedValues: formex$1.values, formContext });
15547
15546
  }
15548
15547
  return /* @__PURE__ */ jsxRuntime.jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15549
- const property_0 = resolvedCollection.properties[key_1];
15550
- if (property_0) {
15548
+ const property = resolvedCollection.properties[key_1];
15549
+ if (property) {
15551
15550
  const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_1) && formex$1.touched[key_1];
15552
- const disabled_0 = disabledProp || !autoSave && formex$1.isSubmitting || isReadOnly(property_0) || Boolean(property_0.disabled);
15553
- const hidden = isHidden(property_0);
15551
+ const disabled_0 = disabledProp || !autoSave && formex$1.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
15552
+ const hidden = isHidden(property);
15554
15553
  if (hidden) return null;
15555
- const widthPercentage = property_0.widthPercentage ?? 100;
15554
+ const widthPercentage = property.widthPercentage ?? 100;
15556
15555
  const cmsFormFieldProps = {
15557
15556
  propertyKey: key_1,
15558
15557
  disabled: disabled_0,
15559
- property: property_0,
15560
- includeDescription: property_0.description || property_0.longDescription,
15558
+ property,
15559
+ includeDescription: property.description || property.longDescription,
15561
15560
  underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
15562
15561
  context: formContext,
15563
15562
  partOfArray: false,
@@ -15613,7 +15612,7 @@
15613
15612
  }
15614
15613
  const dialogActions = /* @__PURE__ */ jsxRuntime.jsx(EntityFormActionsComponent, { collection: resolvedCollection, path, fullPath: path, fullIdPath, entity, layout: forceActionsAtTheBottom ? "bottom" : "side", savingError, formex: formex$1, disabled: actionsDisabled, status, pluginActions: pluginActions ?? [], openEntityMode, showDefaultActions, navigateBack, formContext });
15615
15614
  return /* @__PURE__ */ jsxRuntime.jsx(formex.Formex, { value: formex$1, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: formex$1.handleSubmit, onReset: () => formex$1.resetForm({
15616
- values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs)
15615
+ values: baseInitialValues
15617
15616
  }), noValidate: true, className: ui.cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15618
15617
  /* @__PURE__ */ jsxRuntime.jsx("div", { id: `form_${path}`, className: ui.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__ */ jsxRuntime.jsxs("div", { className: ui.cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10"), children: [
15619
15618
  formex$1.dirty ? /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { title: "Local unsaved changes", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Chip, { size: "small", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsxRuntime.jsx(ui.EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Tooltip, { title: "In sync with the database", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Chip, { size: "small", children: /* @__PURE__ */ jsxRuntime.jsx(ui.CheckIcon, { size: "smallest" }) }) }),
@@ -22826,14 +22825,14 @@
22826
22825
  collection,
22827
22826
  context,
22828
22827
  sideEntityController,
22829
- isSubmitting: formex2.isSubmitting,
22830
22828
  disabled,
22831
22829
  status,
22832
22830
  sideDialogContext,
22833
22831
  pluginActions,
22834
22832
  openEntityMode,
22835
22833
  navigateBack,
22836
- formContext
22834
+ formContext,
22835
+ formex: formex2
22837
22836
  }) : buildSideActions({
22838
22837
  savingError,
22839
22838
  entity,
@@ -22841,14 +22840,14 @@
22841
22840
  collection,
22842
22841
  context,
22843
22842
  sideEntityController,
22844
- isSubmitting: formex2.isSubmitting,
22845
22843
  sideDialogContext,
22846
22844
  disabled,
22847
22845
  status,
22848
22846
  pluginActions,
22849
22847
  openEntityMode,
22850
22848
  navigateBack,
22851
- formContext
22849
+ formContext,
22850
+ formex: formex2
22852
22851
  });
22853
22852
  }
22854
22853
  function buildBottomActions({
@@ -22858,15 +22857,16 @@
22858
22857
  collection,
22859
22858
  context,
22860
22859
  sideEntityController,
22861
- isSubmitting,
22862
22860
  disabled,
22863
22861
  status,
22864
22862
  sideDialogContext,
22865
22863
  pluginActions,
22866
22864
  openEntityMode,
22867
22865
  navigateBack,
22868
- formContext
22866
+ formContext,
22867
+ formex: formex2
22869
22868
  }) {
22869
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
22870
22870
  const canClose = openEntityMode === "side_panel";
22871
22871
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogActions, { position: "absolute", children: [
22872
22872
  savingError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { color: "error", children: savingError.message }) }),
@@ -22886,15 +22886,16 @@
22886
22886
  return /* @__PURE__ */ jsxRuntime.jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22887
22887
  }) }),
22888
22888
  pluginActions,
22889
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", color: "primary", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22890
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, onClick: () => {
22889
+ hasErrors ? /* @__PURE__ */ jsxRuntime.jsx(ErrorTooltip, { title: "This form has errors", children: /* @__PURE__ */ jsxRuntime.jsx(ui.ErrorIcon, { className: "ml-4", color: "error", size: "smallest" }) }) : null,
22890
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", color: "primary", disabled: disabled || formex2.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22891
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || formex2.isSubmitting, onClick: () => {
22891
22892
  sideDialogContext.setPendingClose(false);
22892
22893
  }, children: [
22893
22894
  status === "existing" && "Save",
22894
22895
  status === "copy" && "Create copy",
22895
22896
  status === "new" && "Create"
22896
22897
  ] }),
22897
- canClose && /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: isSubmitting, disabled, onClick: () => {
22898
+ canClose && /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: formex2.isSubmitting, disabled, onClick: () => {
22898
22899
  sideDialogContext.setPendingClose?.(true);
22899
22900
  }, children: [
22900
22901
  status === "existing" && "Save and close",
@@ -22910,24 +22911,25 @@
22910
22911
  collection,
22911
22912
  context,
22912
22913
  sideEntityController,
22913
- isSubmitting,
22914
22914
  disabled,
22915
22915
  status,
22916
22916
  sideDialogContext,
22917
22917
  pluginActions,
22918
22918
  openEntityMode,
22919
22919
  navigateBack,
22920
- formContext
22920
+ formContext,
22921
+ formex: formex2
22921
22922
  }) {
22923
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
22922
22924
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cls("overflow-auto h-full flex flex-col gap-2 w-80 2xl:w-96 px-4 py-16 sticky top-0 border-l", ui.defaultBorderMixin), children: [
22923
- /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", disabled: disabled || isSubmitting, onClick: () => {
22925
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { fullWidth: true, variant: "filled", color: "primary", type: "submit", size: "large", startIcon: hasErrors ? /* @__PURE__ */ jsxRuntime.jsx(ui.ErrorIcon, {}) : void 0, disabled: disabled || formex2.isSubmitting, onClick: () => {
22924
22926
  sideDialogContext.setPendingClose?.(false);
22925
22927
  }, children: [
22926
22928
  status === "existing" && "Save",
22927
22929
  status === "copy" && "Create copy",
22928
22930
  status === "new" && "Create"
22929
22931
  ] }),
22930
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22932
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || formex2.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22931
22933
  pluginActions,
22932
22934
  formActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-row flex-wrap mt-2", children: formActions.map((action) => {
22933
22935
  const props = {
@@ -23155,7 +23157,8 @@
23155
23157
  databaseId: props.databaseId,
23156
23158
  useCache: false
23157
23159
  });
23158
- const cachedValues = entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new");
23160
+ const enableLocalChangesBackup = props.collection.enableLocalChangesBackup !== void 0 ? props.collection.enableLocalChangesBackup : true;
23161
+ const initialDirtyValues = entityId ? getEntityFromCache(props.path + "/" + entityId, enableLocalChangesBackup) : getEntityFromCache(props.path + "#new", enableLocalChangesBackup);
23159
23162
  const authController = useAuthController();
23160
23163
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23161
23164
  const [status, setStatus] = React.useState(initialStatus);
@@ -23166,13 +23169,13 @@
23166
23169
  return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : void 0;
23167
23170
  }
23168
23171
  }, [authController, entity, status]);
23169
- if (dataLoading && !cachedValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23172
+ if (dataLoading && !initialDirtyValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23170
23173
  return /* @__PURE__ */ jsxRuntime.jsx(CircularProgressCenter, {});
23171
23174
  }
23172
- if (entityId && !entity && !cachedValues) {
23175
+ if (entityId && !entity && !initialDirtyValues) {
23173
23176
  console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
23174
23177
  }
23175
- return /* @__PURE__ */ jsxRuntime.jsx(EntityEditViewInner, { ...props, entityId, entity, cachedDirtyValues: cachedValues, dataLoading, status, setStatus, canEdit });
23178
+ return /* @__PURE__ */ jsxRuntime.jsx(EntityEditViewInner, { ...props, entityId, entity, initialDirtyValues, dataLoading, status, setStatus, canEdit });
23176
23179
  }
23177
23180
  function EntityEditViewInner({
23178
23181
  path,
@@ -23185,7 +23188,7 @@
23185
23188
  onSaved,
23186
23189
  onTabChange,
23187
23190
  entity,
23188
- cachedDirtyValues,
23191
+ initialDirtyValues,
23189
23192
  dataLoading,
23190
23193
  layout = "side_panel",
23191
23194
  barActions,
@@ -23313,7 +23316,7 @@
23313
23316
  /* @__PURE__ */ jsxRuntime.jsx(EntityView, { className: "px-8 h-full overflow-auto", entity, path, collection }),
23314
23317
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-16" })
23315
23318
  ] }) }) : null;
23316
- const entityView = /* @__PURE__ */ jsxRuntime.jsx(EntityForm, { fullIdPath, collection, path, entityId: entityId ?? usedEntity?.id, onValuesModified, entity, initialDirtyValues: cachedDirtyValues, openEntityMode: layout, forceActionsAtTheBottom: actionsAtTheBottom, initialStatus: status, className: ui.cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className), EntityFormActionsComponent: EntityEditViewFormActions, disabled: !canEdit, ...formProps, onEntityChange: (entity_0) => {
23319
+ const entityView = /* @__PURE__ */ jsxRuntime.jsx(EntityForm, { fullIdPath, collection, path, entityId: entityId ?? usedEntity?.id, onValuesModified, entity, initialDirtyValues, openEntityMode: layout, forceActionsAtTheBottom: actionsAtTheBottom, initialStatus: status, className: ui.cls((!mainViewVisible || !canEdit) && !selectedSecondaryForm ? "hidden" : "", formProps?.className), EntityFormActionsComponent: EntityEditViewFormActions, disabled: !canEdit, ...formProps, onEntityChange: (entity_0) => {
23317
23320
  setUsedEntity(entity_0);
23318
23321
  formProps?.onEntityChange?.(entity_0);
23319
23322
  }, onStatusChange: (status_0) => {