Compare commits
4 Commits
f365d7b0e0
...
a748a39d2f
| Author | SHA1 | Date | |
|---|---|---|---|
| a748a39d2f | |||
| 8928432fe0 | |||
| 6ff395e9be | |||
| 00e5a70aef |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
|||||||
# @warkypublic/zustandsyncstore
|
# @warkypublic/zustandsyncstore
|
||||||
|
|
||||||
|
## 0.0.40
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 8928432: feat(Former): add keep open functionality and update onClose behavior
|
||||||
|
|
||||||
|
## 0.0.39
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 00e5a70: feat(Former): enhance state management with additional callbacks and state retrieval
|
||||||
|
|
||||||
## 0.0.38
|
## 0.0.38
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@warkypublic/oranguru",
|
"name": "@warkypublic/oranguru",
|
||||||
"author": "Warky Devs",
|
"author": "Warky Devs",
|
||||||
"version": "0.0.38",
|
"version": "0.0.40",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/lib.d.ts",
|
"types": "./dist/lib.d.ts",
|
||||||
"main": "./dist/lib.cjs.js",
|
"main": "./dist/lib.cjs.js",
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ import { newUUID } from '@warkypublic/artemis-kit';
|
|||||||
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
import { createSyncStore } from '@warkypublic/zustandsyncstore';
|
||||||
import { produce } from 'immer';
|
import { produce } from 'immer';
|
||||||
|
|
||||||
import type { FormerProps, FormerState } from './Former.types';
|
import type { FormerProps, FormerState, FormStateAndProps } from './Former.types';
|
||||||
|
|
||||||
const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
||||||
FormerState<any> & Partial<FormerProps<any>>,
|
FormerState<any> & Partial<FormerProps<any>>,
|
||||||
FormerProps<any>
|
FormerProps<any>
|
||||||
>(
|
>(
|
||||||
(set, get) => ({
|
(set, get) => ({
|
||||||
|
getAllState: () => {
|
||||||
|
return get() as FormStateAndProps<any>;
|
||||||
|
},
|
||||||
getState: (key) => {
|
getState: (key) => {
|
||||||
const current = get();
|
const current = get();
|
||||||
return current?.[key];
|
return current?.[key];
|
||||||
@@ -26,17 +29,19 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
|||||||
keyValue
|
keyValue
|
||||||
);
|
);
|
||||||
if (get().afterGet) {
|
if (get().afterGet) {
|
||||||
data = await get().afterGet!({ ...data });
|
data = await get().afterGet!({ ...data }, get());
|
||||||
}
|
}
|
||||||
set({ loading: false, values: data });
|
set({ loading: false, values: data });
|
||||||
get().onChange?.(data);
|
get().onChange?.(data, get());
|
||||||
}
|
}
|
||||||
if (reset && get().getFormMethods) {
|
if (reset && get().getFormMethods) {
|
||||||
const formMethods = get().getFormMethods!();
|
const formMethods = get().getFormMethods!();
|
||||||
formMethods.reset();
|
formMethods.reset();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
set({ error: (e as Error)?.message ?? e, loading: false });
|
const errorMessage = (e as Error)?.message ?? e;
|
||||||
|
set({ error: errorMessage, loading: false });
|
||||||
|
get().onError?.(errorMessage, get());
|
||||||
}
|
}
|
||||||
set({ loading: false });
|
set({ loading: false });
|
||||||
},
|
},
|
||||||
@@ -66,7 +71,7 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
|||||||
let data = formMethods.getValues();
|
let data = formMethods.getValues();
|
||||||
|
|
||||||
if (get().beforeSave) {
|
if (get().beforeSave) {
|
||||||
const newData = await get().beforeSave!(data);
|
const newData = await get().beforeSave!(data, get());
|
||||||
data = newData;
|
data = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +81,9 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
|||||||
data = newdata;
|
data = newdata;
|
||||||
},
|
},
|
||||||
(errors) => {
|
(errors) => {
|
||||||
set({ error: errors.root?.message || 'Validation errors', loading: false });
|
const errorMessage = errors.root?.message || 'Validation errors';
|
||||||
|
set({ error: errorMessage, loading: false });
|
||||||
|
get().onError?.(errorMessage, get());
|
||||||
exit = true;
|
exit = true;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -107,29 +114,49 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
|||||||
data,
|
data,
|
||||||
keyValue
|
keyValue
|
||||||
);
|
);
|
||||||
|
const newData = { ...data, ...savedData }; //Merge what we had. In case the API doesn't return all fields, we don't want to lose them
|
||||||
if (get().afterSave) {
|
if (get().afterSave) {
|
||||||
await get().afterSave!(savedData);
|
await get().afterSave!(newData, get());
|
||||||
}
|
}
|
||||||
set({ loading: false, values: savedData });
|
|
||||||
get().onChange?.(savedData);
|
if (keepOpen) {
|
||||||
formMethods.reset(savedData); //reset with saved data to clear dirty state
|
const keyName = get()?.uniqueKeyField || 'id';
|
||||||
if (!keepOpen) {
|
const clearedData = { ...newData };
|
||||||
get().onClose?.(savedData);
|
delete clearedData[keyName];
|
||||||
|
set({ loading: false, values: clearedData });
|
||||||
|
get().onChange?.(clearedData, get());
|
||||||
|
formMethods.reset(clearedData);
|
||||||
|
return newData;
|
||||||
}
|
}
|
||||||
return savedData;
|
|
||||||
|
set({ loading: false, values: newData });
|
||||||
|
get().onChange?.(newData, get());
|
||||||
|
formMethods.reset(newData); //reset with saved data to clear dirty state
|
||||||
|
get().onClose?.(newData);
|
||||||
|
return newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keepOpen) {
|
||||||
|
const keyName = get()?.uniqueKeyField || 'id';
|
||||||
|
const clearedData = { ...data };
|
||||||
|
delete clearedData[keyName];
|
||||||
|
set({ loading: false, values: clearedData });
|
||||||
|
formMethods.reset(clearedData);
|
||||||
|
get().onChange?.(clearedData, get());
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
set({ loading: false, values: data });
|
set({ loading: false, values: data });
|
||||||
formMethods.reset(data); //reset with saved data to clear dirty state
|
formMethods.reset(data); //reset with saved data to clear dirty state
|
||||||
get().onChange?.(data);
|
get().onChange?.(data, get());
|
||||||
if (!keepOpen) {
|
get().onClose?.(data);
|
||||||
get().onClose?.(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
set({ error: (e as Error)?.message ?? e, loading: false });
|
const errorMessage = (e as Error)?.message ?? e;
|
||||||
|
set({ error: errorMessage, loading: false });
|
||||||
|
get().onError?.(errorMessage, get());
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -181,20 +208,20 @@ const { Provider: FormerProvider, useStore: useFormerStore } = createSyncStore<
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: !id ? newUUID() : id,
|
id: !id ? newUUID() : id,
|
||||||
onClose: () => {
|
onClose: (data?: any) => {
|
||||||
const dirty = useStoreApi.getState().dirty;
|
const dirty = useStoreApi.getState().dirty;
|
||||||
const setState = useStoreApi.getState().setState;
|
const setState = useStoreApi.getState().setState;
|
||||||
if (dirty) {
|
if (dirty) {
|
||||||
if (confirm('You have unsaved changes. Are you sure you want to close?')) {
|
if (confirm('You have unsaved changes. Are you sure you want to close?')) {
|
||||||
if (onClose) {
|
if (onClose) {
|
||||||
onClose();
|
onClose(data);
|
||||||
} else {
|
} else {
|
||||||
setState('opened', false);
|
setState('opened', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (onClose) {
|
if (onClose) {
|
||||||
onClose();
|
onClose(data);
|
||||||
} else {
|
} else {
|
||||||
setState('opened', false);
|
setState('opened', false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
|||||||
ref: any
|
ref: any
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
|
getAllState,
|
||||||
getState,
|
getState,
|
||||||
onChange,
|
onChange,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -26,6 +27,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
|||||||
values,
|
values,
|
||||||
wrapper,
|
wrapper,
|
||||||
} = useFormerStore((state) => ({
|
} = useFormerStore((state) => ({
|
||||||
|
getAllState: state.getAllState,
|
||||||
getState: state.getState,
|
getState: state.getState,
|
||||||
onChange: state.onChange,
|
onChange: state.onChange,
|
||||||
onClose: state.onClose,
|
onClose: state.onClose,
|
||||||
@@ -54,7 +56,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
|||||||
() => ({
|
() => ({
|
||||||
close: async () => {
|
close: async () => {
|
||||||
//console.log('close called');
|
//console.log('close called');
|
||||||
onClose?.();
|
onClose?.(getState('values'));
|
||||||
setState('opened', false);
|
setState('opened', false);
|
||||||
},
|
},
|
||||||
getValue: () => {
|
getValue: () => {
|
||||||
@@ -67,7 +69,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
|||||||
return await save();
|
return await save();
|
||||||
},
|
},
|
||||||
setValue: (value: T) => {
|
setValue: (value: T) => {
|
||||||
onChange?.(value);
|
onChange?.(value, getAllState());
|
||||||
},
|
},
|
||||||
show: async () => {
|
show: async () => {
|
||||||
//console.log('show called');
|
//console.log('show called');
|
||||||
@@ -78,7 +80,7 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
|||||||
return await validate();
|
return await validate();
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[getState, onChange, validate, save, reset, setState, onClose, onOpen]
|
[getState, getAllState, onChange, validate, save, reset, setState, onClose, onOpen]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -97,7 +99,19 @@ const FormerInner = forwardRef<FormerRef<any>, Partial<FormerProps<any>> & Props
|
|||||||
return (
|
return (
|
||||||
<FormProvider {...formMethods}>
|
<FormProvider {...formMethods}>
|
||||||
{typeof wrapper === 'function' ? (
|
{typeof wrapper === 'function' ? (
|
||||||
wrapper(<FormerLayout>{props.children}</FormerLayout>, opened ??false, onClose ?? (() => {setState('opened', false)}), onOpen ?? (() => {setState('opened', true)}), getState)
|
wrapper(
|
||||||
|
<FormerLayout>{props.children}</FormerLayout>,
|
||||||
|
opened ?? false,
|
||||||
|
onClose ??
|
||||||
|
(() => {
|
||||||
|
setState('opened', false);
|
||||||
|
}),
|
||||||
|
onOpen ??
|
||||||
|
(() => {
|
||||||
|
setState('opened', true);
|
||||||
|
}),
|
||||||
|
getState
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<FormerLayout>{props.children || null}</FormerLayout>
|
<FormerLayout>{props.children || null}</FormerLayout>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ export type FormerAPICallType<T extends FieldValues = any> = (
|
|||||||
) => Promise<T>;
|
) => Promise<T>;
|
||||||
|
|
||||||
export interface FormerProps<T extends FieldValues = any> {
|
export interface FormerProps<T extends FieldValues = any> {
|
||||||
afterGet?: (data: T) => Promise<T> | void;
|
afterGet?: (data: T, state: Partial<FormStateAndProps<T>>) => Promise<T> | void;
|
||||||
afterSave?: (data: T) => Promise<void> | void;
|
afterSave?: (data: T, state: Partial<FormStateAndProps<T>>) => Promise<void> | void;
|
||||||
beforeSave?: (data: T) => Promise<T> | T;
|
beforeSave?: (data: T, state: Partial<FormStateAndProps<T>>) => Promise<T> | T;
|
||||||
dirty?: boolean;
|
dirty?: boolean;
|
||||||
disableHTMlForm?: boolean;
|
disableHTMlForm?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
keepOpen?: boolean;
|
keepOpen?: boolean;
|
||||||
layout?: {
|
layout?: {
|
||||||
buttonArea?: "bottom" | "none" | "top";
|
buttonArea?: 'bottom' | 'none' | 'top';
|
||||||
buttonAreaGroupProps?: GroupProps;
|
buttonAreaGroupProps?: GroupProps;
|
||||||
closeButtonProps?: ButtonProps;
|
closeButtonProps?: ButtonProps;
|
||||||
closeButtonTitle?: React.ReactNode;
|
closeButtonTitle?: React.ReactNode;
|
||||||
@@ -31,14 +31,15 @@ export interface FormerProps<T extends FieldValues = any> {
|
|||||||
renderTop?: FormerSectionRender<T>;
|
renderTop?: FormerSectionRender<T>;
|
||||||
saveButtonProps?: ButtonProps;
|
saveButtonProps?: ButtonProps;
|
||||||
saveButtonTitle?: React.ReactNode;
|
saveButtonTitle?: React.ReactNode;
|
||||||
|
showKeepOpenSwitch?: boolean;
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
onAPICall?: FormerAPICallType<T>;
|
onAPICall?: FormerAPICallType<T>;
|
||||||
onCancel?: () => void;
|
onCancel?: () => void;
|
||||||
onChange?: (value: T) => void;
|
onChange?: (value: T, state: Partial<FormStateAndProps<T>>) => void;
|
||||||
onClose?: (data?: T) => void;
|
onClose?: (data?: T | undefined) => void;
|
||||||
onConfirmDelete?: (values?: T) => Promise<boolean>;
|
onConfirmDelete?: (values?: T) => Promise<boolean>;
|
||||||
|
onError?: (error: Error | string, state: Partial<FormStateAndProps<T>>) => void;
|
||||||
onOpen?: (data?: T) => void;
|
onOpen?: (data?: T) => void;
|
||||||
opened?: boolean;
|
opened?: boolean;
|
||||||
primeData?: T;
|
primeData?: T;
|
||||||
@@ -62,15 +63,16 @@ export interface FormerRef<T extends FieldValues = any> {
|
|||||||
|
|
||||||
export type FormerSectionRender<T extends FieldValues = any> = (
|
export type FormerSectionRender<T extends FieldValues = any> = (
|
||||||
children: React.ReactNode,
|
children: React.ReactNode,
|
||||||
opened: boolean ,
|
opened: boolean,
|
||||||
onClose: ((data?: T) => void),
|
onClose: (data?: T) => void,
|
||||||
onOpen: ((data?: T) => void) ,
|
onOpen: (data?: T) => void,
|
||||||
getState: FormerState<T>['getState']
|
getState: FormerState<T>['getState']
|
||||||
) => React.ReactNode;
|
) => React.ReactNode;
|
||||||
|
|
||||||
export interface FormerState<T extends FieldValues = any> {
|
export interface FormerState<T extends FieldValues = any> {
|
||||||
deleteConfirmed?: boolean;
|
deleteConfirmed?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
getAllState: () => FormStateAndProps<T>;
|
||||||
getFormMethods?: () => UseFormReturn<any, any>;
|
getFormMethods?: () => UseFormReturn<any, any>;
|
||||||
getState: <K extends keyof FormStateAndProps<T>>(key: K) => FormStateAndProps<T>[K];
|
getState: <K extends keyof FormStateAndProps<T>>(key: K) => FormStateAndProps<T>[K];
|
||||||
load: (reset?: boolean) => Promise<void>;
|
load: (reset?: boolean) => Promise<void>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Group, Tooltip } from '@mantine/core';
|
import { Button, Group, Switch, Tooltip } from '@mantine/core';
|
||||||
import { IconDeviceFloppy, IconX } from '@tabler/icons-react';
|
import { IconDeviceFloppy, IconX } from '@tabler/icons-react';
|
||||||
|
|
||||||
import { useFormerStore } from './Former.store';
|
import { useFormerStore } from './Former.store';
|
||||||
@@ -9,21 +9,29 @@ export const FormerButtonArea = () => {
|
|||||||
closeButtonProps,
|
closeButtonProps,
|
||||||
closeButtonTitle,
|
closeButtonTitle,
|
||||||
dirty,
|
dirty,
|
||||||
|
getState,
|
||||||
|
keepOpen,
|
||||||
onClose,
|
onClose,
|
||||||
request,
|
request,
|
||||||
save,
|
save,
|
||||||
saveButtonProps,
|
saveButtonProps,
|
||||||
saveButtonTitle,
|
saveButtonTitle,
|
||||||
|
setState,
|
||||||
|
showKeepOpenSwitch,
|
||||||
} = useFormerStore((state) => ({
|
} = useFormerStore((state) => ({
|
||||||
buttonAreaGroupProps: state.layout?.buttonAreaGroupProps,
|
buttonAreaGroupProps: state.layout?.buttonAreaGroupProps,
|
||||||
closeButtonProps: state.layout?.closeButtonProps,
|
closeButtonProps: state.layout?.closeButtonProps,
|
||||||
closeButtonTitle: state.layout?.closeButtonTitle,
|
closeButtonTitle: state.layout?.closeButtonTitle,
|
||||||
dirty: state.dirty,
|
dirty: state.dirty,
|
||||||
|
getState: state.getState,
|
||||||
|
keepOpen: state.keepOpen,
|
||||||
onClose: state.onClose,
|
onClose: state.onClose,
|
||||||
request: state.request,
|
request: state.request,
|
||||||
save: state.save,
|
save: state.save,
|
||||||
saveButtonProps: state.layout?.saveButtonProps,
|
saveButtonProps: state.layout?.saveButtonProps,
|
||||||
saveButtonTitle: state.layout?.saveButtonTitle,
|
saveButtonTitle: state.layout?.saveButtonTitle,
|
||||||
|
setState: state.setState,
|
||||||
|
showKeepOpenSwitch: state.layout?.showKeepOpenSwitch,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const disabledSave =
|
const disabledSave =
|
||||||
@@ -47,12 +55,19 @@ export const FormerButtonArea = () => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
{...closeButtonProps}
|
{...closeButtonProps}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose(getState('values'));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{closeButtonTitle || 'Close'}
|
{closeButtonTitle || 'Close'}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
{showKeepOpenSwitch && (
|
||||||
|
<Switch
|
||||||
|
checked={keepOpen}
|
||||||
|
label="Keep Open"
|
||||||
|
onChange={(event) => setState('keepOpen', event.currentTarget.checked)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={
|
label={
|
||||||
disabledSave ? (
|
disabledSave ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user