@firecms/core 3.0.0-canary.286 → 3.0.0-canary.287

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.
@@ -11,6 +11,12 @@ export type OnUpdateParams = {
11
11
  selectedTab?: string;
12
12
  collection: EntityCollection<any>;
13
13
  };
14
+ export type BarActionsParams = {
15
+ values: object;
16
+ status: EntityStatus;
17
+ path: string;
18
+ entityId?: string;
19
+ };
14
20
  export type OnTabChangeParams<M extends Record<string, any>> = {
15
21
  path: string;
16
22
  entityId?: string;
@@ -32,11 +38,11 @@ export interface EntityEditViewProps<M extends Record<string, any>> {
32
38
  copy?: boolean;
33
39
  selectedTab?: string;
34
40
  parentCollectionIds: string[];
35
- onValuesModified?: (modified: boolean) => void;
41
+ onValuesModified?: (modified: boolean, values: M) => void;
36
42
  onSaved?: (params: OnUpdateParams) => void;
37
43
  onTabChange?: (props: OnTabChangeParams<M>) => void;
38
44
  layout?: "side_panel" | "full_screen";
39
- barActions?: React.ReactNode;
45
+ barActions?: (params: BarActionsParams) => React.ReactNode;
40
46
  formProps?: Partial<EntityFormProps<M>>;
41
47
  }
42
48
  /**
@@ -19,7 +19,7 @@ export type EntityFormProps<M extends Record<string, any>> = {
19
19
  entity?: Entity<M>;
20
20
  databaseId?: string;
21
21
  onIdChange?: (id: string) => void;
22
- onValuesModified?: (modified: boolean) => void;
22
+ onValuesModified?: (modified: boolean, values: M) => void;
23
23
  onSaved?: (params: OnUpdateParams) => void;
24
24
  initialDirtyValues?: Partial<M>;
25
25
  onFormContextReady?: (formContext: FormContext) => void;
@@ -0,0 +1,11 @@
1
+ import { FormexController } from "@firecms/formex";
2
+ import { ResolvedProperties } from "../../types";
3
+ interface LocalChangesMenuProps<M extends object> {
4
+ cacheKey: string;
5
+ localChangesData: Partial<M>;
6
+ formex: FormexController<M>;
7
+ onClearLocalChanges?: () => void;
8
+ properties: ResolvedProperties<M>;
9
+ }
10
+ export declare function LocalChangesMenu<M extends object>({ localChangesData, formex, onClearLocalChanges, cacheKey, properties }: LocalChangesMenuProps<M>): import("react/jsx-runtime").JSX.Element;
11
+ export {};
package/dist/index.es.js CHANGED
@@ -2,10 +2,10 @@ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
2
  import { c } from "react-compiler-runtime";
3
3
  import * as React from "react";
4
4
  import React__default, { useRef, useEffect, useContext, useCallback, useMemo, useState, createElement, createRef, createContext, forwardRef, useLayoutEffect } from "react";
5
- import { getColorSchemeForSeed, CHIP_COLORS, FunctionsIcon, CircleIcon, iconKeys, coolIconKeys, Icon, Tooltip, ErrorIcon, Typography, IconButton, ContentCopyIcon, OpenInNewIcon, DescriptionIcon, cls, Skeleton, Chip, defaultBorderMixin, KeyboardTabIcon, Checkbox, AccountCircleIcon, Markdown, TextareaAutosize, focusedDisabled, MultiSelect, MultiSelectItem, Select, SelectItem, BooleanSwitch, DateTimeField, paperMixin, EditIcon, DoNotDisturbOnIcon, Menu, MenuItem, MoreVertIcon, 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";
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, WarningIcon, KeyboardArrowDownIcon, VisibilityIcon, CheckIcon, CancelIcon, Alert, NotesIcon, InfoIcon, fieldBackgroundMixin, RemoveIcon, fieldBackgroundDisabledMixin, fieldBackgroundHoverMixin, ArrowDropDownIcon, FilterListOffIcon, SearchIcon, Avatar, DarkModeIcon, LightModeIcon, BrightnessMediumIcon, LogoutIcon, HandleIcon, KeyboardArrowUpIcon, debounce, Sheet, Tab, Tabs, CodeIcon, OpenInFullIcon, ViewStreamIcon, RepeatIcon, BallotIcon, ScheduleIcon, AddLinkIcon, LinkIcon, DriveFolderUploadIcon, UploadFileIcon, FormatListNumberedIcon, NumbersIcon, PersonIcon, ListAltIcon, ListIcon, FlagIcon, MailIcon, HttpIcon, FormatQuoteIcon, SubjectIcon, ShortTextIcon, MenuIcon, ChevronLeftIcon } from "@firecms/ui";
6
6
  import { SnackbarProvider as SnackbarProvider$1, useSnackbar } from "notistack";
7
7
  import hash from "object-hash";
8
- import { getIn, useFormex, setIn, useCreateFormex, flattenKeys, Formex, Field } from "@firecms/formex";
8
+ import { getIn, useFormex, flattenKeys, setIn, useCreateFormex, 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";
@@ -1054,6 +1054,12 @@ const applyPermissionsFunctionIfEmpty = (collections, permissionsBuilder) => {
1054
1054
  };
1055
1055
  });
1056
1056
  };
1057
+ function getLocalChangesBackup(collection) {
1058
+ if (!collection.localChangesBackup) {
1059
+ return "manual_apply";
1060
+ }
1061
+ return collection.localChangesBackup;
1062
+ }
1057
1063
  const kebabCaseRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;
1058
1064
  const toKebabCase = (str) => {
1059
1065
  const regExpMatchArray = str.match(kebabCaseRegex);
@@ -9757,7 +9763,6 @@ function customReviver(key, value) {
9757
9763
  return value;
9758
9764
  }
9759
9765
  function saveEntityToCache(path, data) {
9760
- entityCache.set(path, data);
9761
9766
  if (isLocalStorageAvailable) {
9762
9767
  try {
9763
9768
  const key = LOCAL_STORAGE_PREFIX + path;
@@ -9768,17 +9773,22 @@ function saveEntityToCache(path, data) {
9768
9773
  }
9769
9774
  }
9770
9775
  }
9771
- function getEntityFromCache(path, useLocalStorage = true) {
9772
- if (entityCache.has(path)) {
9773
- return entityCache.get(path);
9774
- }
9775
- if (isLocalStorageAvailable && useLocalStorage) {
9776
+ function removeEntityFromMemoryCache(path) {
9777
+ entityCache.delete(path);
9778
+ }
9779
+ function saveEntityToMemoryCache(path, data) {
9780
+ entityCache.set(path, data);
9781
+ }
9782
+ function getEntityFromMemoryCache(path) {
9783
+ return entityCache.get(path);
9784
+ }
9785
+ function getEntityFromCache(path) {
9786
+ if (isLocalStorageAvailable) {
9776
9787
  try {
9777
9788
  const key = LOCAL_STORAGE_PREFIX + path;
9778
9789
  const entityString = localStorage.getItem(key);
9779
9790
  if (entityString) {
9780
9791
  const entity = JSON.parse(entityString, customReviver);
9781
- entityCache.set(path, entity);
9782
9792
  return entity;
9783
9793
  }
9784
9794
  } catch (error) {
@@ -9787,12 +9797,7 @@ function getEntityFromCache(path, useLocalStorage = true) {
9787
9797
  }
9788
9798
  return void 0;
9789
9799
  }
9790
- function hasEntityInCache(path) {
9791
- return entityCache.has(path);
9792
- }
9793
9800
  function removeEntityFromCache(path) {
9794
- console.debug("Removing entity from cache", path);
9795
- entityCache.delete(path);
9796
9801
  if (isLocalStorageAvailable) {
9797
9802
  try {
9798
9803
  const key = LOCAL_STORAGE_PREFIX + path;
@@ -9829,8 +9834,8 @@ const EntityCollectionRowActions = function EntityCollectionRowActions2({
9829
9834
  const hasCollapsedActions = actions.some((a) => a.collapsed || a.collapsed === void 0);
9830
9835
  const collapsedActions = actions.filter((a_0) => a_0.collapsed || a_0.collapsed === void 0);
9831
9836
  const uncollapsedActions = actions.filter((a_1) => a_1.collapsed === false);
9832
- const enableLocalChangesBackup = collection?.enableLocalChangesBackup !== void 0 ? collection?.enableLocalChangesBackup : true;
9833
- const hasDraft = enableLocalChangesBackup ? hasEntityInCache(fullPath + "/" + entity.id) : false;
9837
+ const enableLocalChangesBackup = collection ? getLocalChangesBackup(collection) : false;
9838
+ const hasDraft = enableLocalChangesBackup ? getEntityFromCache(fullPath + "/" + entity.id) : false;
9834
9839
  return /* @__PURE__ */ jsxs("div", { className: cls("h-full flex items-center justify-center flex-col bg-surface-50 dark:bg-surface-900 bg-opacity-90 dark:bg-opacity-90 z-10", frozen ? "sticky left-0" : ""), onClick: useCallback((event) => {
9835
9840
  event.stopPropagation();
9836
9841
  }, []), style: {
@@ -15165,6 +15170,273 @@ function buildSideActions$1({
15165
15170
  savingError && /* @__PURE__ */ jsx("div", { className: "text-right", children: /* @__PURE__ */ jsx(Typography, { color: "error", children: savingError.message }) })
15166
15171
  ] });
15167
15172
  }
15173
+ function LocalChangesMenu(t0) {
15174
+ const $ = c(43);
15175
+ const {
15176
+ localChangesData,
15177
+ formex,
15178
+ onClearLocalChanges,
15179
+ cacheKey,
15180
+ properties
15181
+ } = t0;
15182
+ const snackbarController = useSnackbarController();
15183
+ const [previewDialogOpen, setPreviewDialogOpen] = useState(false);
15184
+ const [open, setOpen] = useState(false);
15185
+ let t1;
15186
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
15187
+ t1 = () => {
15188
+ setOpen(true);
15189
+ };
15190
+ $[0] = t1;
15191
+ } else {
15192
+ t1 = $[0];
15193
+ }
15194
+ const handleOpenMenu = t1;
15195
+ let t2;
15196
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
15197
+ t2 = () => {
15198
+ setOpen(false);
15199
+ };
15200
+ $[1] = t2;
15201
+ } else {
15202
+ t2 = $[1];
15203
+ }
15204
+ const handleCloseMenu = t2;
15205
+ let t3;
15206
+ if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
15207
+ t3 = () => {
15208
+ setPreviewDialogOpen(true);
15209
+ handleCloseMenu();
15210
+ };
15211
+ $[2] = t3;
15212
+ } else {
15213
+ t3 = $[2];
15214
+ }
15215
+ const handlePreview = t3;
15216
+ let t4;
15217
+ if ($[3] !== formex || $[4] !== localChangesData || $[5] !== onClearLocalChanges || $[6] !== snackbarController) {
15218
+ t4 = () => {
15219
+ const mergedValues = mergeDeep(formex.values, localChangesData);
15220
+ const touched = {
15221
+ ...formex.touched
15222
+ };
15223
+ const newTouched = flattenKeys(localChangesData);
15224
+ newTouched.forEach((key) => {
15225
+ touched[key] = true;
15226
+ });
15227
+ formex.setTouched(touched);
15228
+ formex.setValues(mergedValues);
15229
+ snackbarController.open({
15230
+ type: "info",
15231
+ message: "Local changes applied to the form"
15232
+ });
15233
+ handleCloseMenu();
15234
+ onClearLocalChanges?.();
15235
+ };
15236
+ $[3] = formex;
15237
+ $[4] = localChangesData;
15238
+ $[5] = onClearLocalChanges;
15239
+ $[6] = snackbarController;
15240
+ $[7] = t4;
15241
+ } else {
15242
+ t4 = $[7];
15243
+ }
15244
+ const handleApply = t4;
15245
+ let t5;
15246
+ if ($[8] !== cacheKey || $[9] !== onClearLocalChanges || $[10] !== snackbarController) {
15247
+ t5 = () => {
15248
+ removeEntityFromCache(cacheKey);
15249
+ snackbarController.open({
15250
+ type: "info",
15251
+ message: "Local changes discarded"
15252
+ });
15253
+ handleCloseMenu();
15254
+ onClearLocalChanges?.();
15255
+ };
15256
+ $[8] = cacheKey;
15257
+ $[9] = onClearLocalChanges;
15258
+ $[10] = snackbarController;
15259
+ $[11] = t5;
15260
+ } else {
15261
+ t5 = $[11];
15262
+ }
15263
+ const handleDiscard = t5;
15264
+ let t6;
15265
+ if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
15266
+ t6 = /* @__PURE__ */ jsx(WarningIcon, { size: "smallest", className: "mr-1 text-yellow-600 dark:text-yellow-400" });
15267
+ $[12] = t6;
15268
+ } else {
15269
+ t6 = $[12];
15270
+ }
15271
+ let t7;
15272
+ if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
15273
+ t7 = /* @__PURE__ */ jsxs(Button, { size: "small", className: "font-semibold text-xs rounded-full px-4 py-1 bg-yellow-200 dark:bg-yellow-900 hover:bg-yellow-300 dark:hover:bg-yellow-800 text-yellow-800 dark:text-yellow-200", onClick: handleOpenMenu, children: [
15274
+ t6,
15275
+ "Unsaved Local changes",
15276
+ /* @__PURE__ */ jsx(KeyboardArrowDownIcon, { size: "smallest" })
15277
+ ] });
15278
+ $[13] = t7;
15279
+ } else {
15280
+ t7 = $[13];
15281
+ }
15282
+ let t8;
15283
+ if ($[14] === Symbol.for("react.memo_cache_sentinel")) {
15284
+ t8 = /* @__PURE__ */ jsx("div", { className: "max-w-xs px-4 py-4 text-sm text-gray-700 dark:text-gray-300", children: "This document was edited locally and has unsaved changes." });
15285
+ $[14] = t8;
15286
+ } else {
15287
+ t8 = $[14];
15288
+ }
15289
+ let t9;
15290
+ if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
15291
+ t9 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handlePreview, children: [
15292
+ /* @__PURE__ */ jsx(VisibilityIcon, { size: "small" }),
15293
+ "Preview Changes"
15294
+ ] });
15295
+ $[15] = t9;
15296
+ } else {
15297
+ t9 = $[15];
15298
+ }
15299
+ let t10;
15300
+ if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
15301
+ t10 = /* @__PURE__ */ jsx(CheckIcon, { size: "small" });
15302
+ $[16] = t10;
15303
+ } else {
15304
+ t10 = $[16];
15305
+ }
15306
+ let t11;
15307
+ if ($[17] !== handleApply) {
15308
+ t11 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handleApply, children: [
15309
+ t10,
15310
+ "Apply Changes"
15311
+ ] });
15312
+ $[17] = handleApply;
15313
+ $[18] = t11;
15314
+ } else {
15315
+ t11 = $[18];
15316
+ }
15317
+ let t12;
15318
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
15319
+ t12 = /* @__PURE__ */ jsx(CancelIcon, { size: "small" });
15320
+ $[19] = t12;
15321
+ } else {
15322
+ t12 = $[19];
15323
+ }
15324
+ let t13;
15325
+ if ($[20] !== handleDiscard) {
15326
+ t13 = /* @__PURE__ */ jsxs(MenuItem, { dense: true, onClick: handleDiscard, children: [
15327
+ t12,
15328
+ "Discard Local Changes"
15329
+ ] });
15330
+ $[20] = handleDiscard;
15331
+ $[21] = t13;
15332
+ } else {
15333
+ t13 = $[21];
15334
+ }
15335
+ let t14;
15336
+ if ($[22] !== open || $[23] !== t11 || $[24] !== t13) {
15337
+ t14 = /* @__PURE__ */ jsxs(Menu, { trigger: t7, open, onOpenChange: setOpen, children: [
15338
+ t8,
15339
+ t9,
15340
+ t11,
15341
+ t13
15342
+ ] });
15343
+ $[22] = open;
15344
+ $[23] = t11;
15345
+ $[24] = t13;
15346
+ $[25] = t14;
15347
+ } else {
15348
+ t14 = $[25];
15349
+ }
15350
+ let t15;
15351
+ let t16;
15352
+ if ($[26] === Symbol.for("react.memo_cache_sentinel")) {
15353
+ t15 = /* @__PURE__ */ jsx("h3", { className: "text-2xl mb-4", children: "Preview Local Changes" });
15354
+ t16 = /* @__PURE__ */ jsx("p", { className: "mb-4", children: "These are the local changes that will be applied to the form." });
15355
+ $[26] = t15;
15356
+ $[27] = t16;
15357
+ } else {
15358
+ t15 = $[26];
15359
+ t16 = $[27];
15360
+ }
15361
+ let t17;
15362
+ if ($[28] !== localChangesData || $[29] !== properties) {
15363
+ t17 = flattenKeys(localChangesData).map((key_0) => {
15364
+ const value = getIn(localChangesData, key_0);
15365
+ const property = getPropertyInPath(properties, key_0);
15366
+ if (!property) {
15367
+ return null;
15368
+ }
15369
+ return /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-x-4 px-4 py-3 items-center", children: [
15370
+ /* @__PURE__ */ jsx("div", { className: "col-span-3 text-right", children: /* @__PURE__ */ jsx(Typography, { variant: "caption", className: "text-gray-500 dark:text-gray-400 break-words", children: property.name || key_0 }) }),
15371
+ /* @__PURE__ */ jsx("div", { className: "col-span-9", children: /* @__PURE__ */ jsx(PropertyPreview, { propertyKey: key_0, value, property, size: "small" }) })
15372
+ ] }, key_0);
15373
+ });
15374
+ $[28] = localChangesData;
15375
+ $[29] = properties;
15376
+ $[30] = t17;
15377
+ } else {
15378
+ t17 = $[30];
15379
+ }
15380
+ let t18;
15381
+ if ($[31] !== t17) {
15382
+ t18 = /* @__PURE__ */ jsxs(DialogContent, { children: [
15383
+ t15,
15384
+ t16,
15385
+ /* @__PURE__ */ jsx("div", { className: `border rounded-lg divide-y divide-surface-200 divide-surface-opacity-40 dark:divide-surface-700 dark:divide-opacity-40 ${defaultBorderMixin}`, children: t17 })
15386
+ ] });
15387
+ $[31] = t17;
15388
+ $[32] = t18;
15389
+ } else {
15390
+ t18 = $[32];
15391
+ }
15392
+ let t19;
15393
+ if ($[33] === Symbol.for("react.memo_cache_sentinel")) {
15394
+ t19 = /* @__PURE__ */ jsx(Button, { onClick: () => setPreviewDialogOpen(false), children: "Close" });
15395
+ $[33] = t19;
15396
+ } else {
15397
+ t19 = $[33];
15398
+ }
15399
+ let t20;
15400
+ if ($[34] !== handleApply) {
15401
+ t20 = /* @__PURE__ */ jsxs(DialogActions, { children: [
15402
+ t19,
15403
+ /* @__PURE__ */ jsx(Button, { variant: "filled", onClick: () => {
15404
+ handleApply();
15405
+ setPreviewDialogOpen(false);
15406
+ }, children: "Apply changes" })
15407
+ ] });
15408
+ $[34] = handleApply;
15409
+ $[35] = t20;
15410
+ } else {
15411
+ t20 = $[35];
15412
+ }
15413
+ let t21;
15414
+ if ($[36] !== previewDialogOpen || $[37] !== t18 || $[38] !== t20) {
15415
+ t21 = /* @__PURE__ */ jsxs(Dialog, { open: previewDialogOpen, onOpenChange: setPreviewDialogOpen, maxWidth: "4xl", children: [
15416
+ t18,
15417
+ t20
15418
+ ] });
15419
+ $[36] = previewDialogOpen;
15420
+ $[37] = t18;
15421
+ $[38] = t20;
15422
+ $[39] = t21;
15423
+ } else {
15424
+ t21 = $[39];
15425
+ }
15426
+ let t22;
15427
+ if ($[40] !== t14 || $[41] !== t21) {
15428
+ t22 = /* @__PURE__ */ jsxs(Fragment, { children: [
15429
+ t14,
15430
+ t21
15431
+ ] });
15432
+ $[40] = t14;
15433
+ $[41] = t21;
15434
+ $[42] = t22;
15435
+ } else {
15436
+ t22 = $[42];
15437
+ }
15438
+ return t22;
15439
+ }
15168
15440
  function extractTouchedValues(values, touched) {
15169
15441
  let acc = {};
15170
15442
  if (!touched || typeof touched !== "object") {
@@ -15252,6 +15524,12 @@ function EntityForm({
15252
15524
  const [entityIdError, setEntityIdError] = useState(false);
15253
15525
  const [savingError, setSavingError] = useState();
15254
15526
  const autoSave = collection.formAutoSave && !collection.customId;
15527
+ const baseInitialValues = useMemo(() => getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs), [authController, collection, path, status, entity, customizationController.propertyConfigs]);
15528
+ const localChangesDataRaw = useMemo(() => entityId ? getEntityFromCache(path + "/" + entityId) : getEntityFromCache(path + "#new"), [entityId, path]);
15529
+ const [localChangesCleared, setLocalChangesCleared] = useState(false);
15530
+ const localChangesBackup = getLocalChangesBackup(collection);
15531
+ const autoApplyLocalChanges = localChangesBackup === "auto_apply";
15532
+ const manualApplyLocalChanges = localChangesBackup === "manual_apply";
15255
15533
  const onSubmit = (values, formexController) => {
15256
15534
  if (mustSetCustomId && !entityId) {
15257
15535
  console.error("Missing custom Id");
@@ -15282,12 +15560,31 @@ function EntityForm({
15282
15560
  formexController.setSubmitting(false);
15283
15561
  });
15284
15562
  };
15285
- const baseInitialValues = getInitialEntityValues(authController, collection, path, status, entity, customizationController.propertyConfigs);
15286
- const initialValues = initialDirtyValues ? mergeDeep(baseInitialValues, initialDirtyValues) : baseInitialValues;
15287
- const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15563
+ const [initialValues_0, initialDirty_0] = useMemo(() => {
15564
+ const initialValuesWithLocalChanges = autoApplyLocalChanges && localChangesDataRaw ? mergeDeep(baseInitialValues, localChangesDataRaw) : baseInitialValues;
15565
+ const initialValues = initialDirtyValues ? mergeDeep(initialValuesWithLocalChanges, initialDirtyValues) : initialValuesWithLocalChanges;
15566
+ const initialDirty = Boolean(initialDirtyValues) && initialDirtyValues && Object.keys(initialDirtyValues).length > 0;
15567
+ return [initialValues, initialDirty];
15568
+ }, [autoApplyLocalChanges, localChangesDataRaw, baseInitialValues, initialDirtyValues]);
15569
+ const localChangesData = useMemo(() => {
15570
+ if (!localChangesDataRaw) {
15571
+ return void 0;
15572
+ }
15573
+ let filteredChanges = {};
15574
+ const flattenedKeys = flattenKeys(localChangesDataRaw);
15575
+ flattenedKeys.forEach((key) => {
15576
+ const localValue = getIn(localChangesDataRaw, key);
15577
+ const initialValue = getIn(initialValues_0, key);
15578
+ if (!equal(localValue, initialValue)) {
15579
+ filteredChanges = setIn(filteredChanges, key, localValue);
15580
+ }
15581
+ });
15582
+ return filteredChanges;
15583
+ }, [localChangesDataRaw, initialValues_0]);
15584
+ const hasLocalChanges = !localChangesCleared && localChangesData && Object.keys(localChangesData).length > 0;
15288
15585
  const formex = formexProp ?? useCreateFormex({
15289
- initialValues,
15290
- initialDirty,
15586
+ initialValues: initialValues_0,
15587
+ initialDirty: initialDirty_0,
15291
15588
  initialTouched: initialDirtyValues ? flattenKeys(initialDirtyValues).reduce((previousValue, currentValue) => ({
15292
15589
  ...previousValue,
15293
15590
  [currentValue]: true
@@ -15295,13 +15592,13 @@ function EntityForm({
15295
15592
  onSubmit,
15296
15593
  onReset: () => {
15297
15594
  clearDirtyCache();
15298
- onValuesModified?.(false);
15595
+ onValuesModified?.(false, initialValues_0);
15299
15596
  },
15300
15597
  onValuesChangeDeferred: (values_0, controller) => {
15301
- const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15598
+ const key_0 = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
15302
15599
  if (controller.dirty) {
15303
15600
  const touchedValues = extractTouchedValues(values_0, controller.touched);
15304
- saveEntityToCache(key, touchedValues);
15601
+ saveEntityToCache(key_0, touchedValues);
15305
15602
  }
15306
15603
  },
15307
15604
  validation: (values_1) => {
@@ -15354,14 +15651,16 @@ function EntityForm({
15354
15651
  }, [snackbarController]);
15355
15652
  function clearDirtyCache() {
15356
15653
  if (status === "new" || status === "copy") {
15654
+ removeEntityFromMemoryCache(path + "#new");
15357
15655
  removeEntityFromCache(path + "#new");
15358
15656
  } else {
15657
+ removeEntityFromMemoryCache(path + "/" + entityId);
15359
15658
  removeEntityFromCache(path + "/" + entityId);
15360
15659
  }
15361
15660
  }
15362
15661
  const onSaveSuccess = (updatedEntity) => {
15363
15662
  clearDirtyCache();
15364
- onValuesModified?.(false);
15663
+ onValuesModified?.(false, updatedEntity.values);
15365
15664
  if (!autoSave) snackbarController.open({
15366
15665
  type: "success",
15367
15666
  message: `${collection.singularName ?? collection.name}: Saved correctly`
@@ -15521,7 +15820,7 @@ function EntityForm({
15521
15820
  }, [doOnIdUpdate]);
15522
15821
  useEffect(() => {
15523
15822
  if (!autoSave) {
15524
- onValuesModified?.(modified);
15823
+ onValuesModified?.(modified, formex.values);
15525
15824
  }
15526
15825
  }, [formex.dirty]);
15527
15826
  const modified = formex.dirty;
@@ -15533,11 +15832,11 @@ function EntityForm({
15533
15832
  useOnAutoSave(autoSave, formex, lastSavedValues, save);
15534
15833
  useEffect(() => {
15535
15834
  if (!autoSave && !formex.isSubmitting && underlyingChanges && entity) {
15536
- Object.entries(underlyingChanges).forEach(([key_0, value_0]) => {
15537
- const formValue = formex.values[key_0];
15538
- if (!equal(value_0, formValue) && !formex.touched[key_0]) {
15539
- console.debug("Updated value from the datasource:", key_0, value_0);
15540
- formex.setFieldValue(key_0, value_0 !== void 0 ? value_0 : null);
15835
+ Object.entries(underlyingChanges).forEach(([key_1, value_0]) => {
15836
+ const formValue = formex.values[key_1];
15837
+ if (!equal(value_0, formValue) && !formex.touched[key_1]) {
15838
+ console.debug("Updated value from the datasource:", key_1, value_0);
15839
+ formex.setFieldValue(key_1, value_0 !== void 0 ? value_0 : null);
15541
15840
  }
15542
15841
  });
15543
15842
  }
@@ -15547,16 +15846,16 @@ function EntityForm({
15547
15846
  if (Builder) {
15548
15847
  return /* @__PURE__ */ jsx(Builder, { collection, entity, modifiedValues: formex.values, formContext });
15549
15848
  }
15550
- return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_1) => {
15551
- const property = resolvedCollection.properties[key_1];
15849
+ return /* @__PURE__ */ jsx(FormLayout, { children: formFieldKeys.map((key_2) => {
15850
+ const property = resolvedCollection.properties[key_2];
15552
15851
  if (property) {
15553
- const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_1) && formex.touched[key_1];
15852
+ const underlyingValueHasChanged = !!underlyingChanges && Object.keys(underlyingChanges).includes(key_2) && formex.touched[key_2];
15554
15853
  const disabled_0 = disabledProp || !autoSave && formex.isSubmitting || isReadOnly(property) || Boolean(property.disabled);
15555
15854
  const hidden = isHidden(property);
15556
15855
  if (hidden) return null;
15557
15856
  const widthPercentage = property.widthPercentage ?? 100;
15558
15857
  const cmsFormFieldProps = {
15559
- propertyKey: key_1,
15858
+ propertyKey: key_2,
15560
15859
  disabled: disabled_0,
15561
15860
  property,
15562
15861
  includeDescription: property.description || property.longDescription,
@@ -15566,9 +15865,9 @@ function EntityForm({
15566
15865
  minimalistView: false,
15567
15866
  autoFocus: false
15568
15867
  };
15569
- return /* @__PURE__ */ jsx(FormEntry, { propertyKey: key_1, widthPercentage, children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps }) }, `field_${key_1}`);
15868
+ return /* @__PURE__ */ jsx(FormEntry, { propertyKey: key_2, widthPercentage, children: /* @__PURE__ */ jsx(PropertyFieldBinding, { ...cmsFormFieldProps }) }, `field_${key_2}`);
15570
15869
  }
15571
- const additionalField = resolvedCollection.additionalFields?.find((f) => f.key === key_1);
15870
+ const additionalField = resolvedCollection.additionalFields?.find((f) => f.key === key_2);
15572
15871
  if (additionalField && entity) {
15573
15872
  const Builder_0 = additionalField.Builder;
15574
15873
  if (!Builder_0 && !additionalField.value) {
@@ -15579,11 +15878,11 @@ function EntityForm({
15579
15878
  context
15580
15879
  })?.toString() });
15581
15880
  return /* @__PURE__ */ jsxs("div", { className: "w-full", children: [
15582
- /* @__PURE__ */ jsx(LabelWithIconAndTooltip, { propertyKey: key_1, icon: /* @__PURE__ */ jsx(NotesIcon, { size: "small" }), title: additionalField.name, className: "text-text-secondary dark:text-text-secondary-dark ml-3.5" }),
15881
+ /* @__PURE__ */ jsx(LabelWithIconAndTooltip, { propertyKey: key_2, icon: /* @__PURE__ */ jsx(NotesIcon, { size: "small" }), title: additionalField.name, className: "text-text-secondary dark:text-text-secondary-dark ml-3.5" }),
15583
15882
  /* @__PURE__ */ jsx("div", { className: cls(paperMixin, "w-full min-h-14 p-4 md:p-6 overflow-x-scroll no-scrollbar"), children: /* @__PURE__ */ jsx(ErrorBoundary, { children: child }) })
15584
- ] }, `additional_${key_1}`);
15883
+ ] }, `additional_${key_2}`);
15585
15884
  }
15586
- console.warn(`Property ${key_1} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
15885
+ console.warn(`Property ${key_2} not found in collection ${resolvedCollection.name} in properties or additional fields. Skipping.`);
15587
15886
  return null;
15588
15887
  }).filter(Boolean) });
15589
15888
  };
@@ -15618,7 +15917,10 @@ function EntityForm({
15618
15917
  values: baseInitialValues
15619
15918
  }), noValidate: true, className: cls("flex-1 flex flex-row w-full overflow-y-auto justify-center", className), children: [
15620
15919
  /* @__PURE__ */ jsx("div", { id: `form_${path}`, className: cls("relative flex flex-row max-w-4xl lg:max-w-3xl xl:max-w-4xl 2xl:max-w-6xl w-full h-fit"), children: /* @__PURE__ */ jsxs("div", { className: cls("flex flex-col w-full pt-12 pb-16 px-4 sm:px-8 md:px-10"), children: [
15621
- formex.dirty ? /* @__PURE__ */ jsx(Tooltip, { title: "Local unsaved changes", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsx(Chip, { size: "small", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsx(EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsx(Tooltip, { title: "In sync with the database", className: "self-end sticky top-4 z-10", children: /* @__PURE__ */ jsx(Chip, { size: "small", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" }) }) }),
15920
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-row gap-4 self-end sticky top-4 z-10", children: [
15921
+ manualApplyLocalChanges && hasLocalChanges && /* @__PURE__ */ jsx(LocalChangesMenu, { cacheKey: status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId, properties: resolvedCollection.properties, localChangesData, formex, onClearLocalChanges: () => setLocalChangesCleared(true) }),
15922
+ formex.dirty ? /* @__PURE__ */ jsx(Tooltip, { title: "There are local unsaved changes", children: /* @__PURE__ */ jsx(Chip, { size: "small", colorScheme: "orangeDarker", children: /* @__PURE__ */ jsx(EditIcon, { size: "smallest" }) }) }) : /* @__PURE__ */ jsx(Tooltip, { title: "The current form is in sync with the database", children: /* @__PURE__ */ jsx(Chip, { size: "small", children: /* @__PURE__ */ jsx(CheckIcon, { size: "smallest" }) }) })
15923
+ ] }),
15622
15924
  formView
15623
15925
  ] }) }),
15624
15926
  dialogActions
@@ -20428,7 +20730,7 @@ const EntityCollectionView = React__default.memo(function EntityCollectionView2(
20428
20730
  /* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: "So empty..." }),
20429
20731
  /* @__PURE__ */ jsxs(Button, { color: "primary", variant: "outlined", onClick: onNewClick, className: "mt-4", children: [
20430
20732
  /* @__PURE__ */ jsx(AddIcon, {}),
20431
- "Create your first entity"
20733
+ "Create your first entry"
20432
20734
  ] })
20433
20735
  ] }) : /* @__PURE__ */ jsx(Typography, { variant: "label", children: "No results with the applied filter/sort" }), hoverRow, inlineEditing: checkInlineEditing(), AdditionalHeaderWidget: buildAdditionalHeaderWidget, AddColumnComponent: addColumnComponentInternal, getIdColumnWidth, additionalIDHeaderWidget: /* @__PURE__ */ jsx(EntityIdHeaderWidget, { path: fullPath, fullIdPath: fullIdPath ?? fullPath, collection }), openEntityMode }, `collection_table_${fullPath}`),
20434
20736
  popupCell && /* @__PURE__ */ jsx(PopupFormField, { open: Boolean(popupCell), onClose: onPopupClose, cellRect: popupCell?.cellRect, propertyKey: popupCell?.propertyKey, collection, entityId: popupCell.entityId, tableKey: tableKey.current, customFieldValidator: uniqueFieldValidator, path: resolvedFullPath, onCellValueChange: onValueChange, container: containerRef.current }, `popup_form_${popupCell?.propertyKey}_${popupCell?.entityId}`),
@@ -23087,6 +23389,7 @@ function EntityJsonPreview(t0) {
23087
23389
  function createFormexStub(values) {
23088
23390
  const errorMessage = "You are in a read-only context. You cannot modify the formex controller.";
23089
23391
  return {
23392
+ debugId: "",
23090
23393
  values,
23091
23394
  initialValues: values,
23092
23395
  touched: {},
@@ -23101,6 +23404,9 @@ function createFormexStub(values) {
23101
23404
  setValues: () => {
23102
23405
  throw new Error(errorMessage);
23103
23406
  },
23407
+ setTouched(touched) {
23408
+ throw new Error(errorMessage);
23409
+ },
23104
23410
  setFieldValue: () => {
23105
23411
  throw new Error(errorMessage);
23106
23412
  },
@@ -23160,8 +23466,7 @@ function EntityEditView({
23160
23466
  databaseId: props.databaseId,
23161
23467
  useCache: false
23162
23468
  });
23163
- const enableLocalChangesBackup = props.collection.enableLocalChangesBackup !== void 0 ? props.collection.enableLocalChangesBackup : true;
23164
- const initialDirtyValues = entityId ? getEntityFromCache(props.path + "/" + entityId, enableLocalChangesBackup) : getEntityFromCache(props.path + "#new", enableLocalChangesBackup);
23469
+ const initialDirtyValues = entityId ? getEntityFromMemoryCache(props.path + "/" + entityId) : getEntityFromMemoryCache(props.path + "#new");
23165
23470
  const authController = useAuthController();
23166
23471
  const initialStatus = props.copy ? "copy" : entityId ? "existing" : "new";
23167
23472
  const [status, setStatus] = useState(initialStatus);
@@ -23320,6 +23625,7 @@ function EntityEditViewInner({
23320
23625
  /* @__PURE__ */ jsx("div", { className: "h-16" })
23321
23626
  ] }) }) : null;
23322
23627
  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) => {
23628
+ console.log("333 EntityEditView onEntityChange:", entity_0);
23323
23629
  setUsedEntity(entity_0);
23324
23630
  formProps?.onEntityChange?.(entity_0);
23325
23631
  }, onStatusChange: (status_0) => {
@@ -23342,7 +23648,12 @@ function EntityEditViewInner({
23342
23648
  const shouldShowTopBar = Boolean(barActions) || hasAdditionalViews;
23343
23649
  let result = /* @__PURE__ */ jsxs("div", { className: "relative flex flex-col h-full w-full bg-white dark:bg-surface-900", children: [
23344
23650
  shouldShowTopBar && /* @__PURE__ */ jsxs("div", { className: cls("h-14 items-center flex overflow-visible overflow-x-scroll w-full no-scrollbar h-14 border-b pl-2 pr-2 pt-1 flex bg-surface-50 dark:bg-surface-900", defaultBorderMixin), children: [
23345
- barActions,
23651
+ barActions?.({
23652
+ path: fullIdPath ?? path,
23653
+ entityId,
23654
+ values: formContext?.values ?? usedEntity?.values ?? {},
23655
+ status
23656
+ }),
23346
23657
  /* @__PURE__ */ jsx("div", { className: "flex-grow" }),
23347
23658
  pluginActionsTop,
23348
23659
  globalLoading && /* @__PURE__ */ jsx("div", { className: "self-center", children: /* @__PURE__ */ jsx(CircularProgress, { size: "small" }) }),
@@ -23445,9 +23756,14 @@ function EntitySidePanel(props) {
23445
23756
  if (!props || !collection) {
23446
23757
  return /* @__PURE__ */ jsx("div", { className: "w-full" });
23447
23758
  }
23448
- return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(EntityEditView, { ...props, fullIdPath, layout: "side_panel", collection, parentCollectionIds, onValuesModified, onSaved: onUpdate, barActions: /* @__PURE__ */ jsxs(Fragment, { children: [
23759
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(ErrorBoundary, { children: /* @__PURE__ */ jsx(EntityEditView, { ...props, fullIdPath, layout: "side_panel", collection, parentCollectionIds, onValuesModified, onSaved: onUpdate, barActions: ({
23760
+ status,
23761
+ values
23762
+ }) => /* @__PURE__ */ jsxs(Fragment, { children: [
23449
23763
  /* @__PURE__ */ jsx(IconButton, { className: "self-center", onClick: onClose, children: /* @__PURE__ */ jsx(CloseIcon, { size: "small" }) }),
23450
23764
  allowFullScreen && /* @__PURE__ */ jsx(IconButton, { className: "self-center", onClick: () => {
23765
+ const key = status === "new" || status === "copy" ? path + "#new" : path + "/" + entityId;
23766
+ saveEntityToMemoryCache(key, values);
23451
23767
  if (entityId) navigate(location.pathname);
23452
23768
  else navigate(location.pathname + "#new");
23453
23769
  }, children: /* @__PURE__ */ jsx(OpenInFullIcon, { size: "small" }) })
@@ -26228,6 +26544,7 @@ export {
26228
26544
  getIdIcon,
26229
26545
  getLabelOrConfigFrom,
26230
26546
  getLastSegment,
26547
+ getLocalChangesBackup,
26231
26548
  getPropertiesWithPropertiesOrder,
26232
26549
  getPropertyInPath,
26233
26550
  getRandomId,