haze-ui 1.16.1 → 1.17.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/README.md +40 -1
- package/dist/form/FormItem.js +5 -5
- package/dist/types/form/FormItem.d.ts +94 -22
- package/dist/types/form/index.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -190,7 +190,7 @@ render-prop are mutually exclusive.
|
|
|
190
190
|
- With a typed form, `validate`'s value argument is the field's actual
|
|
191
191
|
type (`PathValueOf<TValues, P>`), not `any`.
|
|
192
192
|
|
|
193
|
-
#### `input`: declarative binding for
|
|
193
|
+
#### `input`: declarative binding for cores and raw DOM controls (typed prop forwarding)
|
|
194
194
|
|
|
195
195
|
The ergonomic form for the controlled cores — pass the component and the
|
|
196
196
|
rest of the JSX goes straight to it, type-checked against its own props:
|
|
@@ -243,6 +243,45 @@ checkbox-style core pairs with `valueToProps`). The differences from
|
|
|
243
243
|
own `label`) is unreachable through `input` — use the render-prop or
|
|
244
244
|
`as`/`asProps` for it.
|
|
245
245
|
|
|
246
|
+
`input` also takes raw DOM bindings — no core required. The two raw
|
|
247
|
+
forms are explicit about their `eventToValue` adapter, so the value
|
|
248
|
+
channel is never guessed:
|
|
249
|
+
|
|
250
|
+
```jsx
|
|
251
|
+
// a native form element: the binding pairs the tag with its adapter,
|
|
252
|
+
// and the rest of the JSX is type-checked against that element's own
|
|
253
|
+
// HTML attributes (rows on a textarea, options as a select's children)
|
|
254
|
+
<FormItem
|
|
255
|
+
form={form}
|
|
256
|
+
name="bio"
|
|
257
|
+
label="Bio"
|
|
258
|
+
input={{element: 'textarea', eventToValue: (e) => e.target.value}}
|
|
259
|
+
rows={4}
|
|
260
|
+
/>
|
|
261
|
+
|
|
262
|
+
// a DOM-element-shaped component: the top-level eventToValue is the
|
|
263
|
+
// explicit opt-in from plain-value (core) to event-emitting (raw)
|
|
264
|
+
<FormItem
|
|
265
|
+
form={form}
|
|
266
|
+
name="email"
|
|
267
|
+
label="Email"
|
|
268
|
+
input={NativeInput}
|
|
269
|
+
eventToValue={(e) => e.target.value}
|
|
270
|
+
/>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
- The element binding accepts `'input' | 'textarea' | 'select'` and
|
|
274
|
+
**requires** its `eventToValue` — `input={{element: 'input'}}` without
|
|
275
|
+
the adapter is a compile error (at runtime an untyped caller that
|
|
276
|
+
skips it still gets `e.target.value`, the DOM contract, never an Event
|
|
277
|
+
in the store).
|
|
278
|
+
- The top-level `eventToValue` next to a component `input` switches that
|
|
279
|
+
binding to raw semantics, mirroring the `as` channel; forwarded props
|
|
280
|
+
still check against the component's own props.
|
|
281
|
+
- The same reserved-prop rule applies: `id`, `onBlur`, `onChange`,
|
|
282
|
+
`value`/`checked`, aria-*, and FormItem's own names are never
|
|
283
|
+
forwarded on the raw channel either.
|
|
284
|
+
|
|
246
285
|
#### `mode`: per-field validation timing (react-f0rm ≥ 0.6)
|
|
247
286
|
|
|
248
287
|
Pass `mode` to validate one field on its own schedule instead of the
|
package/dist/form/FormItem.js
CHANGED
|
@@ -15,9 +15,9 @@ function c({ form: c, name: l, label: u, validate: d, mode: f, validateDebounce:
|
|
|
15
15
|
validateDebounce: p,
|
|
16
16
|
delayError: m,
|
|
17
17
|
rules: h
|
|
18
|
-
}), j = A.length > 0, M = x ?? ((e) => e);
|
|
18
|
+
}), j = A.length > 0, M = b && typeof b == "object" && "element" in b ? b : void 0, N = M?.eventToValue ?? x ?? (M ? (e) => e.target?.value : (e) => e);
|
|
19
19
|
if (b && typeof C == "function") throw Error("FormItem: `input` and the render-prop `children` are mutually exclusive — the input component is wired declaratively; remove the render-prop.");
|
|
20
|
-
let
|
|
20
|
+
let P = M ? M.element : b;
|
|
21
21
|
return /* @__PURE__ */ n("div", {
|
|
22
22
|
className: e([a, g]),
|
|
23
23
|
children: [
|
|
@@ -26,13 +26,13 @@ function c({ form: c, name: l, label: u, validate: d, mode: f, validateDebounce:
|
|
|
26
26
|
className: e(o),
|
|
27
27
|
children: u
|
|
28
28
|
}),
|
|
29
|
-
|
|
29
|
+
P ? /* @__PURE__ */ t(P, {
|
|
30
30
|
...w,
|
|
31
31
|
id: T,
|
|
32
32
|
"aria-invalid": j || void 0,
|
|
33
33
|
"aria-describedby": j ? E : void 0,
|
|
34
34
|
onBlur: k,
|
|
35
|
-
onChange: (e) => O(
|
|
35
|
+
onChange: (e) => O(N(e)),
|
|
36
36
|
...S ? S(D) : { value: D },
|
|
37
37
|
children: C
|
|
38
38
|
}) : v ? /* @__PURE__ */ t(v, {
|
|
@@ -40,7 +40,7 @@ function c({ form: c, name: l, label: u, validate: d, mode: f, validateDebounce:
|
|
|
40
40
|
"aria-invalid": j || void 0,
|
|
41
41
|
"aria-describedby": j ? E : void 0,
|
|
42
42
|
onBlur: k,
|
|
43
|
-
onChange: (e) => O(
|
|
43
|
+
onChange: (e) => O(N(e)),
|
|
44
44
|
...y,
|
|
45
45
|
...S ? S(D) : { value: D }
|
|
46
46
|
}) : C({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentType, ReactNode } from 'react';
|
|
1
|
+
import type { ComponentPropsWithoutRef, ComponentType, ReactNode } from 'react';
|
|
2
2
|
import type { FieldError, FieldPath, FieldRules, Name, ValidationMode, FormInstance, PathValueOf } from 'react-f0rm';
|
|
3
3
|
/**
|
|
4
4
|
* Field-level validator, structurally compatible with react-f0rm's
|
|
@@ -52,16 +52,36 @@ export type FormItemAsProps = {
|
|
|
52
52
|
* value props, so `value`/`valueToProps` win conflicts — the same
|
|
53
53
|
* precedence react-f0rm's `Field` uses. */
|
|
54
54
|
asProps?: Record<string, any>;
|
|
55
|
-
/** Converts what the
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* DOM-element component
|
|
55
|
+
/** Converts what the control passes to its `onChange` into the field
|
|
56
|
+
* value. Defaults to identity — haze cores' `onChange` emits the next
|
|
57
|
+
* plain value; pass `(e) => e.target.value` when the control is
|
|
58
|
+
* DOM-element-shaped (a raw `as` component, or a raw component on the
|
|
59
|
+
* `input` channel, where passing the adapter is also the explicit
|
|
60
|
+
* opt-in from core to raw semantics). */
|
|
59
61
|
eventToValue?: (e: any) => any;
|
|
60
62
|
/** Derives the value props for the `as` component from the field value —
|
|
61
63
|
* e.g. `(checked) => ({checked})` for `CheckboxCore`. Defaults to
|
|
62
64
|
* passing `{value}`. */
|
|
63
65
|
valueToProps?: (value: any) => Record<string, any>;
|
|
64
66
|
};
|
|
67
|
+
/**
|
|
68
|
+
* The raw DOM form elements `FormItem`'s `input` channel accepts by tag
|
|
69
|
+
* name. Their `onChange` emits a DOM event, not a plain value, so the
|
|
70
|
+
* binding always carries its own `eventToValue` adapter — see
|
|
71
|
+
* `FormItemRawElementBinding`.
|
|
72
|
+
*/
|
|
73
|
+
export type FormItemRawElement = 'input' | 'textarea' | 'select';
|
|
74
|
+
/**
|
|
75
|
+
* Raw-DOM binding for `FormItem`'s `input` channel — the explicit raw
|
|
76
|
+
* counterpart of passing a core component: a native form element by tag
|
|
77
|
+
* name, paired with the `eventToValue` adapter that extracts the next
|
|
78
|
+
* value from the DOM event. Every other JSX prop forwards to the
|
|
79
|
+
* element, type-checked against that element's own HTML attributes.
|
|
80
|
+
*/
|
|
81
|
+
export type FormItemRawElementBinding<TRawElement extends FormItemRawElement = FormItemRawElement> = {
|
|
82
|
+
element: TRawElement;
|
|
83
|
+
eventToValue: (e: any) => any;
|
|
84
|
+
};
|
|
65
85
|
/** FormItem's own, non-polymorphic props — everything the item itself
|
|
66
86
|
* consumes regardless of how the control is bound (render-prop, `as`
|
|
67
87
|
* or `input`). */
|
|
@@ -104,7 +124,7 @@ export type FormItemOwnProps<TValues extends Record<string, any> = any, P extend
|
|
|
104
124
|
};
|
|
105
125
|
/**
|
|
106
126
|
* Props FormItem wires itself onto an `input`/`as` control — the bridge's
|
|
107
|
-
* own contract. Used to keep them out of
|
|
127
|
+
* own contract. Used to keep them out of the forwarded rest props
|
|
108
128
|
* (type level) and to document that they always win (runtime level):
|
|
109
129
|
* passing one anyway is a compile error, never a silent override.
|
|
110
130
|
*/
|
|
@@ -119,35 +139,64 @@ type FormItemWiredProps = {
|
|
|
119
139
|
'aria-invalid'?: unknown;
|
|
120
140
|
'aria-describedby'?: unknown;
|
|
121
141
|
};
|
|
122
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Prop names the bridge owns or wires — excluded from the forwarded rest
|
|
144
|
+
* props on every channel (core component, raw element, `as`).
|
|
145
|
+
*/
|
|
146
|
+
type FormItemReservedProps<TValues extends Record<string, any> = any, P extends FieldPath<TValues> | Name = Name> = keyof FormItemOwnProps<TValues, P> | keyof FormItemAsProps | keyof FormItemWiredProps | 'input';
|
|
147
|
+
export type FormItemProps<TValues extends Record<string, any> = any, P extends FieldPath<TValues> | Name = Name, TInputProps extends Record<string, any> = Record<never, never>, TRawElement extends FormItemRawElement = never> = FormItemOwnProps<TValues, P> & Omit<FormItemAsProps, 'eventToValue'> & {
|
|
123
148
|
/**
|
|
124
|
-
* Declarative binding for
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
149
|
+
* Declarative binding for the field control, in two forms:
|
|
150
|
+
*
|
|
151
|
+
* - a component (`InputCore`, `TextareaCore`, `TagInputCore`,
|
|
152
|
+
* `SelectCore`, `CheckboxCore`, …, or any DOM-element-shaped
|
|
153
|
+
* component): FormItem wires `id`, `aria-invalid`,
|
|
154
|
+
* `aria-describedby`, `onBlur`, `onChange` and the value channel
|
|
155
|
+
* itself. Haze cores' `onChange` emits the next plain value
|
|
156
|
+
* (identity `eventToValue`); checkbox-style controls pair with
|
|
157
|
+
* `valueToProps={(checked) => ({checked})}`. A DOM-element-shaped
|
|
158
|
+
* component opts into raw semantics by passing `eventToValue`
|
|
159
|
+
* (e.g. `(e) => e.target.value`) — the adapter's presence is the
|
|
160
|
+
* explicit switch from value-direct to event-emitting.
|
|
161
|
+
* - a `FormItemRawElementBinding` — `{element: 'input' |
|
|
162
|
+
* 'textarea' | 'select', eventToValue}` — for a native DOM
|
|
163
|
+
* element: same wiring, value extracted from the DOM event by the
|
|
164
|
+
* required adapter (the top-level `eventToValue` is not this
|
|
165
|
+
* form's slot).
|
|
166
|
+
*
|
|
167
|
+
* Every other prop — and JSX children (a `SelectCore`'s
|
|
168
|
+
* `<option>`s) — is forwarded to the control, fully type-checked
|
|
169
|
+
* against its own props (the component's, or the raw element's HTML
|
|
170
|
+
* attributes). Props FormItem owns or wires (label, className, id,
|
|
171
|
+
* aria-*, onBlur, onChange, value, checked, …) are reserved and
|
|
172
|
+
* cannot be forwarded — use the render-prop or `as`/`asProps` for a
|
|
173
|
+
* colliding control prop.
|
|
136
174
|
*/
|
|
137
|
-
input?: ComponentType<TInputProps>;
|
|
138
|
-
} & Omit<TInputProps,
|
|
175
|
+
input?: ComponentType<TInputProps> | FormItemRawElementBinding<TRawElement>;
|
|
176
|
+
} & ([TRawElement] extends [never] ? Omit<TInputProps, FormItemReservedProps<TValues, P>> : Omit<ComponentPropsWithoutRef<TRawElement>, FormItemReservedProps<TValues, P>>) & ({
|
|
139
177
|
as: ComponentType<any>;
|
|
140
178
|
input?: never;
|
|
141
179
|
children?: never;
|
|
180
|
+
eventToValue?: FormItemAsProps['eventToValue'];
|
|
142
181
|
} | {
|
|
143
182
|
as?: undefined;
|
|
144
183
|
input: ComponentType<TInputProps>;
|
|
184
|
+
eventToValue?: FormItemAsProps['eventToValue'];
|
|
145
185
|
/** JSX children forward to the control (SelectCore's options);
|
|
146
186
|
* the render-prop form is mutually exclusive with `input`. */
|
|
147
187
|
children?: 'children' extends keyof TInputProps ? TInputProps['children'] : undefined;
|
|
188
|
+
} | {
|
|
189
|
+
as?: undefined;
|
|
190
|
+
input: FormItemRawElementBinding<TRawElement>;
|
|
191
|
+
/** the raw element binding carries its own `eventToValue` — the
|
|
192
|
+
* top-level prop is not accepted next to it */
|
|
193
|
+
eventToValue?: never;
|
|
194
|
+
/** JSX children forward to the element (a select's options). */
|
|
195
|
+
children?: ReactNode;
|
|
148
196
|
} | {
|
|
149
197
|
as?: undefined;
|
|
150
198
|
input?: undefined;
|
|
199
|
+
eventToValue?: never;
|
|
151
200
|
children: (binding: FormItemBinding<TValues, P>) => ReactNode;
|
|
152
201
|
});
|
|
153
202
|
/**
|
|
@@ -206,9 +255,32 @@ export type FormItemProps<TValues extends Record<string, any> = any, P extends F
|
|
|
206
255
|
* collides (CheckboxCore's `label`) needs the render-prop or
|
|
207
256
|
* `as`/`asProps` channel.
|
|
208
257
|
*
|
|
258
|
+
* `input` also accepts raw DOM bindings — no core required:
|
|
259
|
+
*
|
|
260
|
+
* ```tsx
|
|
261
|
+
* // a native element: the binding carries its own eventToValue, and the
|
|
262
|
+
* // rest of the JSX is type-checked against that element's attributes
|
|
263
|
+
* <FormItem
|
|
264
|
+
* form={form}
|
|
265
|
+
* name='email'
|
|
266
|
+
* input={{element: 'input', eventToValue: (e) => e.target.value}}
|
|
267
|
+
* type='email'
|
|
268
|
+
* placeholder='Email'
|
|
269
|
+
* />
|
|
270
|
+
*
|
|
271
|
+
* // a DOM-element-shaped component: the top-level eventToValue is the
|
|
272
|
+
* // explicit opt-in from plain-value (core) to event-emitting (raw)
|
|
273
|
+
* <FormItem
|
|
274
|
+
* form={form}
|
|
275
|
+
* name='email'
|
|
276
|
+
* input={NativeInput}
|
|
277
|
+
* eventToValue={(e) => e.target.value}
|
|
278
|
+
* />
|
|
279
|
+
* ```
|
|
280
|
+
*
|
|
209
281
|
* When the field has errors, the first error's message is rendered into a
|
|
210
282
|
* `<span id={errorId} role='alert'>` next to the control; with no errors
|
|
211
283
|
* no extra element is rendered.
|
|
212
284
|
*/
|
|
213
|
-
export default function FormItem<TValues extends Record<string, any> = any, P extends FieldPath<TValues> | Name = Name, TInputProps extends Record<string, any> = Record<never, never
|
|
285
|
+
export default function FormItem<TValues extends Record<string, any> = any, P extends FieldPath<TValues> | Name = Name, TInputProps extends Record<string, any> = Record<never, never>, TRawElement extends FormItemRawElement = never>({ form, name, label, validate, mode, validateDebounce, delayError, rules, className, renderError, as: As, asProps, input: Input, eventToValue, valueToProps, children, ...inputProps }: FormItemProps<TValues, P, TInputProps, TRawElement>): import("react").JSX.Element;
|
|
214
286
|
export {};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { default as FormItem } from './FormItem';
|
|
2
|
-
export type { FieldValidator, FormItemAsProps, FormItemBinding, FormItemOwnProps, FormItemProps } from './FormItem';
|
|
2
|
+
export type { FieldValidator, FormItemAsProps, FormItemBinding, FormItemOwnProps, FormItemProps, FormItemRawElement, FormItemRawElementBinding } from './FormItem';
|
|
3
3
|
export type { FormInstance, PathValueOf } from 'react-f0rm';
|
package/dist/types/index.d.ts
CHANGED
|
@@ -182,6 +182,6 @@ export type { DiffViewerProps, DiffLine } from './components/DiffViewer';
|
|
|
182
182
|
export { LogViewer } from './components/LogViewer';
|
|
183
183
|
export type { LogViewerProps, LogEntry, LogLevel } from './components/LogViewer';
|
|
184
184
|
export { FormItem } from './form';
|
|
185
|
-
export type { FieldValidator, FormItemAsProps, FormItemBinding, FormItemOwnProps, FormItemProps, FormInstance, PathValueOf, } from './form';
|
|
185
|
+
export type { FieldValidator, FormItemAsProps, FormItemBinding, FormItemOwnProps, FormItemProps, FormItemRawElement, FormItemRawElementBinding, FormInstance, PathValueOf, } from './form';
|
|
186
186
|
export { useControl } from 'react-use-control';
|
|
187
187
|
export type { Control, ControlOrValue } from 'react-use-control';
|