@chris-c-brine/form-dialog 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,20 +1,34 @@
1
1
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
- import { createContext, useContext, useState, useEffect, useCallback, useMemo, Fragment, useRef, memo, Children } from 'react';
2
+ import { createContext, useContext, Fragment, useState, useEffect, useRef, useCallback, memo, useMemo, Children } from 'react';
3
3
  import { Dialog, DialogTitle, DialogContent, DialogActions, Paper, IconButton, Button, useTheme as useTheme$1, Box, CircularProgress, Badge, Grid } from '@mui/material';
4
- import { FormContainer, useFormContext, useFormState } from 'react-hook-form-mui';
4
+ import { useFormContext, FormContainer, useFormState } from 'react-hook-form-mui';
5
+ import { isEqualWith, isObject, isNumber, isEqual, isEmpty, omit, merge, debounce } from 'lodash';
6
+ import { Refresh, Close, Save } from '@mui/icons-material';
5
7
  import { useTheme } from '@mui/material/styles';
6
- import { merge, isEqualWith, isObject, isNumber, isEqual, isEmpty, omit, debounce } from 'lodash';
7
- import { Close, Refresh, Save } from '@mui/icons-material';
8
8
  import { create } from 'zustand';
9
9
  import { createJSONStorage, persist } from 'zustand/middleware';
10
- import { useFormContext as useFormContext$1 } from 'react-hook-form';
11
10
 
12
11
  /**
13
12
  * A component for rendering a modal dialog with an optional blackout effect.
14
13
  */
15
- const BlackoutDialog = ({ open = false, blackout = false, id = "blackout-dialog", children, sx, keepMounted = true, ...props }) => {
14
+ const BlackoutDialog = ({ open = false, blackout = false, id = "blackout-dialog", children, sx, ...props }) => {
16
15
  const sxProps = blackout ? { ...sx, backgroundColor: "black" } : sx;
17
- return (jsx(Dialog, { open: open, id: id, keepMounted: keepMounted, ...props, sx: sxProps, children: children }));
16
+ /**
17
+ * 1) When you open a dialog via a button, the button's ancestor gets aria-hidden, presumably so that
18
+ * screen readers (SR) can't interact with it while the dialog is open.
19
+ * 2) When you close the dialog, the aria-hidden is removed so that SRs can interact with the button again.
20
+ * 3) Also, when closing, the focus is moved back on the opener button.
21
+ *
22
+ * Now comes the important part. You get Blocked aria-hidden on a <div> only when 3 happens before 2. In this case,
23
+ * as the error message explains, we are trying to place focus somewhere where SRs don't have access, which is
24
+ * a No No, and browsers correctly warn about it.
25
+ *
26
+ * This ordering is dependent on the closeAfterTransition prop on the MUI Dialog. When false and user triggers
27
+ * dialog close, aria-hidden is removed (2) and then focus is placed on the opener button (3). Meaning there is no
28
+ * issue. You can see it in action here. However, when it's true (the default), the order is the other way around,
29
+ * and you get the warning.
30
+ */
31
+ return (jsx(Dialog, { closeAfterTransition: false, open: open, id: id, ...props, sx: sxProps, children: children }));
18
32
  };
19
33
 
20
34
  /**
@@ -36,27 +50,11 @@ const BlackoutDialog = ({ open = false, blackout = false, id = "blackout-dialog"
36
50
  * consistent styling and behavior.
37
51
  *
38
52
  * @example
39
- * // Basic usage with all sections
40
- * <BaseDialog
41
- * open={open}
42
- * onClose={handleClose}
43
- * title="Confirmation"
44
- * actions={
45
- * <>
46
- * <Button onClick={handleCancel}>Cancel</Button>
47
- * <Button onClick={handleConfirm}>Confirm</Button>
48
- * </>
49
- * }
50
- * >
51
- * Are you sure you want to proceed?
52
- * </BaseDialog>
53
- *
54
- * @example
55
53
  * // Customized with section props
56
54
  * <BaseDialog
57
55
  * open={isOpen}
58
56
  * title="Advanced Settings"
59
- * titleProps={{ sx: { bgcolor: 'primary.main', color: 'white' } }}
57
+ * titleProps={{ sx: { backgroundColor: 'primary.main', color: 'white' } }}
60
58
  * contentProps={{ sx: { p: 3 } }}
61
59
  * actionsProps={{ sx: { justifyContent: 'space-between' } }}
62
60
  * actions={<SettingsDialogActions />}
@@ -74,158 +72,15 @@ const BaseDialog = ({ title = null, closeButton, titleProps, children = null, co
74
72
  ...sx,
75
73
  }, ...props, children: [title && jsx(DialogTitle, { ...titleProps, children: title }), " ", closeButton, children && jsx(DialogContent, { ...contentProps, children: children }), actions && jsx(DialogActions, { ...actionsProps, children: actions })] }));
76
74
 
77
- /**
78
- * A component that combines a Material UI Paper with a form container
79
- *
80
- * PaperForm wraps form content in an elevated paper surface, providing
81
- * visual distinction and a clean container for form elements. It integrates
82
- * seamlessly with react-hook-form by using FormContainer internally.
83
- *
84
- * This component is useful for creating standalone form surfaces outside of dialogs,
85
- * such as on pages or within cards. It supports all Paper props for styling
86
- * while handling form state management through FormContainer.
87
- *
88
- * @example
89
- * // Basic usage
90
- * <PaperForm
91
- * formProps={{
92
- * defaultValues: { name: "", email: "" },
93
- * onSuccess: handleSubmit
94
- * }}
95
- * elevation={3}
96
- * >
97
- * <TextFieldElement name="name" label="Name" />
98
- * <TextFieldElement name="email" label="Email" />
99
- * <Button type="submit">Submit</Button>
100
- * </PaperForm>
101
- *
102
- * @example
103
- * // With custom styling and validation
104
- * <PaperForm
105
- * formProps={{
106
- * defaultValues: formDefaultValues,
107
- * onSuccess: handleSubmit,
108
- * onError: handleError,
109
- * }}
110
- * sx={{
111
- * padding: 3,
112
- * borderRadius: 2,
113
- * backgroundColor: 'background.paper',
114
- * }}
115
- * elevation={2}
116
- * >
117
- * <FormFields />
118
- * <FormDialogActions submitProps={{ children: "Save Profile" }} />
119
- * </PaperForm>
120
- *
121
- * @template T - The type of form values being handled
122
- */
123
- const PaperForm = ({ children, formProps, ...paperProps }) => (jsx(Paper, { ...paperProps, children: jsx(FormContainer, { ...formProps, children: children }) }));
124
-
125
75
  const FormDialogContext = createContext(undefined);
126
76
  FormDialogContext.displayName = "FormDialogContext";
127
77
 
128
- // Create a context that will hold the parent theme
129
- const ParentThemeContext = createContext(null);
130
- const useParentTheme = () => useContext(ParentThemeContext);
131
- const ThemeBridge = ({ children }) => {
132
- const parentTheme = useTheme();
133
- const [themeSnapshot, setThemeSnapshot] = useState(parentTheme);
134
- // Update the snapshot when the parent theme changes
135
- useEffect(() => {
136
- setThemeSnapshot(parentTheme);
137
- }, [parentTheme]);
138
- // Pass the parent theme to our components
139
- return (jsx(ParentThemeContext.Provider, { value: themeSnapshot, children: children }));
140
- };
141
-
142
- /**
143
- * Context provider component for form dialog state management
144
- *
145
- * FormDialogProvider creates and manages shared state for form dialogs,
146
- * allowing child components to access dialog state and controls through
147
- * the useFormDialog hook.
148
- *
149
- * This provider handles:
150
- * - Dialog open/close state
151
- * - Form-wide disabled state
152
- * - Dialog close handler passing
153
- * - Theme consistency via ThemeBridge
154
- *
155
- * Use this provider to wrap form components that need to share state
156
- * and disabled status, such as dialogs with multiple form fields
157
- * and action buttons that need to be coordinated.
158
- *
159
- * @example
160
- * // Basic usage wrapping a form
161
- * <FormDialogProvider>
162
- * <FormContainer onSuccess={handleSubmit}>
163
- * <TextFieldElement name="name" />
164
- * <FormDialogActions />
165
- * </FormContainer>
166
- * </FormDialogProvider>
167
- *
168
- * @example
169
- * // With pre-configured dialog state
170
- * <FormDialogProvider open={isOpen} closeDialog={handleClose}>
171
- * <PaperForm formProps={formProps}>
172
- * <LoginFormFields />
173
- * <FormDialogActions submitProps={{ maxAttempts: 3 }} />
174
- * </PaperForm>
175
- * </FormDialogProvider>
176
- */
177
- const FormDialogProvider = ({ children, ...value }) => {
178
- const [disabled, setDisabled] = useState(false);
179
- return (jsx(ThemeBridge, { children: jsx(FormDialogContext.Provider, { value: { ...value, disabled, setDisabled }, children: children }) }));
180
- };
181
-
182
- /**
183
- * A dialog component specialized for forms with integrated context providers
184
- *
185
- * FormDialog combines Material UI dialog functionality with React Hook Form,
186
- * creating a comprehensive solution for modal forms. It provides context
187
- * providers for form state management, dialog controls, and accessibility.
188
- *
189
- * Key features:
190
- * - Integrates React Hook Form with Material UI dialogs
191
- * - Provides form state management through FormProvider
192
- * - Establishes dialog context through FormDialogProvider
193
- * - Handles form submission and error states
194
- * - Supports loading states for form operations
195
- * - Maintains consistent styling and behavior
196
- *
197
- * Child components can access the form and dialog context through hooks:
198
- * - useFormContext() - Access form methods and state
199
- * - useFormState() - Access form validation state
200
- * - useFormDialog() - Access dialog controls and state
201
- *
202
- */
203
- const FormDialog = ({ formProps, children, open, onClose, ...dialogProps }) => {
204
- const PaperComponent = useCallback((props) => jsx(PaperForm, { formProps: formProps, ...props }), [formProps]);
205
- const baseDialogProps = useMemo(() => merge({
206
- actionsProps: { sx: { pt: 2.5 } },
207
- contentProps: {
208
- sx: {
209
- display: "flex",
210
- justifyContent: "center",
211
- boxSizing: "border-box",
212
- maxHeight: `calc(100vh - 235px)`, // 235 estimate using H4 title & default FormDialogActions
213
- },
214
- },
215
- }, dialogProps), [dialogProps]);
216
- return (jsx(FormDialogProvider, { open: open, closeDialog: onClose, children: jsx(BaseDialog, { PaperComponent: PaperComponent, open: open, onClose: onClose, closeButton: jsx(IconButton, { sx: {
217
- position: "absolute",
218
- top: 8,
219
- right: 8,
220
- }, onClick: (e) => onClose === null || onClose === void 0 ? void 0 : onClose(e, 'escapeKeyDown'), children: jsx(Close, { fontSize: "small", "aria-label": "close-form-dialog" }) }), ...baseDialogProps, children: children }) }));
221
- };
222
-
223
78
  // src/components/dialogs/FormDialog/hooks/useFormDialog.ts
224
79
  /**
225
80
  * Hook for accessing the FormDialog context values and functions
226
81
  *
227
82
  * This hook provides access to the form dialog state and controls managed by
228
- * the FormDialogProvider. It allows components to interact with dialog state,
83
+ * the FormDialogProvider. It allows components to interact with the dialog state,
229
84
  * including open/close status and form-wide disabled state.
230
85
  *
231
86
  * The context provides:
@@ -294,8 +149,20 @@ const deepCompare = (a, b, equalEmpty = false) => {
294
149
  deepCompare.displayName = "deepCompare";
295
150
 
296
151
  function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cancelProps, variant = "iconText" }) {
152
+ // Max Attempts
297
153
  const maxAttempts = (submitProps === null || submitProps === void 0 ? void 0 : submitProps.maxAttempts) || Infinity;
298
154
  const hasMaxAttempts = isFinite(maxAttempts) && maxAttempts > 0;
155
+ // Grid Container
156
+ const gridContainerProps = {
157
+ justifyContent: "space-between",
158
+ minWidth: 275,
159
+ px: 1,
160
+ pb: 1.5,
161
+ columnGap: 1,
162
+ width: "100%",
163
+ ...gridProps,
164
+ };
165
+ // Reset Button
299
166
  let resetChildren = undefined;
300
167
  if (variant == "icon")
301
168
  resetChildren = jsx(Refresh, {});
@@ -306,15 +173,7 @@ function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cance
306
173
  ...(resetChildren ? { children: resetChildren } : {}),
307
174
  ...omit(resetProps, "disabled"),
308
175
  };
309
- const gridContainerProps = {
310
- justifyContent: "space-between",
311
- minWidth: 275,
312
- px: 1,
313
- pb: 1.5,
314
- columnGap: 1,
315
- width: "100%",
316
- ...gridProps,
317
- };
176
+ // Cancel Button
318
177
  let cancelChildren = undefined;
319
178
  if (variant == "icon")
320
179
  cancelChildren = jsx(Close, {});
@@ -324,6 +183,7 @@ function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cance
324
183
  ...(cancelChildren ? { children: cancelChildren } : {}),
325
184
  ...cancelProps,
326
185
  };
186
+ // Submit Button
327
187
  let submitChildren = {};
328
188
  if (variant == "icon")
329
189
  submitChildren = { children: jsx(Fragment$1, {}), altIcon: jsx(Save, {}) };
@@ -333,14 +193,22 @@ function applyDefaultFormDialogProps({ resetProps, submitProps, gridProps, cance
333
193
  ...submitChildren,
334
194
  ...submitProps,
335
195
  };
336
- return {
337
- resetButtonProps,
338
- gridContainerProps,
339
- cancelButtonProps,
340
- submitButtonProps,
341
- };
196
+ return { resetButtonProps, gridContainerProps, cancelButtonProps, submitButtonProps };
342
197
  }
343
198
 
199
+ // Create a context that will hold the parent theme
200
+ const ParentThemeContext = createContext(null);
201
+ const ThemeBridge = ({ children }) => {
202
+ const parentTheme = useTheme();
203
+ const [themeSnapshot, setThemeSnapshot] = useState(parentTheme);
204
+ // Update the snapshot when the parent theme changes
205
+ useEffect(() => {
206
+ setThemeSnapshot(parentTheme);
207
+ }, [parentTheme]);
208
+ // Pass the parent theme to our components
209
+ return (jsx(ParentThemeContext.Provider, { value: themeSnapshot, children: children }));
210
+ };
211
+
344
212
  const useMaxAttempts = ({ maxAttempts }) => {
345
213
  const { formState, resetField } = useFormContext();
346
214
  const { setDisabled, disabled } = useFormDialog();
@@ -435,36 +303,18 @@ function useOnMount(callback) {
435
303
  * - Only saves changed fields, not the entire form state
436
304
  * - Automatically clears storage when form values match defaults
437
305
  *
438
- * @example
439
- * // In a form component:
440
- * const MyPersistedForm = () => {
441
- * const formMethods = useForm({ defaultValues: { name: '' } });
442
- * // Connect the form to persistence
443
- * usePersistedForm({ formName: 'user-registration' });
444
- *
445
- * return (<FormProvider {...formMethods}>
446
- * <TextFieldElement name="name" label="Name" />
447
- * </FormProvider>);
448
- * }
449
- *
450
- * @example
451
- * // For convenience, use with the PersistForm wrapper component:
452
- * <PersistForm formName="user-profile">
453
- * <ProfileFormFields />
454
- * </PersistForm>
455
- *
456
- * @param props - Configuration options
457
306
  */
458
- const usePersistedForm = ({ formName = "" }) => {
459
- const { setValue, watch, formState } = useFormContext();
307
+ const usePersistForm = ({ formName = "", formContext }) => {
308
+ // const { setValue, watch, formState } = formContext;
460
309
  const { formData, updateFormData, resetFormData } = createFormChangeStore(formName)();
461
- const { open, disabled } = useFormDialog();
310
+ const { disabled } = useFormDialog();
462
311
  const debouncedUpdate = debounce((key, values) => {
463
312
  updateFormData(key, values);
464
313
  }, 200);
465
314
  useEffect(() => {
466
- if (!formName)
315
+ if (!formName || !formContext)
467
316
  return;
317
+ const { watch, formState } = formContext;
468
318
  const subscription = watch((newValues, { name }) => {
469
319
  if (deepCompare(newValues, formState.defaultValues, true)) {
470
320
  resetFormData();
@@ -483,19 +333,18 @@ const usePersistedForm = ({ formName = "" }) => {
483
333
  debouncedUpdate.cancel();
484
334
  };
485
335
  // eslint-disable-next-line react-hooks/exhaustive-deps
486
- }, [watch, debouncedUpdate]);
336
+ }, [formContext, debouncedUpdate]);
487
337
  useOnMount(() => {
488
- if (!isEmpty(formData) && !disabled && !formState.isLoading && open && formName) {
338
+ if (!isEmpty(formData) && !disabled && !(formContext === null || formContext === void 0 ? void 0 : formContext.formState.isLoading) && formName) {
489
339
  setTimeout(() => {
490
340
  Object.entries(formData).forEach(([key, value]) => {
491
- setValue(key, value, { shouldDirty: true, shouldTouch: true });
341
+ formContext === null || formContext === void 0 ? void 0 : formContext.setValue(key, value, { shouldDirty: true, shouldTouch: true });
492
342
  });
493
343
  }, 200);
494
344
  }
495
345
  });
496
346
  };
497
347
 
498
- // src/hooks/useDialog.ts
499
348
  /**
500
349
  * Hook for managing dialog state and providing dialog control functions
501
350
  *
@@ -503,6 +352,8 @@ const usePersistedForm = ({ formName = "" }) => {
503
352
  * functions to open and close the dialog, along with props that can be
504
353
  * spread onto a Material-UI Dialog component.
505
354
  *
355
+ * Restores focus to the opening element, but only after the dialog has fully closed.
356
+ *
506
357
  * @example
507
358
  * // Basic usage
508
359
  * const { openDialog, closeDialog, dialogProps } = useDialog();
@@ -513,20 +364,18 @@ const usePersistedForm = ({ formName = "" }) => {
513
364
  *
514
365
  * @example
515
366
  * // With initial configuration
516
- * const { dialogProps } = useDialog({ keepMounted: false });
517
- *
367
+ * const { dialogProps } = useDialog({ keepMounted: true});
518
368
  *
519
369
  * @param props - Optional configuration options
520
370
  */
521
371
  const useDialog = (props) => {
522
- var _a;
523
372
  const [open, setOpen] = useState(!!(props === null || props === void 0 ? void 0 : props.open));
524
- const onClose = useCallback(() => setOpen(false), []);
373
+ const closeDialog = useCallback(() => setOpen(false), []);
525
374
  const openDialog = useCallback(() => setOpen(true), []);
526
375
  return {
527
- closeDialog: onClose,
376
+ closeDialog,
528
377
  openDialog,
529
- dialogProps: { open, onClose, keepMounted: (_a = props === null || props === void 0 ? void 0 : props.keepMounted) !== null && _a !== void 0 ? _a : true }
378
+ dialogProps: { open, onClose: closeDialog, keepMounted: !!(props === null || props === void 0 ? void 0 : props.keepMounted) }
530
379
  };
531
380
  };
532
381
 
@@ -571,11 +420,143 @@ const useDialog = (props) => {
571
420
  * </PersistForm>
572
421
  * </FormDialog>
573
422
  */
574
- const PersistForm = ({ children, formName }) => {
575
- usePersistedForm({ formName });
423
+ const PersistForm = memo(function ({ children, formName }) {
424
+ const formContext = useFormContext();
425
+ usePersistForm({ formName, formContext });
576
426
  return jsx(Fragment$1, { children: children });
427
+ });
428
+ PersistForm.displayName = "PersistForm";
429
+
430
+ /**
431
+ * A component that combines a Material UI Paper with a form container
432
+ *
433
+ * PaperForm wraps form content in an elevated paper surface, providing
434
+ * visual distinction and a clean container for form elements. It integrates
435
+ * seamlessly with react-hook-form by using FormContainer internally.
436
+ *
437
+ * This component is useful for creating standalone form surfaces outside of dialogs,
438
+ * such as on pages or within cards. It supports all Paper props for styling
439
+ * while handling form state management through FormContainer.
440
+ *
441
+ * @example
442
+ * // Basic usage
443
+ * <PaperForm
444
+ * formProps={{
445
+ * defaultValues: { name: "", email: "" },
446
+ * onSuccess: handleSubmit
447
+ * }}
448
+ * elevation={3}
449
+ * >
450
+ * <TextFieldElement name="name" label="Name" />
451
+ * <TextFieldElement name="email" label="Email" />
452
+ * <Button type="submit">Submit</Button>
453
+ * </PaperForm>
454
+ *
455
+ * @example
456
+ * // With custom styling and validation
457
+ * <PaperForm
458
+ * formProps={{
459
+ * defaultValues: formDefaultValues,
460
+ * onSuccess: handleSubmit,
461
+ * onError: handleError,
462
+ * }}
463
+ * sx={{
464
+ * padding: 3,
465
+ * borderRadius: 2,
466
+ * backgroundColor: 'background.paper',
467
+ * }}
468
+ * elevation={2}
469
+ * >
470
+ * <FormFields />
471
+ * <FormDialogActions submitProps={{ children: "Save Profile" }} />
472
+ * </PaperForm>
473
+ *
474
+ * @template T - The type of form values being handled
475
+ */
476
+ const PaperForm = ({ children, persistKey = "", formProps, ...paperProps }) => (jsx(Paper, { ...paperProps, children: jsx(FormContainer, { ...formProps, children: jsx(PersistForm, { formName: persistKey, children: children }) }) }));
477
+
478
+ /**
479
+ * Context provider component for form dialog state management
480
+ *
481
+ * FormDialogProvider creates and manages shared state for form dialogs,
482
+ * allowing child components to access dialog state and controls through
483
+ * the useFormDialog hook.
484
+ *
485
+ * This provider handles:
486
+ * - Dialog open/close state
487
+ * - Form-wide disabled state
488
+ * - Dialog close handler passing
489
+ * - Theme consistency via ThemeBridge
490
+ *
491
+ * Use this provider to wrap form components that need to share state
492
+ * and disabled status, such as dialogs with multiple form fields
493
+ * and action buttons that need to be coordinated.
494
+ *
495
+ * @example
496
+ * // Basic usage wrapping a form
497
+ * <FormDialogProvider>
498
+ * <FormContainer onSuccess={handleSubmit}>
499
+ * <TextFieldElement name="name" />
500
+ * <FormDialogActions />
501
+ * </FormContainer>
502
+ * </FormDialogProvider>
503
+ *
504
+ * @example
505
+ * // With pre-configured dialog state
506
+ * <FormDialogProvider open={isOpen} closeDialog={handleClose}>
507
+ * <PaperForm formProps={formProps}>
508
+ * <LoginFormFields />
509
+ * <FormDialogActions submitProps={{ maxAttempts: 3 }} />
510
+ * </PaperForm>
511
+ * </FormDialogProvider>
512
+ */
513
+ const FormDialogProvider = ({ children, ...value }) => {
514
+ const [disabled, setDisabled] = useState(false);
515
+ return (jsx(ThemeBridge, { children: jsx(FormDialogContext.Provider, { value: { ...value, disabled, setDisabled }, children: children }) }));
577
516
  };
578
517
 
518
+ /**
519
+ * A dialog component specialized for forms with integrated context providers
520
+ *
521
+ * FormDialog combines Material UI dialog functionality with React Hook Form,
522
+ * creating a comprehensive solution for modal forms. It provides context
523
+ * providers for form state management, dialog controls, and accessibility.
524
+ *
525
+ * Key features:
526
+ * - Integrates React Hook Form with Material UI dialogs
527
+ * - Provides form state management through FormProvider
528
+ * - Establishes dialog context through FormDialogProvider
529
+ * - Handles form submission and error states
530
+ * - Supports loading states for form operations
531
+ * - Maintains consistent styling and behavior
532
+ *
533
+ * Child components can access the form and dialog context through hooks:
534
+ * - useFormContext() - Access form methods and state
535
+ * - useFormState() - Access form validation state
536
+ * - useFormDialog() - Access dialog controls and state
537
+ *
538
+ */
539
+ const FormDialog = function ({ formProps, children, open, onClose, persistKey = "", ...dialogProps }) {
540
+ const PaperComponent = useCallback((props) => jsx(PaperForm, { persistKey: persistKey, formProps: formProps, ...props }), [formProps, persistKey]);
541
+ const baseDialogProps = useMemo(() => merge({
542
+ actionsProps: { sx: { pt: 2.5 } },
543
+ contentProps: {
544
+ sx: {
545
+ display: "flex",
546
+ justifyContent: "center",
547
+ boxSizing: "border-box",
548
+ maxHeight: `calc(100vh - 235px)`, // 235 estimate using H4 title & default FormDialogActions
549
+ },
550
+ },
551
+ }, dialogProps), [dialogProps]);
552
+ return (jsx(FormDialogProvider, { open: open, closeDialog: onClose, children: jsx(BaseDialog, { PaperComponent: PaperComponent, open: open, onClose: onClose, closeButton: jsx(IconButton, { sx: {
553
+ position: "absolute",
554
+ top: 8,
555
+ right: 8,
556
+ }, onClick: (e) => onClose === null || onClose === void 0 ? void 0 : onClose(e, 'escapeKeyDown'), children: jsx(Close, { fontSize: "small", "aria-label": "close-form-dialog" }) }), ...baseDialogProps, children: children }) }));
557
+ };
558
+ FormDialog.displayName = "FormDialog";
559
+
579
560
  /**
580
561
  * A cancel button component that integrates with FormDialogContext.
581
562
  * Automatically closes the parent dialog when clicked.
@@ -644,20 +625,20 @@ FormCancelButton.displayName = "FormCancelButton";
644
625
  * Process Payment
645
626
  * </LoadingButton>
646
627
  */
647
- const LoadingButton = ({ children, loading, altIcon, ...props }) => {
628
+ const LoadingButton = ({ children, loading = false, altIcon, ...props }) => {
648
629
  const theme = useTheme$1();
649
- return (jsxs(Button, { ...props, children: [!loading ? (jsx(Box, { sx: {
630
+ return (jsxs(Button, { ...props, children: [!loading ? altIcon : (jsx(Box, { sx: {
650
631
  display: "flex",
651
632
  alignItems: "center",
652
633
  justifyContent: "center",
653
634
  width: "100%",
654
635
  height: "auto",
655
636
  mr: 1,
656
- }, children: jsx(CircularProgress, { color: "inherit", size: theme.typography.fontSize }) })) : (altIcon), children] }));
637
+ }, children: jsx(CircularProgress, { color: "inherit", size: theme.typography.fontSize }) })), children] }));
657
638
  };
658
639
 
659
640
  /**
660
- * A submit button for forms with loading state, attempt tracking and form context awareness
641
+ * A "submit button" for forms with loading state, attempt tracking and form context awareness
661
642
  *
662
643
  * This component extends the LoadingButton with form-specific features:
663
644
  * - Automatically displays loading state during form submission
@@ -671,9 +652,6 @@ const LoadingButton = ({ children, loading, altIcon, ...props }) => {
671
652
  * - Explicit-disabled prop
672
653
  * - Form-wide disabled state from FormDialogContext
673
654
  *
674
- * @example
675
- * // Basic usage
676
- * <FormSubmitButton />
677
655
  *
678
656
  * @example
679
657
  * // With custom text and max attempts
@@ -692,11 +670,11 @@ const LoadingButton = ({ children, loading, altIcon, ...props }) => {
692
670
  * </FormSubmitButton>
693
671
  */
694
672
  const FormSubmitButton = memo(function ({ showAttempts, maxAttempts, children = "Save", ...props }) {
695
- const { formState, getValues } = useFormContext$1();
673
+ const formState = useFormState();
696
674
  const { disabled: disabledForm } = useFormDialog();
697
675
  const disabled = formState.isSubmitting || formState.isLoading || props.disabled || disabledForm;
698
676
  const hasMaxAttempts = maxAttempts && isFinite(maxAttempts);
699
- return (jsx(LoadingButton, { loading: !formState.isSubmitting || formState.isLoading, variant: "contained", type: "submit", size: "large", disabled: disabled, ...props, children: jsx(Badge, { badgeContent: formState.submitCount || (formState.disabled ? maxAttempts : undefined), color: disabled ? "error" : "warning", invisible: !hasMaxAttempts && !showAttempts, children: children }) }));
677
+ return (jsx(LoadingButton, { loading: formState.isSubmitting || formState.isLoading, variant: "contained", type: "submit", size: "large", disabled: disabled, ...props, children: jsx(Badge, { badgeContent: formState.submitCount || (formState.disabled ? maxAttempts : undefined), color: disabled ? "error" : "warning", invisible: !hasMaxAttempts && !showAttempts, children: children }) }));
700
678
  });
701
679
  FormSubmitButton.displayName = "FormSubmitButton";
702
680
 
@@ -717,10 +695,6 @@ FormSubmitButton.displayName = "FormSubmitButton";
717
695
  * - Form-wide disabled state from FormDialogContext
718
696
  *
719
697
  * @example
720
- * // Basic usage
721
- * <FormResetButton />
722
- *
723
- * @example
724
698
  * // With persistence integration
725
699
  * <FormResetButton formKey="user-profile-form" />
726
700
  *
@@ -742,7 +716,6 @@ const FormResetButton = memo(function (props) {
742
716
  const { reset } = useFormContext();
743
717
  const { isSubmitting, isLoading, isDirty } = useFormState();
744
718
  const { disabled: disabledForm } = useFormDialog();
745
- useParentTheme();
746
719
  const keepSubmitCount = !!(props === null || props === void 0 ? void 0 : props.keepCount);
747
720
  const handleOnClick = useCallback(() => {
748
721
  reset(undefined, { keepSubmitCount, keepIsSubmitted: keepSubmitCount });
@@ -804,7 +777,7 @@ FormResetButton.displayName = "FormResetButton";
804
777
  * </Grid>
805
778
  * </Grid>
806
779
  */
807
- const GridSpacer = () => jsx(Grid, { flex: "1 0 0" });
780
+ const GridSpacer = () => jsx(Grid, { flex: "1 0 0", sx: { w: 0 } });
808
781
 
809
782
  /**
810
783
  * Standard set of form dialog action buttons with consistent styling and behavior
@@ -888,5 +861,5 @@ const AutoGrid = ({ components, columnCount = 1, ...props }) => (jsx(Grid, { con
888
861
  xs: 12 / columnCount,
889
862
  }, children: child }, child.key))) }));
890
863
 
891
- export { AutoGrid, BaseDialog, BlackoutDialog, FormCancelButton, FormDialog, FormDialogActions, FormDialogProvider, FormResetButton, FormSubmitButton, GridSpacer, PaperForm, PersistForm, applyDefaultFormDialogProps, deepCompare, hasMaxAttempts, useDialog, useFormDialog, usePersistedForm };
864
+ export { AutoGrid, BaseDialog, BlackoutDialog, FormCancelButton, FormDialog, FormDialogActions, FormDialogProvider, FormResetButton, FormSubmitButton, GridSpacer, LoadingButton, PaperForm, PersistForm, useDialog, useFormDialog, usePersistForm };
892
865
  //# sourceMappingURL=index.js.map