@headless-adminapp/app 1.4.20 → 1.4.21

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.
@@ -9,6 +9,7 @@ const dialog_1 = require("../dialog");
9
9
  const header_1 = require("../header");
10
10
  const locale_1 = require("../locale");
11
11
  const metadata_1 = require("../metadata");
12
+ const unsaved_changes_1 = require("../navigation/unsaved-changes");
12
13
  const progress_indicator_1 = require("../progress-indicator");
13
14
  const route_1 = require("../route");
14
15
  const toast_notification_1 = require("../toast-notification");
@@ -40,6 +41,6 @@ const defaultDataService = {
40
41
  },
41
42
  };
42
43
  const LayoutProvider = ({ authPlaceholder, authProps, dataService = defaultDataService, fileService, localeProps, metadataProps, queryClient = defaults_1.queryClient, routeProps, children, containers: { DialogContainer, ProgressIndicatorContainer, ToastNotificationContainer, }, }) => {
43
- return ((0, jsx_runtime_1.jsx)(route_1.RouteProvider, { ...routeProps, children: (0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: (0, jsx_runtime_1.jsx)(locale_1.LocaleProvider, { ...localeProps, children: (0, jsx_runtime_1.jsx)(metadata_1.MetadataProvider, { ...metadataProps, children: (0, jsx_runtime_1.jsx)(transport_1.DataServiceContext.Provider, { value: dataService, children: (0, jsx_runtime_1.jsx)(context_1.FileServiceContext.Provider, { value: fileService ?? null, children: (0, jsx_runtime_1.jsx)(dialog_1.DialogProvider, { children: (0, jsx_runtime_1.jsx)(progress_indicator_1.ProgressIndicatorProvider, { children: (0, jsx_runtime_1.jsxs)(toast_notification_1.ToastNotificationProvider, { children: [(0, jsx_runtime_1.jsx)(DialogContainer, {}), (0, jsx_runtime_1.jsx)(ProgressIndicatorContainer, {}), (0, jsx_runtime_1.jsx)(ToastNotificationContainer, {}), (0, jsx_runtime_1.jsx)(auth_1.AuthProvider, { ...authProps, children: (0, jsx_runtime_1.jsx)(header_1.HeaderProvider, { children: (0, jsx_runtime_1.jsx)(AuthWrapper_1.AuthWrapper, { Placeholder: authPlaceholder, children: children }) }) })] }) }) }) }) }) }) }) }) }));
44
+ return ((0, jsx_runtime_1.jsx)(route_1.RouteProvider, { ...routeProps, children: (0, jsx_runtime_1.jsx)(react_query_1.QueryClientProvider, { client: queryClient, children: (0, jsx_runtime_1.jsx)(locale_1.LocaleProvider, { ...localeProps, children: (0, jsx_runtime_1.jsx)(metadata_1.MetadataProvider, { ...metadataProps, children: (0, jsx_runtime_1.jsx)(transport_1.DataServiceContext.Provider, { value: dataService, children: (0, jsx_runtime_1.jsx)(context_1.FileServiceContext.Provider, { value: fileService ?? null, children: (0, jsx_runtime_1.jsx)(dialog_1.DialogProvider, { children: (0, jsx_runtime_1.jsx)(progress_indicator_1.ProgressIndicatorProvider, { children: (0, jsx_runtime_1.jsxs)(toast_notification_1.ToastNotificationProvider, { children: [(0, jsx_runtime_1.jsx)(DialogContainer, {}), (0, jsx_runtime_1.jsx)(ProgressIndicatorContainer, {}), (0, jsx_runtime_1.jsx)(ToastNotificationContainer, {}), (0, jsx_runtime_1.jsx)(auth_1.AuthProvider, { ...authProps, children: (0, jsx_runtime_1.jsx)(header_1.HeaderProvider, { children: (0, jsx_runtime_1.jsxs)(AuthWrapper_1.AuthWrapper, { Placeholder: authPlaceholder, children: [(0, jsx_runtime_1.jsx)(unsaved_changes_1.UnsavedChangesRouteGuard, {}), children] }) }) })] }) }) }) }) }) }) }) }) }));
44
45
  };
45
46
  exports.LayoutProvider = LayoutProvider;
@@ -39,7 +39,7 @@ interface BaseCommandState {
39
39
  appearance?: 'subtle' | 'colored';
40
40
  }
41
41
  interface ActionableCommandState {
42
- onClick?: () => void;
42
+ onClick?: () => void | Promise<void>;
43
43
  disabled?: boolean;
44
44
  hidden?: boolean;
45
45
  }
@@ -0,0 +1,2 @@
1
+ import { FC } from 'react';
2
+ export declare const UnsavedChangesInfoSetter: FC;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsavedChangesInfoSetter = void 0;
4
+ const unsaved_changes_1 = require("@headless-adminapp/app/navigation/unsaved-changes");
5
+ const react_1 = require("react");
6
+ const hooks_1 = require("../hooks");
7
+ const UnsavedChangesInfoSetter = () => {
8
+ const form = (0, hooks_1.useFormInstance)();
9
+ (0, react_1.useEffect)(() => {
10
+ if (!form.formState.isDirty) {
11
+ (0, unsaved_changes_1.setUnsavedChangesInfo)(null);
12
+ }
13
+ else {
14
+ (0, unsaved_changes_1.setUnsavedChangesInfo)({
15
+ title: 'Unsaved Changes',
16
+ message: 'You have unsaved changes. Are you sure you want to leave without saving?',
17
+ });
18
+ }
19
+ return () => {
20
+ (0, unsaved_changes_1.setUnsavedChangesInfo)(null);
21
+ };
22
+ }, [form.formState.isDirty]);
23
+ return null;
24
+ };
25
+ exports.UnsavedChangesInfoSetter = UnsavedChangesInfoSetter;
@@ -20,6 +20,7 @@ const DataResolver_1 = require("./DataResolver");
20
20
  const getRecord_1 = require("./getRecord");
21
21
  const InitialValueResolver_1 = require("./InitialValueResolver");
22
22
  const ReadonlyInfoResolver_1 = require("./ReadonlyInfoResolver");
23
+ const UnsavedChangesInfoSetter_1 = require("./UnsavedChangesInfoSetter");
23
24
  const utils_2 = require("./utils");
24
25
  function mergeInitialWithHistory(initialValue, historyState) {
25
26
  return {
@@ -117,5 +118,5 @@ function DataFormProvider(props) {
117
118
  schemaStore,
118
119
  props.commands,
119
120
  ]);
120
- return ((0, jsx_runtime_1.jsx)(context_1.DataFormContext.Provider, { value: contextValue, children: (0, jsx_runtime_1.jsxs)(react_hook_form_1.FormProvider, { ...formInstance, children: [(0, jsx_runtime_1.jsx)(DataResolver_1.DataResolver, { retriveRecordFn: props.retriveRecordFn ?? getRecord_1.getRecord }), (0, jsx_runtime_1.jsx)(InitialValueResolver_1.InitialValueResolver, {}), (0, jsx_runtime_1.jsx)(ReadonlyInfoResolver_1.ReadonlyInfoResolver, { setFormReadOnly: setFormReadOnly }), (0, jsx_runtime_1.jsx)(CustomHookExecuter_1.CustomHookExecuter, { useHookFn: props.form.experience.useHookFn }), (0, jsx_runtime_1.jsx)(CalculatedField_1.CalculatedField, {}), props.children] }) }));
121
+ return ((0, jsx_runtime_1.jsx)(context_1.DataFormContext.Provider, { value: contextValue, children: (0, jsx_runtime_1.jsxs)(react_hook_form_1.FormProvider, { ...formInstance, children: [(0, jsx_runtime_1.jsx)(DataResolver_1.DataResolver, { retriveRecordFn: props.retriveRecordFn ?? getRecord_1.getRecord }), (0, jsx_runtime_1.jsx)(InitialValueResolver_1.InitialValueResolver, {}), (0, jsx_runtime_1.jsx)(ReadonlyInfoResolver_1.ReadonlyInfoResolver, { setFormReadOnly: setFormReadOnly }), (0, jsx_runtime_1.jsx)(CustomHookExecuter_1.CustomHookExecuter, { useHookFn: props.form.experience.useHookFn }), (0, jsx_runtime_1.jsx)(CalculatedField_1.CalculatedField, {}), (0, jsx_runtime_1.jsx)(UnsavedChangesInfoSetter_1.UnsavedChangesInfoSetter, {}), props.children] }) }));
121
122
  }
@@ -1 +1 @@
1
- export declare function useOpenRecord(): (id: string, logicalName: string) => void;
1
+ export declare function useOpenRecord(): (id: string, logicalName: string) => Promise<void>;
@@ -13,13 +13,13 @@ function useOpenRecord() {
13
13
  const router = (0, route_1.useRouter)();
14
14
  const dataRef = (0, react_1.useRef)(data);
15
15
  dataRef.current = data;
16
- return (0, react_1.useCallback)((id, logicalName) => {
16
+ return (0, react_1.useCallback)(async (id, logicalName) => {
17
17
  const path = routeResolver({
18
18
  logicalName: logicalName,
19
19
  type: app_1.PageType.EntityForm,
20
20
  id,
21
21
  });
22
- router.push(path, {
22
+ await router.push(path, {
23
23
  state: {
24
24
  navigator: logicalName === schema.logicalName
25
25
  ? {
@@ -1 +1,4 @@
1
+ /**
2
+ * @zero-deps
3
+ */
1
4
  export declare function useCloseDialog(): (id: string) => void;
@@ -15,6 +15,9 @@ function markDialogAsClosed(items, id) {
15
15
  function excludeDialogItemById(items, id) {
16
16
  return items.filter((item) => item.id !== id);
17
17
  }
18
+ /**
19
+ * @zero-deps
20
+ */
18
21
  function useCloseDialog() {
19
22
  const setValue = (0, context_1.useContextSetValue)(context_2.DialogContext);
20
23
  const closeDialog = (0, react_1.useCallback)((id) => {
@@ -1,2 +1,5 @@
1
1
  import { ConfirmDialogOptions, ConfirmResult } from '@headless-adminapp/core/experience/dialog';
2
+ /**
3
+ * @zero-deps
4
+ */
2
5
  export declare function useOpenConfirmDialog(): (options: Omit<ConfirmDialogOptions, "type" | "onConfirm" | "onDismiss" | "onCancel">) => Promise<ConfirmResult | null>;
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useOpenConfirmDialog = useOpenConfirmDialog;
4
4
  const react_1 = require("react");
5
5
  const useOpenDialog_1 = require("./useOpenDialog");
6
+ /**
7
+ * @zero-deps
8
+ */
6
9
  function useOpenConfirmDialog() {
7
10
  const openDialog = (0, useOpenDialog_1.useOpenDialog)();
8
11
  const openConfirmDialog = (0, react_1.useCallback)((options) => {
@@ -1,4 +1,7 @@
1
1
  import { DialogOptions } from '@headless-adminapp/core/experience/dialog';
2
+ /**
3
+ * @zero-deps
4
+ */
2
5
  export declare function useOpenDialog(): (options: DialogOptions) => {
3
6
  id: string;
4
7
  close: () => void;
@@ -6,6 +6,9 @@ const uuid_1 = require("uuid");
6
6
  const context_1 = require("../../mutable/context");
7
7
  const context_2 = require("../context");
8
8
  const useCloseDialog_1 = require("./useCloseDialog");
9
+ /**
10
+ * @zero-deps
11
+ */
9
12
  function useOpenDialog() {
10
13
  const setValue = (0, context_1.useContextSetValue)(context_2.DialogContext);
11
14
  const closeDialog = (0, useCloseDialog_1.useCloseDialog)();
@@ -8,6 +8,9 @@ export declare function useContextValue<T>(context: React.Context<ContextValue<T
8
8
  current: T;
9
9
  };
10
10
  export declare function useContextSelector<T, R>(context: React.Context<ContextValue<T>>, selector: (state: T) => R): R;
11
+ /**
12
+ * @zero-deps
13
+ */
11
14
  export declare function useContextSetValue<T>(context: React.Context<ContextValue<T>>): (value: Partial<T> | ((state: T) => Partial<T>)) => void;
12
15
  type Setter<T, U extends unknown[]> = (setValue: ContextValue<T>['setValue']) => (...args: U) => void;
13
16
  export declare function useContextValueSetter<T, U extends unknown[]>(context: React.Context<ContextValue<T>>, setter: Setter<T, U>): (...args: U) => void;
@@ -33,6 +33,9 @@ function useContextSelector(context, selector) {
33
33
  }
34
34
  return (0, exports.useContextSelectorInternal)(contextValue, selector);
35
35
  }
36
+ /**
37
+ * @zero-deps
38
+ */
36
39
  function useContextSetValue(context) {
37
40
  const contextValue = (0, react_1.useContext)(context);
38
41
  if (!contextValue) {
@@ -1,2 +1,2 @@
1
1
  import { OpenFormOptions } from '@headless-adminapp/core/experience/command';
2
- export declare function useOpenForm(): (options: OpenFormOptions) => void;
2
+ export declare function useOpenForm(): (options: OpenFormOptions) => Promise<void>;
@@ -8,7 +8,7 @@ const useRouteResolver_1 = require("../../route/hooks/useRouteResolver");
8
8
  function useOpenForm() {
9
9
  const routeResolver = (0, useRouteResolver_1.useRouteResolver)();
10
10
  const router = (0, hooks_1.useRouter)();
11
- return (0, react_1.useCallback)((options) => {
11
+ return (0, react_1.useCallback)(async (options) => {
12
12
  const path = routeResolver({
13
13
  logicalName: options.logicalName,
14
14
  type: app_1.PageType.EntityForm,
@@ -29,7 +29,7 @@ function useOpenForm() {
29
29
  };
30
30
  }
31
31
  if (options.replace) {
32
- router.replace(path, {
32
+ await router.replace(path, {
33
33
  state: {
34
34
  ...router.getState(),
35
35
  ...state,
@@ -37,7 +37,7 @@ function useOpenForm() {
37
37
  });
38
38
  }
39
39
  else {
40
- router.push(path, {
40
+ await router.push(path, {
41
41
  state,
42
42
  });
43
43
  }
@@ -0,0 +1 @@
1
+ export declare const UnsavedChangesRouteGuard: () => null;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsavedChangesRouteGuard = void 0;
4
+ const useUnsavedChangesRouteGuard_1 = require("./useUnsavedChangesRouteGuard");
5
+ const UnsavedChangesRouteGuard = () => {
6
+ (0, useUnsavedChangesRouteGuard_1.useUnsavedChangesRouteGuard)();
7
+ return null;
8
+ };
9
+ exports.UnsavedChangesRouteGuard = UnsavedChangesRouteGuard;
@@ -0,0 +1,3 @@
1
+ export { getUnsavedChangesInfo, setUnsavedChangesInfo, } from './unsavedChangesInfo';
2
+ export { useUnsavedChangesRouteGuard } from './useUnsavedChangesRouteGuard';
3
+ export { UnsavedChangesRouteGuard } from './UnsavedChangesRouteGuard';
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsavedChangesRouteGuard = exports.useUnsavedChangesRouteGuard = exports.setUnsavedChangesInfo = exports.getUnsavedChangesInfo = void 0;
4
+ var unsavedChangesInfo_1 = require("./unsavedChangesInfo");
5
+ Object.defineProperty(exports, "getUnsavedChangesInfo", { enumerable: true, get: function () { return unsavedChangesInfo_1.getUnsavedChangesInfo; } });
6
+ Object.defineProperty(exports, "setUnsavedChangesInfo", { enumerable: true, get: function () { return unsavedChangesInfo_1.setUnsavedChangesInfo; } });
7
+ var useUnsavedChangesRouteGuard_1 = require("./useUnsavedChangesRouteGuard");
8
+ Object.defineProperty(exports, "useUnsavedChangesRouteGuard", { enumerable: true, get: function () { return useUnsavedChangesRouteGuard_1.useUnsavedChangesRouteGuard; } });
9
+ var UnsavedChangesRouteGuard_1 = require("./UnsavedChangesRouteGuard");
10
+ Object.defineProperty(exports, "UnsavedChangesRouteGuard", { enumerable: true, get: function () { return UnsavedChangesRouteGuard_1.UnsavedChangesRouteGuard; } });
@@ -0,0 +1,6 @@
1
+ export interface UnsavedChangesInfo {
2
+ title: string;
3
+ message: string;
4
+ }
5
+ export declare function getUnsavedChangesInfo(): UnsavedChangesInfo | null;
6
+ export declare function setUnsavedChangesInfo(info: UnsavedChangesInfo | null): void;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getUnsavedChangesInfo = getUnsavedChangesInfo;
4
+ exports.setUnsavedChangesInfo = setUnsavedChangesInfo;
5
+ let unsavedChangesInfo = null;
6
+ function getUnsavedChangesInfo() {
7
+ return unsavedChangesInfo;
8
+ }
9
+ function setUnsavedChangesInfo(info) {
10
+ unsavedChangesInfo = info;
11
+ }
@@ -0,0 +1 @@
1
+ export declare function useUnsavedChangesRouteGuard(): null;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useUnsavedChangesRouteGuard = useUnsavedChangesRouteGuard;
4
+ const dialog_1 = require("@headless-adminapp/app/dialog");
5
+ const route_1 = require("@headless-adminapp/app/route");
6
+ const react_1 = require("react");
7
+ const unsavedChangesInfo_1 = require("./unsavedChangesInfo");
8
+ function useUnsavedChangesRouteGuard() {
9
+ const router = (0, route_1.useRouter)();
10
+ const openConfirmDialog = (0, dialog_1.useOpenConfirmDialog)();
11
+ (0, react_1.useEffect)(() => {
12
+ const guard = async () => {
13
+ const unsavedChangesInfo = (0, unsavedChangesInfo_1.getUnsavedChangesInfo)();
14
+ if (!unsavedChangesInfo) {
15
+ return true;
16
+ }
17
+ const confirmResult = await openConfirmDialog({
18
+ title: unsavedChangesInfo.title,
19
+ text: unsavedChangesInfo.message,
20
+ confirmButtonLabel: 'Leave',
21
+ cancelButtonLabel: 'Stay',
22
+ });
23
+ if (!confirmResult?.confirmed) {
24
+ return false;
25
+ }
26
+ (0, unsavedChangesInfo_1.setUnsavedChangesInfo)(null);
27
+ return true;
28
+ };
29
+ router.registerGuard(guard);
30
+ return () => {
31
+ router.unregisterGuard(guard);
32
+ };
33
+ }, [router, openConfirmDialog]);
34
+ (0, react_1.useEffect)(() => {
35
+ function handleBeforeUnload(event) {
36
+ const unsavedChangesInfo = (0, unsavedChangesInfo_1.getUnsavedChangesInfo)();
37
+ if (unsavedChangesInfo) {
38
+ event.preventDefault();
39
+ event.returnValue = unsavedChangesInfo.message;
40
+ }
41
+ }
42
+ window.addEventListener('beforeunload', handleBeforeUnload);
43
+ return () => {
44
+ window.removeEventListener('beforeunload', handleBeforeUnload);
45
+ };
46
+ }, []);
47
+ return null;
48
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-adminapp/app",
3
- "version": "1.4.20",
3
+ "version": "1.4.21",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -38,5 +38,5 @@
38
38
  "uuid": "11.0.3",
39
39
  "yup": "^1.4.0"
40
40
  },
41
- "gitHead": "be1677e04dcd2ac91082078c0b59f81b0799d469"
41
+ "gitHead": "2a993d2eb239ff24ffd73df81aba26240aaf1b2f"
42
42
  }
@@ -5,16 +5,19 @@ export type InternalIsRouteActive = (path: string, item: NavPageItem) => boolean
5
5
  interface NavigateOptions {
6
6
  state?: any;
7
7
  }
8
+ export type GuardFn = () => boolean | Promise<boolean>;
8
9
  export interface RouterInstance {
9
- back: () => void;
10
- forward: () => void;
11
- push(href: string, options?: NavigateOptions): void;
12
- replace(href: string, options?: NavigateOptions): void;
10
+ back: () => Promise<void>;
11
+ forward: () => Promise<void>;
12
+ push(href: string, options?: NavigateOptions): Promise<void>;
13
+ replace(href: string, options?: NavigateOptions): Promise<void>;
13
14
  prefetch(href: string): void;
14
15
  setState(state: any): void;
15
16
  setState(key: string, state: any): void;
16
17
  getState<T = any>(): T;
17
18
  getState<T = any>(key: string): T | undefined;
19
+ registerGuard(fn: GuardFn): void;
20
+ unregisterGuard(fn: GuardFn): void;
18
21
  }
19
22
  export declare const RouterContext: import("react").Context<RouterInstance>;
20
23
  export declare const RouterPathnameContext: import("react").Context<string>;