@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.
@@ -44,9 +44,9 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
44
44
  * an entity is opened.
45
45
  */
46
46
  export declare function EntityEditView<M extends Record<string, any>, USER extends User>({ entityId, ...props }: EntityEditViewProps<M>): import("react/jsx-runtime").JSX.Element;
47
- export declare function EntityEditViewInner<M extends Record<string, any>>({ path, fullIdPath, entityId, selectedTab: selectedTabProp, collection, parentCollectionIds, onValuesModified, onSaved, onTabChange, entity, cachedDirtyValues, dataLoading, layout, barActions, status, setStatus, formProps, canEdit }: EntityEditViewProps<M> & {
47
+ export declare function EntityEditViewInner<M extends Record<string, any>>({ path, fullIdPath, entityId, selectedTab: selectedTabProp, collection, parentCollectionIds, onValuesModified, onSaved, onTabChange, entity, initialDirtyValues, dataLoading, layout, barActions, status, setStatus, formProps, canEdit }: EntityEditViewProps<M> & {
48
48
  entity?: Entity<M>;
49
- cachedDirtyValues?: Partial<M>;
49
+ initialDirtyValues?: Partial<M>;
50
50
  dataLoading: boolean;
51
51
  status: EntityStatus;
52
52
  setStatus: (status: EntityStatus) => void;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Entity, EntityCollection, EntityCustomViewParams, EntityStatus, FormContext } from "../types";
2
+ import { AuthController, Entity, EntityCollection, EntityCustomViewParams, EntityStatus, EntityValues, FormContext, PropertyConfig } from "../types";
3
3
  import { FormexController } from "@firecms/formex";
4
4
  import { ValidationError } from "yup";
5
5
  import { EntityFormActionsProps } from "./EntityFormActions";
@@ -46,5 +46,7 @@ export type EntityFormProps<M extends Record<string, any>> = {
46
46
  Builder?: React.ComponentType<EntityCustomViewParams<M>>;
47
47
  children?: React.ReactNode;
48
48
  };
49
+ export declare function extractTouchedValues(values: any, touched: Record<string, boolean>): Record<string, any>;
49
50
  export declare function EntityForm<M extends Record<string, any>>({ path, fullIdPath, entityId: entityIdProp, collection, onValuesModified, onIdChange, onSaved, entity, initialDirtyValues, onFormContextReady, forceActionsAtTheBottom, initialStatus, className, onStatusChange, onEntityChange, openEntityMode, formex: formexProp, disabled: disabledProp, Builder, EntityFormActionsComponent, showDefaultActions, showEntityPath, children }: EntityFormProps<M>): import("react/jsx-runtime").JSX.Element;
51
+ export declare function getInitialEntityValues<M extends object>(authController: AuthController, collection: EntityCollection, path: string, status: "new" | "existing" | "copy", entity: Entity<M> | undefined, propertyConfigs?: Record<string, PropertyConfig>): Partial<EntityValues<M>>;
50
52
  export declare function yupToFormErrors(yupError: ValidationError): Record<string, any>;
@@ -1,4 +1,5 @@
1
- export * from "./EntityForm";
1
+ export { EntityForm, yupToFormErrors, } from "./EntityForm";
2
+ export type { EntityFormProps } from "./EntityForm";
2
3
  export { SelectFieldBinding } from "./field_bindings/SelectFieldBinding";
3
4
  export { MultiSelectFieldBinding } from "./field_bindings/MultiSelectFieldBinding";
4
5
  export { ArrayOfReferencesFieldBinding } from "./field_bindings/ArrayOfReferencesFieldBinding";
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";
@@ -14989,61 +14989,60 @@ function CustomIdField({
14989
14989
  ] });
14990
14990
  }
14991
14991
  const ErrorFocus = (t0) => {
14992
- const $ = c(6);
14992
+ const $ = c(10);
14993
14993
  const {
14994
14994
  containerRef
14995
14995
  } = t0;
14996
14996
  const {
14997
- isSubmitting,
14998
14997
  isValidating,
14999
- errors
14998
+ errors,
14999
+ version
15000
15000
  } = useFormex();
15001
+ const prevVersion = useRef(version);
15001
15002
  let t1;
15002
- let t2;
15003
- if ($[0] !== containerRef || $[1] !== errors || $[2] !== isSubmitting || $[3] !== isValidating) {
15003
+ if ($[0] !== containerRef?.current || $[1] !== errors || $[2] !== isValidating || $[3] !== version) {
15004
15004
  t1 = () => {
15005
+ if (version === prevVersion.current) {
15006
+ return;
15007
+ }
15005
15008
  const keys = Object.keys(errors);
15006
- if (keys.length > 0 && isSubmitting && !isValidating) {
15009
+ if (!isValidating && keys.length > 0) {
15007
15010
  const errorElement = containerRef?.current?.querySelector(`#form_field_${keys[0]}`);
15008
- if (errorElement && containerRef?.current) {
15009
- const scrollableParent = getScrollableParent(containerRef.current);
15010
- if (scrollableParent) {
15011
- const top = errorElement.getBoundingClientRect().top;
15012
- scrollableParent.scrollTo({
15013
- top: scrollableParent.scrollTop + top - 196,
15014
- behavior: "smooth"
15015
- });
15016
- }
15011
+ if (errorElement) {
15012
+ errorElement.scrollIntoView({
15013
+ behavior: "smooth",
15014
+ block: "center"
15015
+ });
15017
15016
  const input = errorElement.querySelector("input");
15018
15017
  if (input) {
15019
15018
  input.focus();
15020
15019
  }
15021
15020
  }
15021
+ prevVersion.current = version;
15022
15022
  }
15023
15023
  };
15024
- t2 = [isSubmitting, isValidating, errors, containerRef];
15025
- $[0] = containerRef;
15024
+ $[0] = containerRef?.current;
15026
15025
  $[1] = errors;
15027
- $[2] = isSubmitting;
15028
- $[3] = isValidating;
15026
+ $[2] = isValidating;
15027
+ $[3] = version;
15029
15028
  $[4] = t1;
15030
- $[5] = t2;
15031
15029
  } else {
15032
15030
  t1 = $[4];
15033
- 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];
15034
15042
  }
15035
15043
  useEffect(t1, t2);
15036
15044
  return null;
15037
15045
  };
15038
- const isScrollable = (ele) => {
15039
- const hasScrollableContent = ele && ele.scrollHeight > ele.clientHeight;
15040
- const overflowYStyle = ele ? window.getComputedStyle(ele).overflowY : null;
15041
- const isOverflowHidden = overflowYStyle && overflowYStyle.indexOf("hidden") !== -1;
15042
- return hasScrollableContent && !isOverflowHidden;
15043
- };
15044
- const getScrollableParent = (ele) => {
15045
- return !ele || ele === document.body ? document.body : isScrollable(ele) ? ele : getScrollableParent(ele.parentNode);
15046
- };
15047
15046
  function EntityFormActions(t0) {
15048
15047
  const $ = c(16);
15049
15048
  const {
@@ -15064,7 +15063,7 @@ function EntityFormActions(t0) {
15064
15063
  const context = useFireCMSContext();
15065
15064
  const sideEntityController = useSideEntityController();
15066
15065
  let t1;
15067
- if ($[0] !== collection || $[1] !== context || $[2] !== disabled || $[3] !== entity || $[4] !== formContext || $[5] !== formex.isSubmitting || $[6] !== fullIdPath || $[7] !== fullPath || $[8] !== layout || $[9] !== navigateBack || $[10] !== openEntityMode || $[11] !== pluginActions || $[12] !== savingError || $[13] !== sideEntityController || $[14] !== status) {
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) {
15068
15067
  t1 = layout === "bottom" ? buildBottomActions$1({
15069
15068
  fullPath,
15070
15069
  fullIdPath,
@@ -15073,13 +15072,13 @@ function EntityFormActions(t0) {
15073
15072
  collection,
15074
15073
  context,
15075
15074
  sideEntityController,
15076
- isSubmitting: formex.isSubmitting,
15077
15075
  disabled,
15078
15076
  status,
15079
15077
  pluginActions,
15080
15078
  openEntityMode,
15081
15079
  navigateBack,
15082
- formContext
15080
+ formContext,
15081
+ formex
15083
15082
  }) : buildSideActions$1({
15084
15083
  fullPath,
15085
15084
  fullIdPath,
@@ -15088,18 +15087,18 @@ function EntityFormActions(t0) {
15088
15087
  collection,
15089
15088
  context,
15090
15089
  sideEntityController,
15091
- isSubmitting: formex.isSubmitting,
15092
15090
  disabled,
15093
15091
  status,
15094
15092
  pluginActions,
15095
- openEntityMode
15093
+ openEntityMode,
15094
+ formex
15096
15095
  });
15097
15096
  $[0] = collection;
15098
15097
  $[1] = context;
15099
15098
  $[2] = disabled;
15100
15099
  $[3] = entity;
15101
15100
  $[4] = formContext;
15102
- $[5] = formex.isSubmitting;
15101
+ $[5] = formex;
15103
15102
  $[6] = fullIdPath;
15104
15103
  $[7] = fullPath;
15105
15104
  $[8] = layout;
@@ -15124,14 +15123,15 @@ function buildBottomActions$1({
15124
15123
  collection,
15125
15124
  context,
15126
15125
  sideEntityController,
15127
- isSubmitting,
15128
15126
  disabled,
15129
15127
  status,
15130
15128
  pluginActions,
15131
15129
  openEntityMode,
15132
15130
  navigateBack,
15133
- formContext
15131
+ formContext,
15132
+ formex
15134
15133
  }) {
15134
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
15135
15135
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
15136
15136
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
15137
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) => {
@@ -15150,8 +15150,8 @@ function buildBottomActions$1({
15150
15150
  });
15151
15151
  }, children: action.icon }, action.name)) }),
15152
15152
  pluginActions,
15153
- /* @__PURE__ */ jsx(Button, { variant: "text", disabled: disabled || isSubmitting, color: "primary", type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
15154
- /* @__PURE__ */ jsxs(Button, { variant: "filled", color: "primary", type: "submit", disabled: disabled || isSubmitting, children: [
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: [
15155
15155
  status === "existing" && "Save",
15156
15156
  status === "copy" && "Create copy",
15157
15157
  status === "new" && "Create"
@@ -15168,22 +15168,35 @@ function buildSideActions$1({
15168
15168
  collection,
15169
15169
  context,
15170
15170
  sideEntityController,
15171
- isSubmitting,
15172
15171
  disabled,
15173
15172
  status,
15174
- pluginActions
15173
+ pluginActions,
15174
+ formex
15175
15175
  }) {
15176
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
15176
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: [
15177
- /* @__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: [
15178
15179
  status === "existing" && "Save",
15179
15180
  status === "copy" && "Create copy",
15180
15181
  status === "new" && "Create"
15181
15182
  ] }),
15182
- /* @__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" }),
15183
15184
  pluginActions,
15184
15185
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) })
15185
15186
  ] });
15186
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
+ }
15187
15200
  function EntityForm({
15188
15201
  path,
15189
15202
  fullIdPath,
@@ -15241,7 +15254,7 @@ function EntityForm({
15241
15254
  const customizationController = useCustomizationController();
15242
15255
  const context = useFireCMSContext();
15243
15256
  const analyticsController = useAnalyticsController();
15244
- const [underlyingChanges, setUnderlyingChanges] = useState({});
15257
+ const [underlyingChanges] = useState({});
15245
15258
  const [customIdLoading, setCustomIdLoading] = useState(false);
15246
15259
  const mustSetCustomId = (status === "new" || status === "copy") && Boolean(collection.customId) && collection.customId !== "optional";
15247
15260
  const initialEntityId = useMemo(() => {
@@ -15289,16 +15302,30 @@ function EntityForm({
15289
15302
  formexController.setSubmitting(false);
15290
15303
  });
15291
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;
15292
15308
  const formex = formexProp ?? useCreateFormex({
15293
- initialValues: initialDirtyValues ?? getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs),
15294
- initialDirty: Boolean(initialDirtyValues),
15309
+ initialValues,
15310
+ initialDirty,
15311
+ initialTouched: initialDirtyValues ? flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
15312
+ ...previousValue,
15313
+ [currentValue]: true
15314
+ }), {}) : {},
15295
15315
  onSubmit,
15296
15316
  onReset: () => {
15297
15317
  clearDirtyCache();
15298
15318
  onValuesModified?.(false);
15299
15319
  },
15300
- validation: (values_0) => {
15301
- 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, {
15302
15329
  abortEarly: false
15303
15330
  }).then(() => {
15304
15331
  return {};
@@ -15381,7 +15408,7 @@ function EntityForm({
15381
15408
  console.error(e_3);
15382
15409
  }, [entityId, path, snackbarController]);
15383
15410
  const saveEntity = ({
15384
- values: values_1,
15411
+ values: values_2,
15385
15412
  previousValues,
15386
15413
  entityId: entityId_0,
15387
15414
  collection: collection_0,
@@ -15390,7 +15417,7 @@ function EntityForm({
15390
15417
  return saveEntityWithCallbacks({
15391
15418
  path: path_0,
15392
15419
  entityId: entityId_0,
15393
- values: values_1,
15420
+ values: values_2,
15394
15421
  previousValues,
15395
15422
  collection: collection_0,
15396
15423
  status,
@@ -15406,34 +15433,34 @@ function EntityForm({
15406
15433
  collection: collection_1,
15407
15434
  path: path_1,
15408
15435
  entityId: entityId_1,
15409
- values: values_2,
15436
+ values: values_3,
15410
15437
  previousValues: previousValues_0,
15411
15438
  autoSave: autoSave_0
15412
15439
  }) => {
15413
15440
  if (!status) return;
15414
15441
  if (autoSave_0) {
15415
- setValuesToBeSaved(values_2);
15442
+ setValuesToBeSaved(values_3);
15416
15443
  } else {
15417
15444
  return saveEntity({
15418
15445
  collection: collection_1,
15419
15446
  path: path_1,
15420
15447
  entityId: entityId_1,
15421
- values: values_2,
15448
+ values: values_3,
15422
15449
  previousValues: previousValues_0
15423
15450
  });
15424
15451
  }
15425
15452
  };
15426
15453
  const lastSavedValues = useRef(entity?.values);
15427
- const save = (values_3) => {
15428
- lastSavedValues.current = values_3;
15454
+ const save = (values_4) => {
15455
+ lastSavedValues.current = values_4;
15429
15456
  return onSaveEntityRequest({
15430
15457
  collection: resolvedCollection,
15431
15458
  path,
15432
15459
  entityId,
15433
- values: values_3,
15460
+ values: values_4,
15434
15461
  previousValues: entity?.values,
15435
15462
  autoSave: autoSave ?? false
15436
- }).then((res) => {
15463
+ }).then(() => {
15437
15464
  const eventName = status === "new" ? "new_entity_saved" : status === "copy" ? "entity_copied" : status === "existing" ? "entity_edited" : "unmapped_event";
15438
15465
  analyticsController.onAnalyticsEvent?.(eventName, {
15439
15466
  path
@@ -15467,7 +15494,8 @@ function EntityForm({
15467
15494
  type: "error",
15468
15495
  message: "Error updating id, check the console"
15469
15496
  });
15470
- }, []);
15497
+ console.error(error);
15498
+ }, [snackbarController]);
15471
15499
  const pluginActions = [];
15472
15500
  const plugins = customizationController.plugins;
15473
15501
  const actionsDisabled = disabled || formex.isSubmitting || status === "existing" && !formex.dirty || Boolean(disabledProp);
@@ -15516,20 +15544,12 @@ function EntityForm({
15516
15544
  onValuesModified?.(modified);
15517
15545
  }
15518
15546
  }, [formex.dirty]);
15519
- const deferredValues = useDeferredValue(formex.values);
15520
15547
  const modified = formex.dirty;
15521
15548
  const uniqueFieldValidator = useCallback(({
15522
15549
  name,
15523
- value,
15524
- property
15550
+ value
15525
15551
  }) => dataSource.checkUniqueField(path, name, value, entityId, collection), [dataSource, path, entityId]);
15526
15552
  const validationSchema = useMemo(() => entityId ? getYupEntitySchema(entityId, resolvedCollection.properties, uniqueFieldValidator) : void 0, [entityId, resolvedCollection.properties, uniqueFieldValidator]);
15527
- useEffect(() => {
15528
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15529
- if (modified) {
15530
- saveEntityToCache(key, deferredValues);
15531
- }
15532
- }, [deferredValues, modified, path, entityId, status]);
15533
15553
  useOnAutoSave(autoSave, formex, lastSavedValues, save);
15534
15554
  useEffect(() => {
15535
15555
  if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
@@ -15548,18 +15568,18 @@ function EntityForm({
15548
15568
  return /* @__PURE__ */ jsx(Builder, { collection, entity, modifiedValues: formex.values, formContext });
15549
15569
  }
15550
15570
  return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15551
- const property_0 = resolvedCollection.properties[key_1];
15552
- if (property_0) {
15571
+ const property = resolvedCollection.properties[key_1];
15572
+ if (property) {
15553
15573
  const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_1) && formex.touched[key_1];
15554
- const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property_0) || Boolean(property_0.disabled);
15555
- const hidden = isHidden(property_0);
15574
+ const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
15575
+ const hidden = isHidden(property);
15556
15576
  if (hidden) return null;
15557
- const widthPercentage = property_0.widthPercentage ?? 100;
15577
+ const widthPercentage = property.widthPercentage ?? 100;
15558
15578
  const cmsFormFieldProps = {
15559
15579
  propertyKey: key_1,
15560
15580
  disabled: disabled_0,
15561
- property: property_0,
15562
- includeDescription: property_0.description || property_0.longDescription,
15581
+ property,
15582
+ includeDescription: property.description || property.longDescription,
15563
15583
  underlyingValueHasChanged: underlyingValueHasChanged && !autoSave,
15564
15584
  context: formContext,
15565
15585
  partOfArray: false,
@@ -15615,7 +15635,7 @@ function EntityForm({
15615
15635
  }
15616
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 });
15617
15637
  return /* @__PURE__ */ jsx(Formex, { value: formex, children: /* @__PURE__ */ jsxs("form", { onSubmit: formex.handleSubmit, onReset: () => formex.resetForm({
15618
- values: getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs)
15638
+ values: baseInitialValues
15619
15639
  }), noValidate: true, className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15620
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: [
15621
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" }) }) }),
@@ -22828,14 +22848,14 @@ function EntityEditViewFormActions({
22828
22848
  collection,
22829
22849
  context,
22830
22850
  sideEntityController,
22831
- isSubmitting: formex.isSubmitting,
22832
22851
  disabled,
22833
22852
  status,
22834
22853
  sideDialogContext,
22835
22854
  pluginActions,
22836
22855
  openEntityMode,
22837
22856
  navigateBack,
22838
- formContext
22857
+ formContext,
22858
+ formex
22839
22859
  }) : buildSideActions({
22840
22860
  savingError,
22841
22861
  entity,
@@ -22843,14 +22863,14 @@ function EntityEditViewFormActions({
22843
22863
  collection,
22844
22864
  context,
22845
22865
  sideEntityController,
22846
- isSubmitting: formex.isSubmitting,
22847
22866
  sideDialogContext,
22848
22867
  disabled,
22849
22868
  status,
22850
22869
  pluginActions,
22851
22870
  openEntityMode,
22852
22871
  navigateBack,
22853
- formContext
22872
+ formContext,
22873
+ formex
22854
22874
  });
22855
22875
  }
22856
22876
  function buildBottomActions({
@@ -22860,15 +22880,16 @@ function buildBottomActions({
22860
22880
  collection,
22861
22881
  context,
22862
22882
  sideEntityController,
22863
- isSubmitting,
22864
22883
  disabled,
22865
22884
  status,
22866
22885
  sideDialogContext,
22867
22886
  pluginActions,
22868
22887
  openEntityMode,
22869
22888
  navigateBack,
22870
- formContext
22889
+ formContext,
22890
+ formex
22871
22891
  }) {
22892
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22872
22893
  const canClose = openEntityMode === "side_panel";
22873
22894
  return /* @__PURE__ */ jsxs(DialogActions, { position: "absolute", children: [
22874
22895
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) }),
@@ -22888,15 +22909,16 @@ function buildBottomActions({
22888
22909
  return /* @__PURE__ */ jsx(EntityActionButton, { action, enabled: isEnabled, props }, action.key);
22889
22910
  }) }),
22890
22911
  pluginActions,
22891
- /* @__PURE__ */ jsx(Button, { variant: "text", color: "primary", disabled: disabled || isSubmitting, type: "reset", children: status === "existing" ? "Discard" : "Clear" }),
22892
- /* @__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: () => {
22893
22915
  sideDialogContext.setPendingClose(false);
22894
22916
  }, children: [
22895
22917
  status === "existing" && "Save",
22896
22918
  status === "copy" && "Create copy",
22897
22919
  status === "new" && "Create"
22898
22920
  ] }),
22899
- 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: () => {
22900
22922
  sideDialogContext.setPendingClose?.(true);
22901
22923
  }, children: [
22902
22924
  status === "existing" && "Save and close",
@@ -22912,24 +22934,25 @@ function buildSideActions({
22912
22934
  collection,
22913
22935
  context,
22914
22936
  sideEntityController,
22915
- isSubmitting,
22916
22937
  disabled,
22917
22938
  status,
22918
22939
  sideDialogContext,
22919
22940
  pluginActions,
22920
22941
  openEntityMode,
22921
22942
  navigateBack,
22922
- formContext
22943
+ formContext,
22944
+ formex
22923
22945
  }) {
22946
+ const hasErrors = Object.keys(formex.errors).length > 0 && formex.submitCount > 0;
22924
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: [
22925
- /* @__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: () => {
22926
22949
  sideDialogContext.setPendingClose?.(false);
22927
22950
  }, children: [
22928
22951
  status === "existing" && "Save",
22929
22952
  status === "copy" && "Create copy",
22930
22953
  status === "new" && "Create"
22931
22954
  ] }),
22932
- /* @__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" }),
22933
22956
  pluginActions,
22934
22957
  formActions.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-row flex-wrap mt-2", children: formActions.map((action) => {
22935
22958
  const props = {
@@ -23157,7 +23180,8 @@ function EntityEditView({
23157
23180
  databaseId: props.databaseId,
23158
23181
  useCache: false
23159
23182
  });
23160
- 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;
23161
23185
  const authController = useAuthController();
23162
23186
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23163
23187
  const [status, setStatus] = useState(initialStatus);
@@ -23168,13 +23192,13 @@ function EntityEditView({
23168
23192
  return entity ? canEditEntity(props.collection, authController, props.path, entity ?? null) : void 0;
23169
23193
  }
23170
23194
  }, [authController, entity, status]);
23171
- if (dataLoading && !cachedValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23195
+ if (dataLoading && !initialDirtyValues || (!entity || canEdit === void 0) && (status === "existing" || status === "copy")) {
23172
23196
  return /* @__PURE__ */ jsx(CircularProgressCenter, {});
23173
23197
  }
23174
- if (entityId && !entity && !cachedValues) {
23198
+ if (entityId && !entity && !initialDirtyValues) {
23175
23199
  console.error(`Entity with id ${entityId} not found in collection ${props.path}`);
23176
23200
  }
23177
- 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 });
23178
23202
  }
23179
23203
  function EntityEditViewInner({
23180
23204
  path,
@@ -23187,7 +23211,7 @@ function EntityEditViewInner({
23187
23211
  onSaved,
23188
23212
  onTabChange,
23189
23213
  entity,
23190
- cachedDirtyValues,
23214
+ initialDirtyValues,
23191
23215
  dataLoading,
23192
23216
  layout = "side_panel",
23193
23217
  barActions,
@@ -23315,7 +23339,7 @@ function EntityEditViewInner({
23315
23339
  /* @__PURE__ */ jsx(EntityView, { className: "px-8 h-full overflow-auto", entity, path, collection }),
23316
23340
  /* @__PURE__ */ jsx("div", { className: "h-16" })
23317
23341
  ] }) }) : null;
23318
- 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) => {
23319
23343
  setUsedEntity(entity_0);
23320
23344
  formProps?.onEntityChange?.(entity_0);
23321
23345
  }, onStatusChange: (status_0) => {