@simoncomputing/mui-bueno-v3 0.1.16 → 0.1.18

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/README.md CHANGED
@@ -41,7 +41,7 @@ In the application repo:
41
41
 
42
42
  ```shell
43
43
  # Update package to look for this file in the proper directory.
44
- npm i ~/git/mui-bueno-v3/simoncomputing-mui-bueno-v3-0.1.16.tgz
44
+ npm i ~/git/mui-bueno-v3/simoncomputing-mui-bueno-v3-0.1.18.tgz
45
45
 
46
46
  # Install
47
47
  npm install
@@ -1,5 +1,5 @@
1
1
  import { TypographyProps, TextFieldProps as MuiTextFieldProps } from '@mui/material';
2
- import * as React from "react";
2
+ import * as React from 'react';
3
3
  type BaseTextFieldProps = {
4
4
  /**
5
5
  * Name and ID of the component. Must match a key from initialValues in react-hook-form.
@@ -46,13 +46,13 @@ type BaseTextFieldProps = {
46
46
  /**
47
47
  * Margin for the FormControl
48
48
  */
49
- margin?: "dense" | "none" | "normal";
49
+ margin?: 'dense' | 'none' | 'normal';
50
50
  /**
51
51
  * By default, TextField will trim leading and trailing whitespace on blur. To disable this, set `noTrimOnBlur` to true.
52
52
  */
53
53
  noTrimOnBlur?: boolean;
54
54
  };
55
- export type TextFieldProps = BaseTextFieldProps & Omit<MuiTextFieldProps, "defaultValue" | "error" | "fullWidth" | "select" | "SelectProps" | "value">;
55
+ export type TextFieldProps = BaseTextFieldProps & Omit<MuiTextFieldProps, 'defaultValue' | 'error' | 'fullWidth' | 'select' | 'SelectProps' | 'value'>;
56
56
  /**
57
57
  * The `TextField` is a MUI input. For use with validation, view the examples at the bottom of this page.
58
58
  */
@@ -0,0 +1,115 @@
1
+ import { FieldValues } from 'react-hook-form';
2
+ import { MFormProvider2Props } from './MFormProvider2';
3
+ import * as React from 'react';
4
+ /**
5
+ * # MForm2 Documentation
6
+ *
7
+ * ## What this module is
8
+ * `MForm2` is a **thin convenience wrapper** around `MFormProvider2` that:
9
+ * - Renders the actual HTML `<form>` element.
10
+ * - Wires the form’s `onSubmit` event to the provider’s `submit()` function.
11
+ * - Preserves the ability to use either:
12
+ * - **Normal children** (`ReactNode`), or
13
+ * - **Render-prop children** that receive `{ methods, submit }`.
14
+ *
15
+ * `MForm2` exists so most consumers don’t need to manually place `<form>` tags or call `submit()`.
16
+ *
17
+ * ## Responsibilities
18
+ * 1) Delegates all RHF setup/ownership and submit logic to `MFormProvider2`.
19
+ * 2) Adds a `<form>` boundary with:
20
+ * - `noValidate` to disable browser-native validation bubbles
21
+ * - `onSubmit` to call `submit()` (which runs RHF validation + submit wrapper)
22
+ * 3) Exposes basic styling/identification props for the `<form>`:
23
+ * - `formId`, `className`, `style`
24
+ *
25
+ * ## What MForm2 does NOT do
26
+ * - It does not call `useForm()` directly.
27
+ * - It does not implement validation logic itself.
28
+ * - It does not map server errors itself.
29
+ * - It does not manage reinitialization itself.
30
+ * All of those are handled by `MFormProvider2`.
31
+ *
32
+ * ## Props (high level)
33
+ * `MForm2Props<T>` is:
34
+ * - all props accepted by `MFormProvider2` (owned or controlled),
35
+ * - plus `MForm2BaseProps` for the `<form>` element itself.
36
+ *
37
+ * ### Base props (MForm2BaseProps)
38
+ * - `formId?: string`
39
+ * - Applied as the `<form id="...">`.
40
+ * - Useful when your submit button is outside the form (e.g., MUI DialogActions):
41
+ * `<Button type="submit" form="your-form-id">Save</Button>`
42
+ *
43
+ * - `className?: string`
44
+ * - Passed to the `<form>` element.
45
+ *
46
+ * - `style?: React.CSSProperties`
47
+ * - Inline styles for the `<form>` element.
48
+ *
49
+ * ### Provider props (from MFormProvider2Props<T>)
50
+ * MForm2 supports both provider modes via the same prop union:
51
+ *
52
+ * **Controlled mode**
53
+ * - pass `methods: UseFormReturn<T>` to re-use an existing RHF form instance
54
+ *
55
+ * **Owned mode**
56
+ * - pass `defaultValues: DefaultValues<T>` (required)
57
+ * - optionally pass `resolver`, `useFormProps`, `reinitializeKey`, etc.
58
+ *
59
+ * ## Default values (source of truth)
60
+ * MForm2 does not define its own default values. It forwards props to `MFormProvider2`, which chooses:
61
+ *
62
+ * - Controlled mode: default values are whatever the caller used when creating `methods` via `useForm({ defaultValues })`.
63
+ * - Owned mode: default values come from the required `defaultValues` prop.
64
+ *
65
+ * There are no additional default values introduced by MForm2 itself; it only forwards and wraps.
66
+ *
67
+ * ## Render-prop children
68
+ * Children can be either:
69
+ * - Regular JSX:
70
+ * ```tsx
71
+ * <MForm2 ...>
72
+ * <MyFields />
73
+ * </MForm2>
74
+ * ```
75
+ * - Or a render function that receives:
76
+ * - `methods`: RHF methods object (UseFormReturn<T>)
77
+ * - `submit`: programmatic submit function (runs RHF handleSubmit)
78
+ * ```tsx
79
+ * <MForm2 ...>
80
+ * {({ methods, submit }) => (
81
+ * <>
82
+ * <MyFields />
83
+ * <button type="button" onClick={() => void submit()}>Save</button>
84
+ * </>
85
+ * )}
86
+ * </MForm2>
87
+ * ```
88
+ *
89
+ * ## Submit behavior
90
+ * The `<form onSubmit>` handler:
91
+ * - calls `e.preventDefault()` to avoid browser navigation,
92
+ * - calls `submit()` from `MFormProvider2`, which:
93
+ * - runs RHF validation
94
+ * - calls `onSubmit(values)` when valid
95
+ * - calls `onInvalid(errors)` when invalid (if provided)
96
+ * - optionally maps Axios 422 field errors into RHF errors
97
+ *
98
+ * ## Testing note
99
+ * In unit tests, prefer submitting via user interaction when possible:
100
+ * - `await user.click(button)` or `fireEvent.submit(form)`
101
+ * If you call `submitRef.current()` programmatically, ensure the test awaits resulting UI changes
102
+ * (e.g., `await waitFor(...)` / `await findBy...`) to avoid act warnings.
103
+ */
104
+ export type MForm2BaseProps = {
105
+ formId?: string;
106
+ className?: string;
107
+ style?: React.CSSProperties;
108
+ };
109
+ export type MForm2Props<T extends FieldValues> = MFormProvider2Props<T> & MForm2BaseProps;
110
+ /**
111
+ * MForm2:
112
+ * - Wraps children with MFormProvider2
113
+ * - Renders a <form> and wires onSubmit to provider's submit()
114
+ */
115
+ export declare function MForm2<T extends FieldValues>(props: MForm2Props<T>): React.JSX.Element;
@@ -0,0 +1,225 @@
1
+ import { DefaultValues, FieldValues, Resolver, SubmitErrorHandler, SubmitHandler, UseFormProps, UseFormReturn } from 'react-hook-form';
2
+ import * as React from 'react';
3
+ /**
4
+ * # MFormProvider2 Documentation
5
+ *
6
+ * ## What this module is
7
+ * `MFormProvider2` is a **thin React Hook Form (RHF) wrapper** that:
8
+ * - Provides a `FormProvider` context for descendants (so `useFormContext()` works).
9
+ * - Standardizes submit behavior (including optional Axios 422 → field error mapping).
10
+ * - Supports two modes:
11
+ * - **Controlled**: you create the RHF methods with `useForm()` elsewhere and pass `methods`.
12
+ * - **Owned**: this provider calls `useForm()` internally using `defaultValues`/`resolver`.
13
+ * - Optionally exposes a programmatic submit function via:
14
+ * - `submitRef` (imperative ref),
15
+ * - and/or an internal context + `useMForm2()` hook.
16
+ *
17
+ * Importantly, this component does **not** render a `<form>` element. That is handled by `MForm2`
18
+ * (or any parent component that wants to place the `<form>` tag wherever it needs to).
19
+ *
20
+ * ## When to use MFormProvider2 vs MForm2
21
+ * - Use **MFormProvider2** when you need RHF context and submit helpers but want to control the `<form>` tag placement.
22
+ * - Use **MForm2** (a separate wrapper) when you want a ready-to-use `<form>` element around your children.
23
+ *
24
+ * ## Primary responsibilities
25
+ * 1) **RHF context**: wraps children with `<FormProvider {...methods}>`.
26
+ * 2) **Submit orchestration**:
27
+ * - Calls `methods.handleSubmit(submitWrapped, onInvalid)`
28
+ * - `submitWrapped` calls your `onSubmit(values)` and catches optional Axios 422 errors.
29
+ * 3) **Server validation errors** (optional):
30
+ * - If `applyAxios422Errors` is enabled and the submit throws an axios-ish 422 error, it maps response field errors
31
+ * to RHF’s `setError()` so the UI can display them like any other validation error.
32
+ * 4) **Programmatic submit**:
33
+ * - Exposes `submit()` to render-prop children,
34
+ * - optionally stores it into `submitRef.current`,
35
+ * - optionally provides it via `useMForm2()` context.
36
+ *
37
+ * ## Concepts: Controlled vs Owned
38
+ *
39
+ * ### Controlled mode
40
+ * You provide the RHF methods object:
41
+ * ```ts
42
+ * const methods = useForm<MyValues>({ defaultValues });
43
+ * <MFormProvider2 methods={methods} onSubmit={...}>{...}</MFormProvider2>
44
+ * ```
45
+ *
46
+ * Use this when:
47
+ * - You need `methods` in the parent component (e.g., `useFieldArray()` at the dialog level),
48
+ * - You want to configure RHF outside of the wrapper.
49
+ *
50
+ * ### Owned mode
51
+ * You provide `defaultValues` (and optionally `resolver`, `useFormProps`, etc.):
52
+ * ```ts
53
+ * <MFormProvider2 defaultValues={...} resolver={...} onSubmit={...}>{...}</MFormProvider2>
54
+ * ```
55
+ *
56
+ * Use this when:
57
+ * - You want a very simple call site,
58
+ * - You don’t need to share methods with a parent component.
59
+ *
60
+ * ## Error mapping (Axios 422 → RHF errors)
61
+ *
62
+ * Many APIs return validation errors in a 422 response like:
63
+ * ```json
64
+ * { "Name": "Name is required" }
65
+ * ```
66
+ *
67
+ * If `applyAxios422Errors` is true (default is true), `submitWrapped` catches that error and:
68
+ * - Lowercases the first character of the field name (`Name` → `name`) to match typical TS/RHF casing
69
+ * - Applies the message to RHF via `setError("name", { message: "..." })`
70
+ *
71
+ * ### Custom mapping
72
+ * If your backend response shape differs, provide `mapAxios422` to adapt it.
73
+ *
74
+ * ## Reinitialization (owned mode only)
75
+ * Owned mode supports `reinitializeKey`: when the key changes, the provider calls `methods.reset(defaultValues)`.
76
+ * This is intentionally more predictable than “reset whenever defaultValues changes”.
77
+ *
78
+ * A common pattern is:
79
+ * - `reinitializeKey = divisionId` or a dialog open counter
80
+ * - `defaultValues` comes from your loaded entity
81
+ *
82
+ * ## validateOnMount
83
+ * If `validateOnMount` is true (default is false), the provider calls `methods.trigger()` after mount. This runs validation immediately.
84
+ * (Note: this can cause async updates in tests; prefer `findBy...`/`waitFor` to await the UI.)
85
+ *
86
+ * ## Programmatic submit
87
+ *
88
+ * There are three ways to submit without a submit button:
89
+ *
90
+ * 1) Render-prop children receive `submit()`:
91
+ * ```tsx
92
+ * <MFormProvider2 ...>
93
+ * {({ submit }) => <button onClick={() => void submit()}>Save</button>}
94
+ * </MFormProvider2>
95
+ * ```
96
+ *
97
+ * 2) Use `submitRef`:
98
+ * ```ts
99
+ * const submitRef = useRef<(() => Promise<void>) | null>(null);
100
+ * <MFormProvider2 submitRef={submitRef} ... />
101
+ * // later
102
+ * await submitRef.current?.();
103
+ * ```
104
+ *
105
+ * 3) Use `useMForm2()` anywhere under the provider:
106
+ * ```ts
107
+ * const { submit } = useMForm2<MyValues>();
108
+ * ```
109
+ *
110
+ * ## File-level map of what’s below
111
+ * - Server error helpers (`applyServerErrors`, `Axios422Mapper`)
112
+ * - Optional context + `useMForm2()`
113
+ * - Prop types for owned/controlled
114
+ * - `MFormProvider2WithMethods`: shared implementation given a `methods` object
115
+ * - `MFormProvider2ControlledInner`: controlled mode implementation
116
+ * - `MFormProvider2OwnedInner`: owned mode implementation (calls `useForm()`)
117
+ * - `MFormProvider2`: public selector (chooses controlled vs owned)
118
+ */
119
+ /** ---------------- Server errors ---------------- */
120
+ export type ServerErrorMap = Record<string, string>;
121
+ /**
122
+ * Apply the server errors to RHF errors.
123
+ * Server errors are a map of fieldName -> error message.
124
+ *
125
+ * Notes:
126
+ * - `focusFirst` (default is true) controls whether RHF should focus the first errored field.
127
+ * - Field names are normalized (`Name` -> `name`) via `lowerCaseFieldName`.
128
+ */
129
+ export declare function applyServerErrors<T extends FieldValues>(setError: UseFormReturn<T>['setError'], errors: ServerErrorMap, focusFirst?: boolean): void;
130
+ /**
131
+ * Converts a thrown error (usually Axios) into either:
132
+ * - `{ ok: true, errors: ServerErrorMap }` if it is a 422 validation error, or
133
+ * - `{ ok: false }` if it is not.
134
+ *
135
+ * This is intentionally generic so applications can replace it with their own mapping logic.
136
+ */
137
+ export type Axios422Mapper = (err: unknown) => {
138
+ ok: true;
139
+ errors: ServerErrorMap;
140
+ } | {
141
+ ok: false;
142
+ };
143
+ /**
144
+ * Default mapping for axios-ish errors:
145
+ * - expects `err.response.status === 422`
146
+ * - expects `err.response.data` to be an object of `{ field: message }`
147
+ */
148
+ export declare const defaultAxios422Mapper: Axios422Mapper;
149
+ /** ---------------- Context (optional) ---------------- */
150
+ /**
151
+ * Context is optional but convenient. It allows any descendant to access:
152
+ * - `submit()` (programmatic submit),
153
+ * - RHF `methods`,
154
+ * - and `applyServerErrors()` without needing prop drilling.
155
+ */
156
+ type MForm2ContextValue<T extends FieldValues> = {
157
+ submit: () => Promise<void>;
158
+ methods: UseFormReturn<T>;
159
+ applyServerErrors: (errors: ServerErrorMap, focusFirst?: boolean) => void;
160
+ };
161
+ /**
162
+ * Hook to access MFormProvider2 utilities from descendants.
163
+ * Must be called under `<MFormProvider2>` (or `<MForm2>` if it wraps provider+form).
164
+ */
165
+ export declare function useMForm2<T extends FieldValues>(): MForm2ContextValue<T>;
166
+ /** ---------------- Props ---------------- */
167
+ /**
168
+ * Render-prop signature for children:
169
+ * - `methods`: RHF methods object
170
+ * - `submit`: programmatic submit (runs handleSubmit with error mapping + onInvalid)
171
+ */
172
+ export type MFormProvider2RenderArgs<T extends FieldValues> = {
173
+ methods: UseFormReturn<T>;
174
+ submit: () => Promise<void>;
175
+ };
176
+ /**
177
+ * Common props shared by owned and controlled usage.
178
+ *
179
+ * - `onSubmit`: called with validated values
180
+ * - `onInvalid`: called with RHF errors when invalid
181
+ * - `validateOnMount`: runs validation immediately on mount via `trigger()`
182
+ * - `applyAxios422Errors` + `mapAxios422`: server validation mapping support
183
+ * - `submitRef`: imperative handle to the `submit()` function
184
+ * - `children`: ReactNode or render-prop (receives methods+submit)
185
+ */
186
+ type CommonProps<T extends FieldValues> = {
187
+ onSubmit: SubmitHandler<T> | ((values: T) => Promise<void>);
188
+ onInvalid?: SubmitErrorHandler<T>;
189
+ validateOnMount?: boolean;
190
+ applyAxios422Errors?: boolean;
191
+ mapAxios422?: Axios422Mapper;
192
+ submitRef?: React.RefObject<(() => Promise<void>) | null>;
193
+ children: React.ReactNode | ((args: MFormProvider2RenderArgs<T>) => React.ReactNode);
194
+ };
195
+ /**
196
+ * Controlled mode: consumer provides RHF `methods`.
197
+ */
198
+ export type MFormProvider2ControlledProps<T extends FieldValues> = CommonProps<T> & {
199
+ methods: UseFormReturn<T>;
200
+ };
201
+ /**
202
+ * Owned mode: provider creates RHF `methods` using `useForm()`.
203
+ */
204
+ export type MFormProvider2OwnedProps<T extends FieldValues> = CommonProps<T> & {
205
+ defaultValues: DefaultValues<T>;
206
+ resolver?: Resolver<T>;
207
+ shouldFocusError?: boolean;
208
+ useFormProps?: Omit<UseFormProps<T>, 'defaultValues' | 'resolver' | 'shouldFocusError'>;
209
+ /** Reset only when this key changes (owned mode only). */
210
+ reinitializeKey?: string | number;
211
+ resetOptions?: Parameters<UseFormReturn<T>['reset']>[1];
212
+ };
213
+ export type MFormProvider2Props<T extends FieldValues> = MFormProvider2ControlledProps<T> | MFormProvider2OwnedProps<T>;
214
+ /** ---------------- Public provider ---------------- */
215
+ /**
216
+ * Public entry point:
217
+ * - Uses a discriminant check: `'methods' in props`
218
+ * - If present -> controlled mode
219
+ * - Otherwise -> owned mode
220
+ *
221
+ * Note: This is safe with React hooks because hooks are only called inside the
222
+ * owned-mode component, not conditionally within a single component body.
223
+ */
224
+ export declare function MFormProvider2<T extends FieldValues>(props: MFormProvider2Props<T>): React.JSX.Element;
225
+ export {};