@firecms/core 3.0.0-canary.283 → 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.umd.js CHANGED
@@ -14987,61 +14987,60 @@
14987
14987
  ] });
14988
14988
  }
14989
14989
  const ErrorFocus = (t0) => {
14990
- const $ = reactCompilerRuntime.c(6);
14990
+ const $ = reactCompilerRuntime.c(10);
14991
14991
  const {
14992
14992
  containerRef
14993
14993
  } = t0;
14994
14994
  const {
14995
- isSubmitting,
14996
14995
  isValidating,
14997
- errors
14996
+ errors,
14997
+ version
14998
14998
  } = formex.useFormex();
14999
+ const prevVersion = React.useRef(version);
14999
15000
  let t1;
15000
- let t2;
15001
- if ($[0] !== containerRef || $[1] !== errors || $[2] !== isSubmitting || $[3] !== isValidating) {
15001
+ if ($[0] !== containerRef?.current || $[1] !== errors || $[2] !== isValidating || $[3] !== version) {
15002
15002
  t1 = () => {
15003
+ if (version === prevVersion.current) {
15004
+ return;
15005
+ }
15003
15006
  const keys = Object.keys(errors);
15004
- if (keys.length > 0 && isSubmitting && !isValidating) {
15007
+ if (!isValidating && keys.length > 0) {
15005
15008
  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
- }
15009
+ if (errorElement) {
15010
+ errorElement.scrollIntoView({
15011
+ behavior: "smooth",
15012
+ block: "center"
15013
+ });
15015
15014
  const input = errorElement.querySelector("input");
15016
15015
  if (input) {
15017
15016
  input.focus();
15018
15017
  }
15019
15018
  }
15019
+ prevVersion.current = version;
15020
15020
  }
15021
15021
  };
15022
- t2 = [isSubmitting, isValidating, errors, containerRef];
15023
- $[0] = containerRef;
15022
+ $[0] = containerRef?.current;
15024
15023
  $[1] = errors;
15025
- $[2] = isSubmitting;
15026
- $[3] = isValidating;
15024
+ $[2] = isValidating;
15025
+ $[3] = version;
15027
15026
  $[4] = t1;
15028
- $[5] = t2;
15029
15027
  } else {
15030
15028
  t1 = $[4];
15031
- t2 = $[5];
15029
+ }
15030
+ let t2;
15031
+ if ($[5] !== containerRef || $[6] !== errors || $[7] !== isValidating || $[8] !== version) {
15032
+ t2 = [isValidating, errors, containerRef, version];
15033
+ $[5] = containerRef;
15034
+ $[6] = errors;
15035
+ $[7] = isValidating;
15036
+ $[8] = version;
15037
+ $[9] = t2;
15038
+ } else {
15039
+ t2 = $[9];
15032
15040
  }
15033
15041
  React.useEffect(t1, t2);
15034
15042
  return null;
15035
15043
  };
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
15044
  function EntityFormActions(t0) {
15046
15045
  const $ = reactCompilerRuntime.c(16);
15047
15046
  const {
@@ -15062,7 +15061,7 @@
15062
15061
  const context = useFireCMSContext();
15063
15062
  const sideEntityController = useSideEntityController();
15064
15063
  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) {
15064
+ 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
15065
  t1 = layout === "bottom" ? buildBottomActions$1({
15067
15066
  fullPath,
15068
15067
  fullIdPath,
@@ -15071,13 +15070,13 @@
15071
15070
  collection,
15072
15071
  context,
15073
15072
  sideEntityController,
15074
- isSubmitting: formex2.isSubmitting,
15075
15073
  disabled,
15076
15074
  status,
15077
15075
  pluginActions,
15078
15076
  openEntityMode,
15079
15077
  navigateBack,
15080
- formContext
15078
+ formContext,
15079
+ formex: formex2
15081
15080
  }) : buildSideActions$1({
15082
15081
  fullPath,
15083
15082
  fullIdPath,
@@ -15086,18 +15085,18 @@
15086
15085
  collection,
15087
15086
  context,
15088
15087
  sideEntityController,
15089
- isSubmitting: formex2.isSubmitting,
15090
15088
  disabled,
15091
15089
  status,
15092
15090
  pluginActions,
15093
- openEntityMode
15091
+ openEntityMode,
15092
+ formex: formex2
15094
15093
  });
15095
15094
  $[0] = collection;
15096
15095
  $[1] = context;
15097
15096
  $[2] = disabled;
15098
15097
  $[3] = entity;
15099
15098
  $[4] = formContext;
15100
- $[5] = formex2.isSubmitting;
15099
+ $[5] = formex2;
15101
15100
  $[6] = fullIdPath;
15102
15101
  $[7] = fullPath;
15103
15102
  $[8] = layout;
@@ -15122,14 +15121,15 @@
15122
15121
  collection,
15123
15122
  context,
15124
15123
  sideEntityController,
15125
- isSubmitting,
15126
15124
  disabled,
15127
15125
  status,
15128
15126
  pluginActions,
15129
15127
  openEntityMode,
15130
15128
  navigateBack,
15131
- formContext
15129
+ formContext,
15130
+ formex: formex2
15132
15131
  }) {
15132
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
15133
15133
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogActions, { position: "absolute", children: [
15134
15134
  savingError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { color: "error", children: savingError.message }) }),
15135
15135
  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 +15148,8 @@
15148
15148
  });
15149
15149
  }, children: action.icon }, action.name)) }),
15150
15150
  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: [
15151
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", disabled: disabled || formex2.isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15152
+ /* @__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
15153
  status === "existing" && "Save",
15154
15154
  status === "copy" && "Create copy",
15155
15155
  status === "new" && "Create"
@@ -15166,22 +15166,35 @@
15166
15166
  collection,
15167
15167
  context,
15168
15168
  sideEntityController,
15169
- isSubmitting,
15170
15169
  disabled,
15171
15170
  status,
15172
- pluginActions
15171
+ pluginActions,
15172
+ formex: formex2
15173
15173
  }) {
15174
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
15174
15175
  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: [
15176
+ /* @__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
15177
  status === "existing" && "Save",
15177
15178
  status === "copy" && "Create copy",
15178
15179
  status === "new" && "Create"
15179
15180
  ] }),
15180
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15181
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || formex2.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15181
15182
  pluginActions,
15182
15183
  savingError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { color: "error", children: savingError.message }) })
15183
15184
  ] });
15184
15185
  }
15186
+ function extractTouchedValues(values, touched) {
15187
+ let acc = {};
15188
+ if (!touched || typeof touched !== "object") {
15189
+ return acc;
15190
+ }
15191
+ Object.entries(touched).forEach(([key, value]) => {
15192
+ if (value) {
15193
+ acc = formex.setIn(acc, key, formex.getIn(values, key));
15194
+ }
15195
+ });
15196
+ return acc;
15197
+ }
15185
15198
  function EntityForm({
15186
15199
  path,
15187
15200
  fullIdPath,
@@ -15239,7 +15252,7 @@
15239
15252
  const customizationController = useCustomizationController();
15240
15253
  const context = useFireCMSContext();
15241
15254
  const analyticsController = useAnalyticsController();
15242
- const [underlyingChanges, setUnderlyingChanges] = React.useState({});
15255
+ const [underlyingChanges] = React.useState({});
15243
15256
  const [customIdLoading, setCustomIdLoading] = React.useState(false);
15244
15257
  const mustSetCustomId = (status === "new" || status === "copy") && Boolean(collection.customId) && collection.customId !== "optional";
15245
15258
  const initialEntityId = React.useMemo(() => {
@@ -15287,16 +15300,30 @@
15287
15300
  formexController.setSubmitting(false);
15288
15301
  });
15289
15302
  };
15303
+ const baseInitialValues = getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs);
15304
+ const initialValues = initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues;
15305
+ const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15290
15306
  const formex$1 = formexProp ?? formex.useCreateFormex({
15291
- initialValues: initialDirtyValues ?? getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs),
15292
- initialDirty: Boolean(initialDirtyValues),
15307
+ initialValues,
15308
+ initialDirty,
15309
+ initialTouched: initialDirtyValues ? formex.flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
15310
+ ...previousValue,
15311
+ [currentValue]: true
15312
+ }), {}) : {},
15293
15313
  onSubmit,
15294
15314
  onReset: () => {
15295
15315
  clearDirtyCache();
15296
15316
  onValuesModified?.(false);
15297
15317
  },
15298
- validation: (values_0) => {
15299
- return validationSchema?.validate(values_0, {
15318
+ onValuesChangeDeferred: (values_0, controller) => {
15319
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15320
+ if (controller.dirty) {
15321
+ const touchedValues = extractTouchedValues(values_0, controller.touched);
15322
+ saveEntityToCache(key, touchedValues);
15323
+ }
15324
+ },
15325
+ validation: (values_1) => {
15326
+ return validationSchema?.validate(values_1, {
15300
15327
  abortEarly: false
15301
15328
  }).then(() => {
15302
15329
  return {};
@@ -15379,7 +15406,7 @@
15379
15406
  console.error(e_3);
15380
15407
  }, [entityId, path, snackbarController]);
15381
15408
  const saveEntity = ({
15382
- values: values_1,
15409
+ values: values_2,
15383
15410
  previousValues,
15384
15411
  entityId: entityId_0,
15385
15412
  collection: collection_0,
@@ -15388,7 +15415,7 @@
15388
15415
  return saveEntityWithCallbacks({
15389
15416
  path: path_0,
15390
15417
  entityId: entityId_0,
15391
- values: values_1,
15418
+ values: values_2,
15392
15419
  previousValues,
15393
15420
  collection: collection_0,
15394
15421
  status,
@@ -15404,34 +15431,34 @@
15404
15431
  collection: collection_1,
15405
15432
  path: path_1,
15406
15433
  entityId: entityId_1,
15407
- values: values_2,
15434
+ values: values_3,
15408
15435
  previousValues: previousValues_0,
15409
15436
  autoSave: autoSave_0
15410
15437
  }) => {
15411
15438
  if (!status) return;
15412
15439
  if (autoSave_0) {
15413
- setValuesToBeSaved(values_2);
15440
+ setValuesToBeSaved(values_3);
15414
15441
  } else {
15415
15442
  return saveEntity({
15416
15443
  collection: collection_1,
15417
15444
  path: path_1,
15418
15445
  entityId: entityId_1,
15419
- values: values_2,
15446
+ values: values_3,
15420
15447
  previousValues: previousValues_0
15421
15448
  });
15422
15449
  }
15423
15450
  };
15424
15451
  const lastSavedValues = React.useRef(entity?.values);
15425
- const save = (values_3) => {
15426
- lastSavedValues.current = values_3;
15452
+ const save = (values_4) => {
15453
+ lastSavedValues.current = values_4;
15427
15454
  return onSaveEntityRequest({
15428
15455
  collection: resolvedCollection,
15429
15456
  path,
15430
15457
  entityId,
15431
- values: values_3,
15458
+ values: values_4,
15432
15459
  previousValues: entity?.values,
15433
15460
  autoSave: autoSave ?? false
15434
- }).then((res) => {
15461
+ }).then(() => {
15435
15462
  const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
15436
15463
  analyticsController.onAnalyticsEvent?.(eventName, {
15437
15464
  path
@@ -15465,7 +15492,8 @@
15465
15492
  type: "error",
15466
15493
  message: "Error updating id, check the console"
15467
15494
  });
15468
- }, []);
15495
+ console.error(error);
15496
+ }, [snackbarController]);
15469
15497
  const pluginActions = [];
15470
15498
  const plugins = customizationController.plugins;
15471
15499
  const actionsDisabled = disabled || formex$1.isSubmitting || status === "existing" && !formex$1.dirty || Boolean(disabledProp);
@@ -15514,20 +15542,12 @@
15514
15542
  onValuesModified?.(modified);
15515
15543
  }
15516
15544
  }, [formex$1.dirty]);
15517
- const deferredValues = React.useDeferredValue(formex$1.values);
15518
15545
  const modified = formex$1.dirty;
15519
15546
  const uniqueFieldValidator = React.useCallback(({
15520
15547
  name,
15521
- value,
15522
- property
15548
+ value
15523
15549
  }) => dataSource.checkUniqueField(path, name, value, entityId, collection), [dataSource, path, entityId]);
15524
15550
  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
15551
  useOnAutoSave(autoSave, formex$1, lastSavedValues, save);
15532
15552
  React.useEffect(() => {
15533
15553
  if (!autoSave && !formex$1.isSubmitting && underlyingChanges && entity) {
@@ -15546,18 +15566,18 @@
15546
15566
  return /* @__PURE__ */ jsxRuntime.jsx(Builder, { collection, entity, modifiedValues: formex$1.values, formContext });
15547
15567
  }
15548
15568
  return /* @__PURE__ */ jsxRuntime.jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15549
- const property_0 = resolvedCollection.properties[key_1];
15550
- if (property_0) {
15569
+ const property = resolvedCollection.properties[key_1];
15570
+ if (property) {
15551
15571
  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);
15572
+ const disabled_0 = disabledProp || !autoSave && formex$1.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
15573
+ const hidden = isHidden(property);
15554
15574
  if (hidden) return null;
15555
- const widthPercentage = property_0.widthPercentage ?? 100;
15575
+ const widthPercentage = property.widthPercentage ?? 100;
15556
15576
  const cmsFormFieldProps = {
15557
15577
  propertyKey: key_1,
15558
15578
  disabled: disabled_0,
15559
- property: property_0,
15560
- includeDescription: property_0.description || property_0.longDescription,
15579
+ property,
15580
+ includeDescription: property.description || property.longDescription,
15561
15581
  underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
15562
15582
  context: formContext,
15563
15583
  partOfArray: false,
@@ -15613,7 +15633,7 @@
15613
15633
  }
15614
15634
  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
15635
  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)
15636
+ values: baseInitialValues
15617
15637
  }), noValidate: true, className: ui.cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15618
15638
  /* @__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
15639
  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 +22846,14 @@
22826
22846
  collection,
22827
22847
  context,
22828
22848
  sideEntityController,
22829
- isSubmitting: formex2.isSubmitting,
22830
22849
  disabled,
22831
22850
  status,
22832
22851
  sideDialogContext,
22833
22852
  pluginActions,
22834
22853
  openEntityMode,
22835
22854
  navigateBack,
22836
- formContext
22855
+ formContext,
22856
+ formex: formex2
22837
22857
  }) : buildSideActions({
22838
22858
  savingError,
22839
22859
  entity,
@@ -22841,14 +22861,14 @@
22841
22861
  collection,
22842
22862
  context,
22843
22863
  sideEntityController,
22844
- isSubmitting: formex2.isSubmitting,
22845
22864
  sideDialogContext,
22846
22865
  disabled,
22847
22866
  status,
22848
22867
  pluginActions,
22849
22868
  openEntityMode,
22850
22869
  navigateBack,
22851
- formContext
22870
+ formContext,
22871
+ formex: formex2
22852
22872
  });
22853
22873
  }
22854
22874
  function buildBottomActions({
@@ -22858,15 +22878,16 @@
22858
22878
  collection,
22859
22879
  context,
22860
22880
  sideEntityController,
22861
- isSubmitting,
22862
22881
  disabled,
22863
22882
  status,
22864
22883
  sideDialogContext,
22865
22884
  pluginActions,
22866
22885
  openEntityMode,
22867
22886
  navigateBack,
22868
- formContext
22887
+ formContext,
22888
+ formex: formex2
22869
22889
  }) {
22890
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
22870
22891
  const canClose = openEntityMode === "side_panel";
22871
22892
  return /* @__PURE__ */ jsxRuntime.jsxs(ui.DialogActions, { position: "absolute", children: [
22872
22893
  savingError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-right", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Typography, { color: "error", children: savingError.message }) }),
@@ -22886,15 +22907,16 @@
22886
22907
  return /* @__PURE__ */ jsxRuntime.jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22887
22908
  }) }),
22888
22909
  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: () => {
22910
+ hasErrors ? /* @__PURE__ */ jsxRuntime.jsx(ErrorTooltip, { title: "This form has errors", children: /* @__PURE__ */ jsxRuntime.jsx(ui.ErrorIcon, { className: "ml-4", color: "error", size: "smallest" }) }) : null,
22911
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "text", color: "primary", disabled: disabled || formex2.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22912
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: canClose ? "text" : "filled", color: "primary", type: "submit", disabled: disabled || formex2.isSubmitting, onClick: () => {
22891
22913
  sideDialogContext.setPendingClose(false);
22892
22914
  }, children: [
22893
22915
  status === "existing" && "Save",
22894
22916
  status === "copy" && "Create copy",
22895
22917
  status === "new" && "Create"
22896
22918
  ] }),
22897
- canClose && /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: isSubmitting, disabled, onClick: () => {
22919
+ canClose && /* @__PURE__ */ jsxRuntime.jsxs(ui.LoadingButton, { variant: "filled", color: "primary", type: "submit", loading: formex2.isSubmitting, disabled, onClick: () => {
22898
22920
  sideDialogContext.setPendingClose?.(true);
22899
22921
  }, children: [
22900
22922
  status === "existing" && "Save and close",
@@ -22910,24 +22932,25 @@
22910
22932
  collection,
22911
22933
  context,
22912
22934
  sideEntityController,
22913
- isSubmitting,
22914
22935
  disabled,
22915
22936
  status,
22916
22937
  sideDialogContext,
22917
22938
  pluginActions,
22918
22939
  openEntityMode,
22919
22940
  navigateBack,
22920
- formContext
22941
+ formContext,
22942
+ formex: formex2
22921
22943
  }) {
22944
+ const hasErrors = Object.keys(formex2.errors).length > 0 && formex2.submitCount > 0;
22922
22945
  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: () => {
22946
+ /* @__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
22947
  sideDialogContext.setPendingClose?.(false);
22925
22948
  }, children: [
22926
22949
  status === "existing" && "Save",
22927
22950
  status === "copy" && "Create copy",
22928
22951
  status === "new" && "Create"
22929
22952
  ] }),
22930
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22953
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { fullWidth: true, variant: "text", disabled: disabled || formex2.isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22931
22954
  pluginActions,
22932
22955
  formActions.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-row flex-wrap mt-2", children: formActions.map((action) => {
22933
22956
  const props = {
@@ -23155,7 +23178,8 @@
23155
23178
  databaseId: props.databaseId,
23156
23179
  useCache: false
23157
23180
  });
23158
- const cachedValues = entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new");
23181
+ const enableLocalChangesBackup = props.collection.enableLocalChangesBackup !== void 0 ? props.collection.enableLocalChangesBackup : true;
23182
+ const initialDirtyValues = enableLocalChangesBackup ? entityId ? getEntityFromCache(props.path + "/" + entityId) : getEntityFromCache(props.path + "#new") : void 0;
23159
23183
  const authController = useAuthController();
23160
23184
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23161
23185
  const [status, setStatus] = React.useState(initialStatus);
@@ -23166,13 +23190,13 @@
23166
23190
  return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : void 0;
23167
23191
  }
23168
23192
  }, [authController, entity, status]);
23169
- if (dataLoading && !cachedValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23193
+ if (dataLoading && !initialDirtyValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23170
23194
  return /* @__PURE__ */ jsxRuntime.jsx(CircularProgressCenter, {});
23171
23195
  }
23172
- if (entityId && !entity && !cachedValues) {
23196
+ if (entityId && !entity && !initialDirtyValues) {
23173
23197
  console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
23174
23198
  }
23175
- return /* @__PURE__ */ jsxRuntime.jsx(EntityEditViewInner, { ...props, entityId, entity, cachedDirtyValues: cachedValues, dataLoading, status, setStatus, canEdit });
23199
+ return /* @__PURE__ */ jsxRuntime.jsx(EntityEditViewInner, { ...props, entityId, entity, initialDirtyValues, dataLoading, status, setStatus, canEdit });
23176
23200
  }
23177
23201
  function EntityEditViewInner({
23178
23202
  path,
@@ -23185,7 +23209,7 @@
23185
23209
  onSaved,
23186
23210
  onTabChange,
23187
23211
  entity,
23188
- cachedDirtyValues,
23212
+ initialDirtyValues,
23189
23213
  dataLoading,
23190
23214
  layout = "side_panel",
23191
23215
  barActions,
@@ -23313,7 +23337,7 @@
23313
23337
  /* @__PURE__ */ jsxRuntime.jsx(EntityView, { className: "px-8 h-full overflow-auto", entity, path, collection }),
23314
23338
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-16" })
23315
23339
  ] }) }) : 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) => {
23340
+ 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
23341
  setUsedEntity(entity_0);
23318
23342
  formProps?.onEntityChange?.(entity_0);
23319
23343
  }, onStatusChange: (status_0) => {