@simoncomputing/mui-bueno-v3 0.1.17 → 0.2.1

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.17.tgz
44
+ npm i ~/git/mui-bueno-v3/simoncomputing-mui-bueno-v3-0.2.1.tgz
45
45
 
46
46
  # Install
47
47
  npm install
@@ -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 `validationSchema` (Yup) and/or `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,236 @@
1
+ import { DefaultValues, FieldValues, Resolver, SubmitErrorHandler, SubmitHandler, UseFormProps, UseFormReturn } from 'react-hook-form';
2
+ import { AnyObjectSchema } from 'yup';
3
+ import * as React from 'react';
4
+ /**
5
+ * # MFormProvider2 Documentation
6
+ *
7
+ * ## What this module is
8
+ * `MFormProvider2` is a **thin React Hook Form (RHF) wrapper** that:
9
+ * - Provides a `FormProvider` context for descendants (so `useFormContext()` works).
10
+ * - Standardizes submit behavior (including optional Axios 422 → field error mapping).
11
+ * - Supports two modes:
12
+ * - **Controlled**: you create the RHF methods with `useForm()` elsewhere and pass `methods`.
13
+ * - **Owned**: this provider calls `useForm()` internally using `defaultValues`/resolver.
14
+ * - Optionally exposes a programmatic submit function via:
15
+ * - `submitRef` (imperative ref),
16
+ * - and/or an internal context + `useMForm2()` hook.
17
+ *
18
+ * Importantly, this component does **not** render a `<form>` element. That is handled by `MForm2`
19
+ * (or any parent component that wants to place the `<form>` tag wherever it needs to).
20
+ *
21
+ * ## When to use MFormProvider2 vs MForm2
22
+ * - Use **MFormProvider2** when you need RHF context and submit helpers but want to control the `<form>` tag placement.
23
+ * - Use **MForm2** (a separate wrapper) when you want a ready-to-use `<form>` element around your children.
24
+ *
25
+ * ## Primary responsibilities
26
+ * 1) **RHF context**: wraps children with `<FormProvider {...methods}>`.
27
+ * 2) **Submit orchestration**:
28
+ * - Calls `methods.handleSubmit(submitWrapped, onInvalid)`
29
+ * - `submitWrapped` calls your `onSubmit(values)` and catches optional Axios 422 errors.
30
+ * 3) **Server validation errors** (optional):
31
+ * - If `applyAxios422Errors` is enabled and the submit throws an axios-ish 422 error, it maps response field errors
32
+ * to RHF’s `setError()` so the UI can display them like any other validation error.
33
+ * 4) **Programmatic submit**:
34
+ * - Exposes `submit()` to render-prop children,
35
+ * - optionally stores it into `submitRef.current`,
36
+ * - optionally provides it via `useMForm2()` context.
37
+ *
38
+ * ## Concepts: Controlled vs Owned
39
+ *
40
+ * ### Controlled mode
41
+ * You provide the RHF methods object:
42
+ * ```ts
43
+ * const methods = useForm<MyValues>({ defaultValues });
44
+ * <MFormProvider2 methods={methods} onSubmit={...}>{...}</MFormProvider2>
45
+ * ```
46
+ *
47
+ * Use this when:
48
+ * - You need `methods` in the parent component (e.g., `useFieldArray()` at the dialog level),
49
+ * - You want to configure RHF outside of the wrapper.
50
+ *
51
+ * Note: in controlled mode, `validationSchema` is not accepted because the resolver must be configured
52
+ * when you create `methods` via `useForm({ resolver: ... })`.
53
+ *
54
+ * ### Owned mode
55
+ * You provide `defaultValues` (and optionally `validationSchema`, `resolver`, `useFormProps`, etc.):
56
+ * ```ts
57
+ * <MFormProvider2 defaultValues={...} validationSchema={...} onSubmit={...}>{...}</MFormProvider2>
58
+ * ```
59
+ *
60
+ * Owned mode is the recommended path when you want minimal call-site configuration.
61
+ *
62
+ * ## Error mapping (Axios 422 → RHF errors)
63
+ *
64
+ * Many APIs return validation errors in a 422 response like:
65
+ * ```json
66
+ * { "Name": "Name is required" }
67
+ * ```
68
+ *
69
+ * If `applyAxios422Errors` is true (default is true), `submitWrapped` catches that error and:
70
+ * - Lowercases the first character of the field name (`Name` → `name`) to match typical TS/RHF casing
71
+ * - Applies the message to RHF via `setError("name", { message: "..." })`
72
+ *
73
+ * ### Custom mapping
74
+ * If your backend response shape differs, provide `mapAxios422` to adapt it.
75
+ *
76
+ * ## Reinitialization (owned mode only)
77
+ * Owned mode supports `reinitializeKey`: when the key changes, the provider calls `methods.reset(defaultValues)`.
78
+ * This is intentionally more predictable than “reset whenever defaultValues changes”.
79
+ *
80
+ * A common pattern is:
81
+ * - `reinitializeKey = divisionId` or a dialog open counter
82
+ * - `defaultValues` comes from your loaded entity
83
+ *
84
+ * ## validateOnMount
85
+ * If `validateOnMount` is true (default is false), the provider calls `methods.trigger()` after mount. This runs validation immediately.
86
+ * (Note: this can cause async updates in tests; prefer `findBy...`/`waitFor` to await the UI.)
87
+ *
88
+ * ## Programmatic submit
89
+ *
90
+ * There are three ways to submit without a submit button:
91
+ *
92
+ * 1) Render-prop children receive `submit()`:
93
+ * ```tsx
94
+ * <MFormProvider2 ...>
95
+ * {({ submit }) => <button onClick={() => void submit()}>Save</button>}
96
+ * </MFormProvider2>
97
+ * ```
98
+ *
99
+ * 2) Use `submitRef`:
100
+ * ```ts
101
+ * const submitRef = useRef<(() => Promise<void>) | null>(null);
102
+ * <MFormProvider2 submitRef={submitRef} ... />
103
+ * // later
104
+ * await submitRef.current?.();
105
+ * ```
106
+ *
107
+ * 3) Use `useMForm2()` anywhere under the provider:
108
+ * ```ts
109
+ * const { submit } = useMForm2<MyValues>();
110
+ * ```
111
+ *
112
+ * ## File-level map of what’s below
113
+ * - Server error helpers (`applyServerErrors`, `Axios422Mapper`)
114
+ * - Optional context + `useMForm2()`
115
+ * - Prop types for owned/controlled
116
+ * - `MFormProvider2WithMethods`: shared implementation given a `methods` object
117
+ * - `MFormProvider2ControlledInner`: controlled mode implementation
118
+ * - `MFormProvider2OwnedInner`: owned mode implementation (calls `useForm()`)
119
+ * - `MFormProvider2`: public selector (chooses controlled vs owned)
120
+ */
121
+ /** ---------------- Server errors ---------------- */
122
+ export type ServerErrorMap = Record<string, string>;
123
+ /**
124
+ * Apply the server errors to RHF errors.
125
+ * Server errors are a map of fieldName -> error message.
126
+ *
127
+ * Notes:
128
+ * - `focusFirst` (default is true) controls whether RHF should focus the first errored field.
129
+ * - Field names are normalized (`Name` -> `name`) via `lowerCaseFieldName`.
130
+ */
131
+ export declare function applyServerErrors<T extends FieldValues>(setError: UseFormReturn<T>['setError'], errors: ServerErrorMap, focusFirst?: boolean): void;
132
+ /**
133
+ * Converts a thrown error (usually Axios) into either:
134
+ * - `{ ok: true, errors: ServerErrorMap }` if it is a 422 validation error, or
135
+ * - `{ ok: false }` if it is not.
136
+ *
137
+ * This is intentionally generic so applications can replace it with their own mapping logic.
138
+ */
139
+ export type Axios422Mapper = (err: unknown) => {
140
+ ok: true;
141
+ errors: ServerErrorMap;
142
+ } | {
143
+ ok: false;
144
+ };
145
+ /**
146
+ * Default mapping for axios-ish errors:
147
+ * - expects `err.response.status === 422`
148
+ * - expects `err.response.data` to be an object of `{ field: message }`
149
+ */
150
+ export declare const defaultAxios422Mapper: Axios422Mapper;
151
+ /** ---------------- Context (optional) ---------------- */
152
+ /**
153
+ * Context is optional but convenient. It allows any descendant to access:
154
+ * - `submit()` (programmatic submit),
155
+ * - RHF `methods`,
156
+ * - and `applyServerErrors()` without needing prop drilling.
157
+ */
158
+ type MForm2ContextValue<T extends FieldValues> = {
159
+ submit: () => Promise<void>;
160
+ methods: UseFormReturn<T>;
161
+ applyServerErrors: (errors: ServerErrorMap, focusFirst?: boolean) => void;
162
+ };
163
+ /**
164
+ * Hook to access MFormProvider2 utilities from descendants.
165
+ * Must be called under `<MFormProvider2>` (or `<MForm2>` if it wraps provider+form).
166
+ */
167
+ export declare function useMForm2<T extends FieldValues>(): MForm2ContextValue<T>;
168
+ /** ---------------- Props ---------------- */
169
+ /**
170
+ * Render-prop signature for children:
171
+ * - `methods`: RHF methods object
172
+ * - `submit`: programmatic submit (runs handleSubmit with error mapping + onInvalid)
173
+ */
174
+ export type MFormProvider2RenderArgs<T extends FieldValues> = {
175
+ methods: UseFormReturn<T>;
176
+ submit: () => Promise<void>;
177
+ };
178
+ /**
179
+ * Common props shared by owned and controlled usage.
180
+ *
181
+ * - `onSubmit`: called with validated values
182
+ * - `onInvalid`: called with RHF errors when invalid
183
+ * - `validateOnMount`: runs validation immediately on mount via `trigger()`
184
+ * - `applyAxios422Errors` + `mapAxios422`: server validation mapping support
185
+ * - `submitRef`: imperative handle to the `submit()` function
186
+ * - `children`: ReactNode or render-prop (receives methods+submit)
187
+ */
188
+ type CommonProps<T extends FieldValues> = {
189
+ onSubmit: SubmitHandler<T> | ((values: T) => Promise<void>);
190
+ onInvalid?: SubmitErrorHandler<T>;
191
+ validateOnMount?: boolean;
192
+ applyAxios422Errors?: boolean;
193
+ mapAxios422?: Axios422Mapper;
194
+ submitRef?: React.RefObject<(() => Promise<void>) | null>;
195
+ children: React.ReactNode | ((args: MFormProvider2RenderArgs<T>) => React.ReactNode);
196
+ };
197
+ /**
198
+ * Controlled mode: consumer provides RHF `methods`.
199
+ */
200
+ export type MFormProvider2ControlledProps<T extends FieldValues> = CommonProps<T> & {
201
+ methods: UseFormReturn<T>;
202
+ };
203
+ /**
204
+ * Owned mode: provider creates RHF `methods` using `useForm()`.
205
+ */
206
+ export type MFormProvider2OwnedProps<T extends FieldValues> = CommonProps<T> & {
207
+ defaultValues: DefaultValues<T>;
208
+ /**
209
+ * Yup schema for validation (owned mode convenience).
210
+ * If provided and `resolver` is not provided, the provider will use `yupResolver(validationSchema)`.
211
+ */
212
+ validationSchema?: AnyObjectSchema;
213
+ /**
214
+ * Explicit resolver override.
215
+ * If provided, it takes precedence over `validationSchema`.
216
+ */
217
+ resolver?: Resolver<T>;
218
+ shouldFocusError?: boolean;
219
+ useFormProps?: Omit<UseFormProps<T>, 'defaultValues' | 'resolver' | 'shouldFocusError'>;
220
+ /** Reset only when this key changes (owned mode only). */
221
+ reinitializeKey?: string | number;
222
+ resetOptions?: Parameters<UseFormReturn<T>['reset']>[1];
223
+ };
224
+ export type MFormProvider2Props<T extends FieldValues> = MFormProvider2ControlledProps<T> | MFormProvider2OwnedProps<T>;
225
+ /** ---------------- Public provider ---------------- */
226
+ /**
227
+ * Public entry point:
228
+ * - Uses a discriminant check: `'methods' in props`
229
+ * - If present -> controlled mode
230
+ * - Otherwise -> owned mode
231
+ *
232
+ * Note: This is safe with React hooks because hooks are only called inside the
233
+ * owned-mode component, not conditionally within a single component body.
234
+ */
235
+ export declare function MFormProvider2<T extends FieldValues>(props: MFormProvider2Props<T>): React.JSX.Element;
236
+ export {};