@tamagui/field 0.0.0-bootstrap.0 → 3.0.0-beta.643.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/LICENSE +21 -0
- package/dist/cjs/Field.cjs +857 -0
- package/dist/cjs/Field.native.cjs +1018 -0
- package/dist/cjs/Field.native.js +1020 -0
- package/dist/cjs/Field.native.js.map +1 -0
- package/dist/cjs/FieldContext.cjs +86 -0
- package/dist/cjs/FieldContext.native.cjs +90 -0
- package/dist/cjs/FieldContext.native.js +92 -0
- package/dist/cjs/FieldContext.native.js.map +1 -0
- package/dist/cjs/index.cjs +21 -0
- package/dist/cjs/index.native.cjs +23 -0
- package/dist/cjs/index.native.js +25 -0
- package/dist/cjs/index.native.js.map +1 -0
- package/dist/cjs/types.cjs +17 -0
- package/dist/cjs/types.native.cjs +19 -0
- package/dist/cjs/types.native.js +21 -0
- package/dist/cjs/types.native.js.map +1 -0
- package/dist/cjs/validation.cjs +114 -0
- package/dist/cjs/validation.native.cjs +126 -0
- package/dist/cjs/validation.native.js +128 -0
- package/dist/cjs/validation.native.js.map +1 -0
- package/dist/esm/Field.mjs +824 -0
- package/dist/esm/Field.mjs.map +1 -0
- package/dist/esm/Field.native.js +983 -0
- package/dist/esm/Field.native.js.map +1 -0
- package/dist/esm/FieldContext.mjs +51 -0
- package/dist/esm/FieldContext.mjs.map +1 -0
- package/dist/esm/FieldContext.native.js +53 -0
- package/dist/esm/FieldContext.native.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.mjs +5 -0
- package/dist/esm/index.native.js +5 -0
- package/dist/esm/types.mjs +0 -0
- package/dist/esm/types.native.js +0 -0
- package/dist/esm/validation.mjs +86 -0
- package/dist/esm/validation.mjs.map +1 -0
- package/dist/esm/validation.native.js +96 -0
- package/dist/esm/validation.native.js.map +1 -0
- package/dist/jsx/Field.mjs +824 -0
- package/dist/jsx/Field.mjs.map +1 -0
- package/dist/jsx/Field.native.cjs +1018 -0
- package/dist/jsx/Field.native.js +1020 -0
- package/dist/jsx/Field.native.js.map +1 -0
- package/dist/jsx/FieldContext.mjs +51 -0
- package/dist/jsx/FieldContext.mjs.map +1 -0
- package/dist/jsx/FieldContext.native.cjs +90 -0
- package/dist/jsx/FieldContext.native.js +92 -0
- package/dist/jsx/FieldContext.native.js.map +1 -0
- package/dist/jsx/index.js +5 -0
- package/dist/jsx/index.mjs +5 -0
- package/dist/jsx/index.native.cjs +23 -0
- package/dist/jsx/index.native.js +25 -0
- package/dist/jsx/index.native.js.map +1 -0
- package/dist/jsx/types.mjs +0 -0
- package/dist/jsx/types.native.cjs +19 -0
- package/dist/jsx/types.native.js +21 -0
- package/dist/jsx/types.native.js.map +1 -0
- package/dist/jsx/validation.mjs +86 -0
- package/dist/jsx/validation.mjs.map +1 -0
- package/dist/jsx/validation.native.cjs +126 -0
- package/dist/jsx/validation.native.js +128 -0
- package/dist/jsx/validation.native.js.map +1 -0
- package/package.json +50 -5
- package/src/Field.tsx +1129 -0
- package/src/FieldContext.tsx +61 -0
- package/src/index.ts +3 -0
- package/src/types.ts +125 -0
- package/src/validation.ts +136 -0
- package/types/Field.d.ts +401 -0
- package/types/Field.d.ts.map +1 -0
- package/types/FieldContext.d.ts +11 -0
- package/types/FieldContext.d.ts.map +1 -0
- package/types/index.d.ts +4 -0
- package/types/index.d.ts.map +1 -0
- package/types/types.d.ts +112 -0
- package/types/types.d.ts.map +1 -0
- package/types/validation.d.ts +24 -0
- package/types/validation.d.ts.map +1 -0
- package/README.md +0 -1
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { Text, View, createStyledHOC, isWeb, styled, useIsomorphicLayoutEffect, withStaticProperties } from "@tamagui/core";
|
|
2
|
+
import { useFormRegistryContext } from "@tamagui/form";
|
|
3
|
+
import { focusFocusable } from "@tamagui/focusable";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { FieldControlContext, FieldStateContext, FieldStyledContext, useFieldControl, useFieldState } from "./FieldContext.mjs";
|
|
6
|
+
import { createDefaultValidityState, createValidationCommitter, getValidationResult, isFilled, normalizeNativeValidity, shouldValidateOnChange } from "./validation.mjs";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
|
|
9
|
+
const fieldStateVariants = {
|
|
10
|
+
valid: { true: {} },
|
|
11
|
+
invalid: { true: {} },
|
|
12
|
+
touched: { true: {} },
|
|
13
|
+
dirty: { true: {} },
|
|
14
|
+
filled: { true: {} },
|
|
15
|
+
focused: { true: {} },
|
|
16
|
+
disabled: { true: {} }
|
|
17
|
+
};
|
|
18
|
+
const FieldFrame = styled(View, {
|
|
19
|
+
displayName: "Field",
|
|
20
|
+
context: FieldStyledContext,
|
|
21
|
+
variants: fieldStateVariants
|
|
22
|
+
});
|
|
23
|
+
const FieldLabelFrame = styled(Text, {
|
|
24
|
+
displayName: "FieldLabel",
|
|
25
|
+
render: "label",
|
|
26
|
+
context: FieldStyledContext,
|
|
27
|
+
variants: fieldStateVariants
|
|
28
|
+
});
|
|
29
|
+
const FieldDescriptionFrame = styled(Text, {
|
|
30
|
+
displayName: "FieldDescription",
|
|
31
|
+
render: "p",
|
|
32
|
+
context: FieldStyledContext,
|
|
33
|
+
variants: fieldStateVariants
|
|
34
|
+
});
|
|
35
|
+
const FieldErrorFrame = styled(Text, {
|
|
36
|
+
displayName: "FieldError",
|
|
37
|
+
render: "span",
|
|
38
|
+
context: FieldStyledContext,
|
|
39
|
+
variants: fieldStateVariants
|
|
40
|
+
});
|
|
41
|
+
const FieldItemFrame = styled(View, {
|
|
42
|
+
displayName: "FieldItem",
|
|
43
|
+
context: FieldStyledContext,
|
|
44
|
+
variants: fieldStateVariants
|
|
45
|
+
});
|
|
46
|
+
const validityKeys = [
|
|
47
|
+
"badInput",
|
|
48
|
+
"customError",
|
|
49
|
+
"patternMismatch",
|
|
50
|
+
"rangeOverflow",
|
|
51
|
+
"rangeUnderflow",
|
|
52
|
+
"stepMismatch",
|
|
53
|
+
"tooLong",
|
|
54
|
+
"tooShort",
|
|
55
|
+
"typeMismatch",
|
|
56
|
+
"valueMissing"
|
|
57
|
+
];
|
|
58
|
+
const AssociationContext = React.createContext(null);
|
|
59
|
+
const FieldRootContext = React.createContext(null);
|
|
60
|
+
const FieldItemContext = React.createContext(false);
|
|
61
|
+
function getText(children) {
|
|
62
|
+
const text = [];
|
|
63
|
+
React.Children.forEach(children, (child) => {
|
|
64
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
65
|
+
text.push(String(child));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (React.isValidElement(child)) {
|
|
69
|
+
const nested = getText(child.props.children);
|
|
70
|
+
if (nested) {
|
|
71
|
+
text.push(nested);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return text.join(" ").trim() || void 0;
|
|
76
|
+
}
|
|
77
|
+
function useAssociation(prefix, parent) {
|
|
78
|
+
const defaultControlId = `${prefix}-control`;
|
|
79
|
+
const controlIdsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
80
|
+
const labelsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
81
|
+
const messagesRef = React.useRef(/* @__PURE__ */ new Map());
|
|
82
|
+
const [, update] = React.useReducer((value) => value + 1, 0);
|
|
83
|
+
const registerControlId = React.useCallback((id) => {
|
|
84
|
+
const source = /* @__PURE__ */ Symbol();
|
|
85
|
+
controlIdsRef.current.set(source, id);
|
|
86
|
+
update();
|
|
87
|
+
return () => {
|
|
88
|
+
controlIdsRef.current.delete(source);
|
|
89
|
+
update();
|
|
90
|
+
};
|
|
91
|
+
}, []);
|
|
92
|
+
const registerLabel = React.useCallback((id, text) => {
|
|
93
|
+
const source = /* @__PURE__ */ Symbol();
|
|
94
|
+
labelsRef.current.set(source, {
|
|
95
|
+
id,
|
|
96
|
+
text
|
|
97
|
+
});
|
|
98
|
+
update();
|
|
99
|
+
return () => {
|
|
100
|
+
labelsRef.current.delete(source);
|
|
101
|
+
update();
|
|
102
|
+
};
|
|
103
|
+
}, []);
|
|
104
|
+
const registerMessage = React.useCallback((id, text) => {
|
|
105
|
+
const source = /* @__PURE__ */ Symbol();
|
|
106
|
+
messagesRef.current.set(source, {
|
|
107
|
+
id,
|
|
108
|
+
text
|
|
109
|
+
});
|
|
110
|
+
update();
|
|
111
|
+
return () => {
|
|
112
|
+
messagesRef.current.delete(source);
|
|
113
|
+
update();
|
|
114
|
+
};
|
|
115
|
+
}, []);
|
|
116
|
+
const controlId = Array.from(controlIdsRef.current.values()).at(-1) ?? defaultControlId;
|
|
117
|
+
const label = Array.from(labelsRef.current.values()).at(-1);
|
|
118
|
+
const messages = Array.from(messagesRef.current.values());
|
|
119
|
+
return React.useMemo(() => ({
|
|
120
|
+
controlId,
|
|
121
|
+
labelId: [parent?.labelId, label?.id].filter(Boolean).join(" ") || void 0,
|
|
122
|
+
labelText: [parent?.labelText, label?.text].filter(Boolean).join(" ") || void 0,
|
|
123
|
+
messageIds: Array.from(/* @__PURE__ */ new Set([...parent?.messageIds ?? [], ...messages.map((message) => message.id)])),
|
|
124
|
+
messageTexts: [...parent?.messageTexts ?? [], ...messages.flatMap((message) => message.text ? [message.text] : [])],
|
|
125
|
+
registerControlId,
|
|
126
|
+
registerLabel,
|
|
127
|
+
registerMessage
|
|
128
|
+
}), [
|
|
129
|
+
controlId,
|
|
130
|
+
label?.id,
|
|
131
|
+
label?.text,
|
|
132
|
+
messages,
|
|
133
|
+
parent?.labelId,
|
|
134
|
+
parent?.labelText,
|
|
135
|
+
parent?.messageIds,
|
|
136
|
+
parent?.messageTexts,
|
|
137
|
+
registerControlId,
|
|
138
|
+
registerLabel,
|
|
139
|
+
registerMessage
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
function getStyleState(state, disabled = state.disabled) {
|
|
143
|
+
return {
|
|
144
|
+
valid: state.valid === true,
|
|
145
|
+
invalid: state.valid === false,
|
|
146
|
+
touched: state.touched,
|
|
147
|
+
dirty: state.dirty,
|
|
148
|
+
filled: state.filled,
|
|
149
|
+
focused: state.focused,
|
|
150
|
+
disabled
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function getDataProps(state, disabled = state.disabled) {
|
|
154
|
+
if (!isWeb) {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
"data-valid": state.valid === true ? "" : void 0,
|
|
159
|
+
"data-invalid": state.valid === false ? "" : void 0,
|
|
160
|
+
"data-touched": state.touched ? "" : void 0,
|
|
161
|
+
"data-dirty": state.dirty ? "" : void 0,
|
|
162
|
+
"data-filled": state.filled ? "" : void 0,
|
|
163
|
+
"data-focused": state.focused ? "" : void 0,
|
|
164
|
+
"data-disabled": disabled ? "" : void 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function useControlContext(root, association, itemDisabled = false, isItem = false) {
|
|
168
|
+
const disabled = root.state.disabled || itemDisabled;
|
|
169
|
+
const controlIdRef = React.useRef(association.controlId);
|
|
170
|
+
const registrationDisabledRef = React.useRef(root.rootDisabled || itemDisabled);
|
|
171
|
+
controlIdRef.current = association.controlId;
|
|
172
|
+
registrationDisabledRef.current = root.rootDisabled || itemDisabled;
|
|
173
|
+
const registerControl = React.useCallback((registration) => {
|
|
174
|
+
const id = registration.id ?? controlIdRef.current;
|
|
175
|
+
const unregisterId = association.registerControlId(id);
|
|
176
|
+
const nextRegistration = {
|
|
177
|
+
...registration,
|
|
178
|
+
id,
|
|
179
|
+
get disabled() {
|
|
180
|
+
return registrationDisabledRef.current || registration.disabled;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const unregisterControl = root.registerControl(nextRegistration, !isItem);
|
|
184
|
+
return () => {
|
|
185
|
+
unregisterControl();
|
|
186
|
+
unregisterId();
|
|
187
|
+
};
|
|
188
|
+
}, [
|
|
189
|
+
association.registerControlId,
|
|
190
|
+
isItem,
|
|
191
|
+
root.registerControl
|
|
192
|
+
]);
|
|
193
|
+
return React.useMemo(() => ({
|
|
194
|
+
name: root.name,
|
|
195
|
+
disabled,
|
|
196
|
+
ariaProps: {
|
|
197
|
+
id: association.controlId,
|
|
198
|
+
...isWeb ? {
|
|
199
|
+
"aria-labelledby": association.labelId,
|
|
200
|
+
"aria-describedby": association.messageIds.join(" ") || void 0,
|
|
201
|
+
"aria-invalid": root.state.valid === false ? true : void 0
|
|
202
|
+
} : {
|
|
203
|
+
accessibilityLabel: association.labelText,
|
|
204
|
+
accessibilityHint: association.messageTexts.join(" ") || void 0,
|
|
205
|
+
accessibilityState: disabled ? { disabled: true } : void 0
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
dataProps: getDataProps(root.state, disabled),
|
|
209
|
+
onFocus: root.onFocus,
|
|
210
|
+
onBlur: root.onBlur,
|
|
211
|
+
onChange: root.onChange,
|
|
212
|
+
onDisabledChange: isItem ? () => {} : root.onDisabledChange,
|
|
213
|
+
registerControl
|
|
214
|
+
}), [
|
|
215
|
+
association.controlId,
|
|
216
|
+
association.labelId,
|
|
217
|
+
association.labelText,
|
|
218
|
+
association.messageIds,
|
|
219
|
+
association.messageTexts,
|
|
220
|
+
disabled,
|
|
221
|
+
isItem,
|
|
222
|
+
registerControl,
|
|
223
|
+
root.name,
|
|
224
|
+
root.onBlur,
|
|
225
|
+
root.onChange,
|
|
226
|
+
root.onDisabledChange,
|
|
227
|
+
root.onFocus,
|
|
228
|
+
root.state
|
|
229
|
+
]);
|
|
230
|
+
}
|
|
231
|
+
function getRegistrationValue(registration) {
|
|
232
|
+
return registration?.getValue ? registration.getValue() : registration?.value;
|
|
233
|
+
}
|
|
234
|
+
function getInput(registration) {
|
|
235
|
+
return registration.inputRef?.current;
|
|
236
|
+
}
|
|
237
|
+
function canValidateInput(input) {
|
|
238
|
+
return Boolean(input && input.validity && typeof input.setCustomValidity === "function" && !input.disabled);
|
|
239
|
+
}
|
|
240
|
+
function isEligibleInput(input, formElement) {
|
|
241
|
+
if (input.matches?.(":disabled")) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (!formElement || input.form === formElement) {
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
return input.form === null && !input.hasAttribute?.("form");
|
|
248
|
+
}
|
|
249
|
+
function isEligibleRegistration(registration, formElement) {
|
|
250
|
+
if (registration.disabled) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const input = getInput(registration);
|
|
254
|
+
return !canValidateInput(input) || isEligibleInput(input, formElement);
|
|
255
|
+
}
|
|
256
|
+
function getNativeValidity(input, showValueMissing) {
|
|
257
|
+
const state = createDefaultValidityState(true);
|
|
258
|
+
for (const key of validityKeys) {
|
|
259
|
+
state[key] = Boolean(input.validity[key]);
|
|
260
|
+
}
|
|
261
|
+
state.valid = Boolean(input.validity.valid);
|
|
262
|
+
return normalizeNativeValidity(state, showValueMissing);
|
|
263
|
+
}
|
|
264
|
+
function hasValidityError(state) {
|
|
265
|
+
return state.valid === false;
|
|
266
|
+
}
|
|
267
|
+
function hasError(errors) {
|
|
268
|
+
return Array.isArray(errors) ? errors.length > 0 : Boolean(errors);
|
|
269
|
+
}
|
|
270
|
+
const FieldComponent = createStyledHOC(FieldFrame, function Field({ name: nameProp, disabled: disabledProp = false, invalid = false, validate, validationMode: validationModeProp, validationDebounceTime = 0, children, ...fieldProps }, forwardedRef) {
|
|
271
|
+
const form = useFormRegistryContext();
|
|
272
|
+
const { clearErrors: clearFormErrors, errors: formErrors, formElementRef, getValues: getFormValues, registerField: registerFormField, submitAttemptedRef, validationMode: formValidationMode } = form;
|
|
273
|
+
const reactId = React.useId().replace(/:/g, "");
|
|
274
|
+
const association = useAssociation(`field-${reactId}`);
|
|
275
|
+
const fieldId = `field-${reactId}`;
|
|
276
|
+
const controlsRef = React.useRef(/* @__PURE__ */ new Map());
|
|
277
|
+
const reportsDisabledRef = React.useRef(/* @__PURE__ */ new WeakMap());
|
|
278
|
+
const activeControlRef = React.useRef(void 0);
|
|
279
|
+
const activeValidationControlRef = React.useRef(void 0);
|
|
280
|
+
const formControlRef = React.useRef(null);
|
|
281
|
+
const formValidityDataRef = React.useRef({ state: { valid: null } });
|
|
282
|
+
const initialValueRef = React.useRef(void 0);
|
|
283
|
+
const hasInitialValueRef = React.useRef(false);
|
|
284
|
+
const valueRef = React.useRef(void 0);
|
|
285
|
+
const dirtyRef = React.useRef(false);
|
|
286
|
+
const debounceRef = React.useRef(void 0);
|
|
287
|
+
const [registeredName, setRegisteredName] = React.useState();
|
|
288
|
+
const [controlDisabled, setControlDisabled] = React.useState(false);
|
|
289
|
+
const [touched, setTouched] = React.useState(false);
|
|
290
|
+
const [dirty, setDirty] = React.useState(false);
|
|
291
|
+
const [filled, setFilled] = React.useState(false);
|
|
292
|
+
const [focused, setFocused] = React.useState(false);
|
|
293
|
+
const [currentValue, setCurrentValue] = React.useState(void 0);
|
|
294
|
+
const [validityData, setValidityData] = React.useState({
|
|
295
|
+
state: createDefaultValidityState(),
|
|
296
|
+
error: "",
|
|
297
|
+
errors: [],
|
|
298
|
+
value: void 0,
|
|
299
|
+
initialValue: void 0
|
|
300
|
+
});
|
|
301
|
+
const validityDataRef = React.useRef(validityData);
|
|
302
|
+
validityDataRef.current = validityData;
|
|
303
|
+
const effectiveName = nameProp ?? registeredName;
|
|
304
|
+
const effectiveNameRef = React.useRef(effectiveName);
|
|
305
|
+
effectiveNameRef.current = effectiveName;
|
|
306
|
+
const disabled = disabledProp || controlDisabled;
|
|
307
|
+
const disabledRef = React.useRef(disabled);
|
|
308
|
+
disabledRef.current = disabled;
|
|
309
|
+
const validationMode = validationModeProp ?? formValidationMode;
|
|
310
|
+
const externalError = effectiveName && Object.hasOwn(formErrors, effectiveName) ? formErrors[effectiveName] : void 0;
|
|
311
|
+
const externalErrors = Array.isArray(externalError) ? externalError : externalError ? [externalError] : [];
|
|
312
|
+
const externallyInvalid = invalid || hasError(externalError);
|
|
313
|
+
const externallyInvalidRef = React.useRef(externallyInvalid);
|
|
314
|
+
externallyInvalidRef.current = externallyInvalid;
|
|
315
|
+
const valid = externallyInvalid ? false : disabled ? null : validityData.state.valid;
|
|
316
|
+
formValidityDataRef.current.state.valid = disabled ? null : externallyInvalid ? false : validityData.state.valid;
|
|
317
|
+
const errors = externalErrors.length ? externalErrors : validityData.errors;
|
|
318
|
+
const error = errors[0] ?? "";
|
|
319
|
+
const state = React.useMemo(() => ({
|
|
320
|
+
name: effectiveName,
|
|
321
|
+
value: currentValue,
|
|
322
|
+
error,
|
|
323
|
+
errors,
|
|
324
|
+
validity: validityData.state,
|
|
325
|
+
valid,
|
|
326
|
+
touched,
|
|
327
|
+
dirty,
|
|
328
|
+
filled,
|
|
329
|
+
focused,
|
|
330
|
+
disabled
|
|
331
|
+
}), [
|
|
332
|
+
dirty,
|
|
333
|
+
disabled,
|
|
334
|
+
currentValue,
|
|
335
|
+
effectiveName,
|
|
336
|
+
error,
|
|
337
|
+
errors,
|
|
338
|
+
filled,
|
|
339
|
+
focused,
|
|
340
|
+
touched,
|
|
341
|
+
valid,
|
|
342
|
+
validityData.state
|
|
343
|
+
]);
|
|
344
|
+
const stateRef = React.useRef(state);
|
|
345
|
+
stateRef.current = state;
|
|
346
|
+
const publishValidityRef = React.useRef(() => {});
|
|
347
|
+
const validationCommitterRef = React.useRef(void 0);
|
|
348
|
+
if (!validationCommitterRef.current) {
|
|
349
|
+
validationCommitterRef.current = createValidationCommitter((data) => {
|
|
350
|
+
publishValidityRef.current(data);
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
const getActiveControl = React.useCallback(() => {
|
|
354
|
+
let active;
|
|
355
|
+
for (const registration of controlsRef.current.values()) {
|
|
356
|
+
if (!isEligibleRegistration(registration, formElementRef.current)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
active = registration;
|
|
360
|
+
}
|
|
361
|
+
return active;
|
|
362
|
+
}, [formElementRef]);
|
|
363
|
+
const syncControlDisabled = React.useCallback(() => {
|
|
364
|
+
let nextDisabled = false;
|
|
365
|
+
for (const registration of controlsRef.current.values()) {
|
|
366
|
+
if (reportsDisabledRef.current.get(registration)) {
|
|
367
|
+
nextDisabled = Boolean(registration.disabled);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
setControlDisabled(nextDisabled);
|
|
371
|
+
}, []);
|
|
372
|
+
const getRepresentativeControl = React.useCallback(() => {
|
|
373
|
+
let fallback;
|
|
374
|
+
for (const registration of controlsRef.current.values()) {
|
|
375
|
+
const input = getInput(registration);
|
|
376
|
+
if (!isEligibleRegistration(registration, formElementRef.current)) {
|
|
377
|
+
continue;
|
|
378
|
+
}
|
|
379
|
+
fallback ??= registration;
|
|
380
|
+
if (canValidateInput(input) && !input.validity.valid) {
|
|
381
|
+
return registration;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
return fallback ?? getActiveControl();
|
|
385
|
+
}, [formElementRef, getActiveControl]);
|
|
386
|
+
const clearCustomValidity = React.useCallback(() => {
|
|
387
|
+
for (const registration of controlsRef.current.values()) {
|
|
388
|
+
const input = getInput(registration);
|
|
389
|
+
if (canValidateInput(input)) {
|
|
390
|
+
input.setCustomValidity("");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}, []);
|
|
394
|
+
const setCustomValidity = React.useCallback((message) => {
|
|
395
|
+
for (const registration of controlsRef.current.values()) {
|
|
396
|
+
if (!isEligibleRegistration(registration, formElementRef.current)) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const input = getInput(registration);
|
|
400
|
+
if (canValidateInput(input)) {
|
|
401
|
+
input.setCustomValidity(message);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}, [formElementRef]);
|
|
405
|
+
const publishValidity = React.useCallback((data) => {
|
|
406
|
+
if (data.state.customError) {
|
|
407
|
+
setCustomValidity(data.errors.join("\n"));
|
|
408
|
+
} else {
|
|
409
|
+
clearCustomValidity();
|
|
410
|
+
}
|
|
411
|
+
formValidityDataRef.current.state.valid = disabledRef.current ? null : externallyInvalidRef.current ? false : data.state.valid;
|
|
412
|
+
validityDataRef.current = data;
|
|
413
|
+
setCurrentValue(data.value);
|
|
414
|
+
setValidityData(data);
|
|
415
|
+
}, [clearCustomValidity, setCustomValidity]);
|
|
416
|
+
publishValidityRef.current = publishValidity;
|
|
417
|
+
const validateValue = React.useCallback((value, submit = false, fromChange = false) => {
|
|
418
|
+
if (debounceRef.current) {
|
|
419
|
+
clearTimeout(debounceRef.current);
|
|
420
|
+
debounceRef.current = void 0;
|
|
421
|
+
}
|
|
422
|
+
const showValueMissing = dirtyRef.current || submit || submitAttemptedRef.current;
|
|
423
|
+
if (fromChange) {
|
|
424
|
+
clearCustomValidity();
|
|
425
|
+
}
|
|
426
|
+
const representative = getRepresentativeControl();
|
|
427
|
+
activeValidationControlRef.current = representative;
|
|
428
|
+
formControlRef.current = representative?.controlRef.current ?? null;
|
|
429
|
+
const input = representative && getInput(representative);
|
|
430
|
+
let nativeState = createDefaultValidityState(true);
|
|
431
|
+
let nativeMessage = "";
|
|
432
|
+
if (canValidateInput(input)) {
|
|
433
|
+
nativeState = getNativeValidity(input, showValueMissing);
|
|
434
|
+
nativeMessage = nativeState.valid === false ? input.validationMessage : "";
|
|
435
|
+
} else {
|
|
436
|
+
const required = Array.from(controlsRef.current.values()).some((registration) => registration.required && isEligibleRegistration(registration, formElementRef.current));
|
|
437
|
+
if (required && !isFilled(value) && showValueMissing) {
|
|
438
|
+
nativeState = {
|
|
439
|
+
...createDefaultValidityState(false),
|
|
440
|
+
valueMissing: true
|
|
441
|
+
};
|
|
442
|
+
nativeMessage = "Please fill out this field.";
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const initialValue = initialValueRef.current;
|
|
446
|
+
const makeValidityData = (customErrors) => {
|
|
447
|
+
if (customErrors.length) {
|
|
448
|
+
return {
|
|
449
|
+
state: {
|
|
450
|
+
...nativeState,
|
|
451
|
+
valid: false,
|
|
452
|
+
customError: true
|
|
453
|
+
},
|
|
454
|
+
error: customErrors[0] ?? "",
|
|
455
|
+
errors: customErrors,
|
|
456
|
+
value,
|
|
457
|
+
initialValue
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
if (hasValidityError(nativeState)) {
|
|
461
|
+
return {
|
|
462
|
+
state: nativeState,
|
|
463
|
+
error: nativeMessage,
|
|
464
|
+
errors: nativeMessage ? [nativeMessage] : [],
|
|
465
|
+
value,
|
|
466
|
+
initialValue
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
state: createDefaultValidityState(true),
|
|
471
|
+
error: "",
|
|
472
|
+
errors: [],
|
|
473
|
+
value,
|
|
474
|
+
initialValue
|
|
475
|
+
};
|
|
476
|
+
};
|
|
477
|
+
if (nativeMessage && !fromChange) {
|
|
478
|
+
validationCommitterRef.current.run(makeValidityData([]));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const result = validate ? getValidationResult(validate, value, getFormValues()) : [];
|
|
482
|
+
if (result instanceof Promise) {
|
|
483
|
+
void validationCommitterRef.current.run(result.then((customErrors) => makeValidityData(customErrors)));
|
|
484
|
+
} else {
|
|
485
|
+
validationCommitterRef.current.run(makeValidityData(result));
|
|
486
|
+
}
|
|
487
|
+
}, [
|
|
488
|
+
clearCustomValidity,
|
|
489
|
+
formElementRef,
|
|
490
|
+
getFormValues,
|
|
491
|
+
getRepresentativeControl,
|
|
492
|
+
submitAttemptedRef,
|
|
493
|
+
validate
|
|
494
|
+
]);
|
|
495
|
+
const registerControl = React.useCallback((registration, reportDisabled = true) => {
|
|
496
|
+
const source = /* @__PURE__ */ Symbol();
|
|
497
|
+
controlsRef.current.set(source, registration);
|
|
498
|
+
reportsDisabledRef.current.set(registration, reportDisabled);
|
|
499
|
+
activeControlRef.current = getActiveControl();
|
|
500
|
+
formControlRef.current = activeControlRef.current?.controlRef.current ?? null;
|
|
501
|
+
const value = getRegistrationValue(registration);
|
|
502
|
+
valueRef.current = value;
|
|
503
|
+
setCurrentValue(value);
|
|
504
|
+
if (!hasInitialValueRef.current) {
|
|
505
|
+
hasInitialValueRef.current = true;
|
|
506
|
+
initialValueRef.current = value;
|
|
507
|
+
validityDataRef.current = {
|
|
508
|
+
...validityDataRef.current,
|
|
509
|
+
initialValue: value
|
|
510
|
+
};
|
|
511
|
+
setValidityData(validityDataRef.current);
|
|
512
|
+
}
|
|
513
|
+
if (!nameProp && registration.name) {
|
|
514
|
+
setRegisteredName(registration.name);
|
|
515
|
+
}
|
|
516
|
+
setFilled(isFilled(value));
|
|
517
|
+
syncControlDisabled();
|
|
518
|
+
return () => {
|
|
519
|
+
controlsRef.current.delete(source);
|
|
520
|
+
activeControlRef.current = getActiveControl();
|
|
521
|
+
activeValidationControlRef.current = void 0;
|
|
522
|
+
formControlRef.current = activeControlRef.current?.controlRef.current ?? null;
|
|
523
|
+
const next = activeControlRef.current;
|
|
524
|
+
const nextValue = getRegistrationValue(next);
|
|
525
|
+
valueRef.current = nextValue;
|
|
526
|
+
setCurrentValue(nextValue);
|
|
527
|
+
setFilled(isFilled(nextValue));
|
|
528
|
+
if (!nameProp) {
|
|
529
|
+
setRegisteredName(next?.name);
|
|
530
|
+
}
|
|
531
|
+
syncControlDisabled();
|
|
532
|
+
};
|
|
533
|
+
}, [
|
|
534
|
+
getActiveControl,
|
|
535
|
+
nameProp,
|
|
536
|
+
syncControlDisabled
|
|
537
|
+
]);
|
|
538
|
+
const onFocus = React.useCallback(() => {
|
|
539
|
+
setFocused(true);
|
|
540
|
+
}, []);
|
|
541
|
+
const onBlur = React.useCallback((value = valueRef.current) => {
|
|
542
|
+
valueRef.current = value;
|
|
543
|
+
setCurrentValue(value);
|
|
544
|
+
setTouched(true);
|
|
545
|
+
setFocused(false);
|
|
546
|
+
if (validationMode === "onBlur") {
|
|
547
|
+
validateValue(value);
|
|
548
|
+
}
|
|
549
|
+
}, [validateValue, validationMode]);
|
|
550
|
+
const onChange = React.useCallback((value) => {
|
|
551
|
+
valueRef.current = value;
|
|
552
|
+
setCurrentValue(value);
|
|
553
|
+
validationCommitterRef.current.invalidate();
|
|
554
|
+
if (debounceRef.current) {
|
|
555
|
+
clearTimeout(debounceRef.current);
|
|
556
|
+
debounceRef.current = void 0;
|
|
557
|
+
}
|
|
558
|
+
const nextDirty = !Object.is(value, initialValueRef.current);
|
|
559
|
+
dirtyRef.current = nextDirty;
|
|
560
|
+
setDirty(nextDirty);
|
|
561
|
+
setFilled(isFilled(value));
|
|
562
|
+
clearFormErrors(effectiveNameRef.current);
|
|
563
|
+
const validateOnChange = shouldValidateOnChange(validationMode, submitAttemptedRef.current);
|
|
564
|
+
if (validateOnChange) {
|
|
565
|
+
if (validationMode === "onChange" && validationDebounceTime > 0 && value !== "") {
|
|
566
|
+
debounceRef.current = setTimeout(() => {
|
|
567
|
+
validateValue(value, false, true);
|
|
568
|
+
}, validationDebounceTime);
|
|
569
|
+
} else {
|
|
570
|
+
validateValue(value, false, true);
|
|
571
|
+
}
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
if (stateRef.current.valid === false) {
|
|
575
|
+
const required = Array.from(controlsRef.current.values()).some((registration) => registration.required && isEligibleRegistration(registration, formElementRef.current));
|
|
576
|
+
if (required && !isFilled(value) && dirtyRef.current) {
|
|
577
|
+
validateValue(value);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
clearCustomValidity();
|
|
581
|
+
publishValidity({
|
|
582
|
+
state: createDefaultValidityState(true),
|
|
583
|
+
error: "",
|
|
584
|
+
errors: [],
|
|
585
|
+
value,
|
|
586
|
+
initialValue: initialValueRef.current
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}, [
|
|
590
|
+
clearFormErrors,
|
|
591
|
+
clearCustomValidity,
|
|
592
|
+
formElementRef,
|
|
593
|
+
publishValidity,
|
|
594
|
+
submitAttemptedRef,
|
|
595
|
+
validateValue,
|
|
596
|
+
validationDebounceTime,
|
|
597
|
+
validationMode
|
|
598
|
+
]);
|
|
599
|
+
const onDisabledChange = React.useCallback((nextDisabled) => {
|
|
600
|
+
setControlDisabled(nextDisabled);
|
|
601
|
+
}, []);
|
|
602
|
+
React.useEffect(() => {
|
|
603
|
+
return () => {
|
|
604
|
+
validationCommitterRef.current?.invalidate();
|
|
605
|
+
if (debounceRef.current) {
|
|
606
|
+
clearTimeout(debounceRef.current);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}, []);
|
|
610
|
+
const formRegistration = React.useMemo(() => ({
|
|
611
|
+
controlRef: formControlRef,
|
|
612
|
+
validityData: formValidityDataRef.current,
|
|
613
|
+
get name() {
|
|
614
|
+
return stateRef.current.disabled ? void 0 : effectiveNameRef.current;
|
|
615
|
+
},
|
|
616
|
+
get controlId() {
|
|
617
|
+
return activeValidationControlRef.current?.id ?? getRepresentativeControl()?.id ?? association.controlId;
|
|
618
|
+
},
|
|
619
|
+
getValue() {
|
|
620
|
+
return getRegistrationValue(getActiveControl()) ?? valueRef.current;
|
|
621
|
+
},
|
|
622
|
+
validate() {
|
|
623
|
+
validateValue(getRegistrationValue(getActiveControl()) ?? valueRef.current, true);
|
|
624
|
+
}
|
|
625
|
+
}), [
|
|
626
|
+
association.controlId,
|
|
627
|
+
getActiveControl,
|
|
628
|
+
getRepresentativeControl,
|
|
629
|
+
validateValue
|
|
630
|
+
]);
|
|
631
|
+
React.useEffect(() => {
|
|
632
|
+
return registerFormField(fieldId, formRegistration);
|
|
633
|
+
}, [
|
|
634
|
+
fieldId,
|
|
635
|
+
formRegistration,
|
|
636
|
+
registerFormField
|
|
637
|
+
]);
|
|
638
|
+
const rootContext = React.useMemo(() => ({
|
|
639
|
+
state,
|
|
640
|
+
validityData,
|
|
641
|
+
name: effectiveName,
|
|
642
|
+
rootDisabled: disabledProp,
|
|
643
|
+
registerControl,
|
|
644
|
+
onFocus,
|
|
645
|
+
onBlur,
|
|
646
|
+
onChange,
|
|
647
|
+
onDisabledChange
|
|
648
|
+
}), [
|
|
649
|
+
disabledProp,
|
|
650
|
+
effectiveName,
|
|
651
|
+
onBlur,
|
|
652
|
+
onChange,
|
|
653
|
+
onDisabledChange,
|
|
654
|
+
onFocus,
|
|
655
|
+
registerControl,
|
|
656
|
+
state,
|
|
657
|
+
validityData
|
|
658
|
+
]);
|
|
659
|
+
const controlContext = useControlContext(rootContext, association);
|
|
660
|
+
const styleState = getStyleState(state);
|
|
661
|
+
return /* @__PURE__ */ jsx(FieldStateContext.Provider, {
|
|
662
|
+
value: state,
|
|
663
|
+
children: /* @__PURE__ */ jsx(FieldRootContext.Provider, {
|
|
664
|
+
value: rootContext,
|
|
665
|
+
children: /* @__PURE__ */ jsx(AssociationContext.Provider, {
|
|
666
|
+
value: association,
|
|
667
|
+
children: /* @__PURE__ */ jsx(FieldControlContext.Provider, {
|
|
668
|
+
value: controlContext,
|
|
669
|
+
children: /* @__PURE__ */ jsx(FieldStyledContext.Provider, {
|
|
670
|
+
...styleState,
|
|
671
|
+
children: /* @__PURE__ */ jsx(FieldFrame, {
|
|
672
|
+
...fieldProps,
|
|
673
|
+
...getDataProps(state),
|
|
674
|
+
disabled,
|
|
675
|
+
ref: forwardedRef,
|
|
676
|
+
children
|
|
677
|
+
})
|
|
678
|
+
})
|
|
679
|
+
})
|
|
680
|
+
})
|
|
681
|
+
})
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
function usePartContexts() {
|
|
685
|
+
const root = React.useContext(FieldRootContext);
|
|
686
|
+
const association = React.useContext(AssociationContext);
|
|
687
|
+
const itemDisabled = React.useContext(FieldItemContext);
|
|
688
|
+
if (!root || !association) {
|
|
689
|
+
throw new Error("Field parts must be rendered inside <Field>.");
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
root,
|
|
693
|
+
association,
|
|
694
|
+
itemDisabled
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
const FieldLabel = createStyledHOC(FieldLabelFrame, function FieldLabel2({ id: idProp, children, onPress, onMouseDown, ...labelProps }, forwardedRef) {
|
|
698
|
+
const { root, association, itemDisabled } = usePartContexts();
|
|
699
|
+
const generatedId = React.useId().replace(/:/g, "");
|
|
700
|
+
const id = idProp ?? `field-label-${generatedId}`;
|
|
701
|
+
const text = getText(children);
|
|
702
|
+
const disabled = root.state.disabled || itemDisabled;
|
|
703
|
+
useIsomorphicLayoutEffect(() => {
|
|
704
|
+
return association.registerLabel(id, text);
|
|
705
|
+
}, [
|
|
706
|
+
association.registerLabel,
|
|
707
|
+
id,
|
|
708
|
+
text
|
|
709
|
+
]);
|
|
710
|
+
return /* @__PURE__ */ jsx(FieldLabelFrame, {
|
|
711
|
+
...labelProps,
|
|
712
|
+
...getDataProps(root.state, disabled),
|
|
713
|
+
id,
|
|
714
|
+
ref: forwardedRef,
|
|
715
|
+
...isWeb ? { htmlFor: association.controlId } : void 0,
|
|
716
|
+
...!isWeb && disabled ? { accessibilityState: { disabled: true } } : void 0,
|
|
717
|
+
onMouseDown: (event) => {
|
|
718
|
+
onMouseDown?.(event);
|
|
719
|
+
if (!event.defaultPrevented && event.detail > 1) {
|
|
720
|
+
event.preventDefault();
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
onPress: (event) => {
|
|
724
|
+
onPress?.(event);
|
|
725
|
+
if (!isWeb && !event?.defaultPrevented) {
|
|
726
|
+
focusFocusable(association.controlId);
|
|
727
|
+
}
|
|
728
|
+
},
|
|
729
|
+
children
|
|
730
|
+
});
|
|
731
|
+
});
|
|
732
|
+
const FieldDescription = createStyledHOC(FieldDescriptionFrame, function FieldDescription2({ id: idProp, children, ...descriptionProps }, forwardedRef) {
|
|
733
|
+
const { root, association, itemDisabled } = usePartContexts();
|
|
734
|
+
const generatedId = React.useId().replace(/:/g, "");
|
|
735
|
+
const id = idProp ?? `field-description-${generatedId}`;
|
|
736
|
+
const text = getText(children);
|
|
737
|
+
const disabled = root.state.disabled || itemDisabled;
|
|
738
|
+
useIsomorphicLayoutEffect(() => {
|
|
739
|
+
return association.registerMessage(id, text);
|
|
740
|
+
}, [
|
|
741
|
+
association.registerMessage,
|
|
742
|
+
id,
|
|
743
|
+
text
|
|
744
|
+
]);
|
|
745
|
+
return /* @__PURE__ */ jsx(FieldDescriptionFrame, {
|
|
746
|
+
...descriptionProps,
|
|
747
|
+
...getDataProps(root.state, disabled),
|
|
748
|
+
id,
|
|
749
|
+
ref: forwardedRef,
|
|
750
|
+
children
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
const FieldError = createStyledHOC(FieldErrorFrame, function FieldError2({ id: idProp, children, match, ...errorProps }, forwardedRef) {
|
|
754
|
+
const { root, association, itemDisabled } = usePartContexts();
|
|
755
|
+
const generatedId = React.useId().replace(/:/g, "");
|
|
756
|
+
const id = idProp ?? `field-error-${generatedId}`;
|
|
757
|
+
const disabled = root.state.disabled || itemDisabled;
|
|
758
|
+
const rendered = match === true || !disabled && (typeof match === "string" ? Boolean(root.validityData.state[match]) : root.state.valid === false);
|
|
759
|
+
const message = children ?? root.state.errors.join("\n");
|
|
760
|
+
const text = getText(message);
|
|
761
|
+
useIsomorphicLayoutEffect(() => {
|
|
762
|
+
if (!rendered) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
return association.registerMessage(id, text);
|
|
766
|
+
}, [
|
|
767
|
+
association.registerMessage,
|
|
768
|
+
id,
|
|
769
|
+
rendered,
|
|
770
|
+
text
|
|
771
|
+
]);
|
|
772
|
+
if (!rendered) {
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
return /* @__PURE__ */ jsx(FieldErrorFrame, {
|
|
776
|
+
...errorProps,
|
|
777
|
+
...getDataProps(root.state, disabled),
|
|
778
|
+
id,
|
|
779
|
+
ref: forwardedRef,
|
|
780
|
+
children: message
|
|
781
|
+
});
|
|
782
|
+
});
|
|
783
|
+
const FieldItem = createStyledHOC(FieldItemFrame, function FieldItem2({ disabled: disabledProp = false, children, ...itemProps }, forwardedRef) {
|
|
784
|
+
const root = React.useContext(FieldRootContext);
|
|
785
|
+
const parentAssociation = React.useContext(AssociationContext);
|
|
786
|
+
const reactId = React.useId().replace(/:/g, "");
|
|
787
|
+
const association = useAssociation(`field-item-${reactId}`, parentAssociation);
|
|
788
|
+
if (!root || !parentAssociation) {
|
|
789
|
+
throw new Error("Field.Item must be rendered inside <Field>.");
|
|
790
|
+
}
|
|
791
|
+
const disabled = root.state.disabled || disabledProp;
|
|
792
|
+
const controlContext = useControlContext(root, association, disabledProp, true);
|
|
793
|
+
const styleState = getStyleState(root.state, disabled);
|
|
794
|
+
return /* @__PURE__ */ jsx(FieldItemContext.Provider, {
|
|
795
|
+
value: disabledProp,
|
|
796
|
+
children: /* @__PURE__ */ jsx(AssociationContext.Provider, {
|
|
797
|
+
value: association,
|
|
798
|
+
children: /* @__PURE__ */ jsx(FieldControlContext.Provider, {
|
|
799
|
+
value: controlContext,
|
|
800
|
+
children: /* @__PURE__ */ jsx(FieldStyledContext.Provider, {
|
|
801
|
+
...styleState,
|
|
802
|
+
children: /* @__PURE__ */ jsx(FieldItemFrame, {
|
|
803
|
+
...itemProps,
|
|
804
|
+
...getDataProps(root.state, disabled),
|
|
805
|
+
disabled,
|
|
806
|
+
ref: forwardedRef,
|
|
807
|
+
children
|
|
808
|
+
})
|
|
809
|
+
})
|
|
810
|
+
})
|
|
811
|
+
})
|
|
812
|
+
});
|
|
813
|
+
});
|
|
814
|
+
const Field2 = withStaticProperties(FieldComponent, {
|
|
815
|
+
Label: FieldLabel,
|
|
816
|
+
Description: FieldDescription,
|
|
817
|
+
Error: FieldError,
|
|
818
|
+
Item: FieldItem,
|
|
819
|
+
useFieldState,
|
|
820
|
+
useFieldControl
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
export { Field2 as Field, FieldDescriptionFrame, FieldErrorFrame, FieldFrame, FieldItemFrame, FieldLabelFrame };
|
|
824
|
+
//# sourceMappingURL=Field.mjs.map
|