@t007/input 0.0.23 → 0.0.25
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 +64 -2
- package/dist/index.css +6 -4
- package/dist/index.d.ts +20 -51
- package/dist/index.global.js +142 -156
- package/dist/index.js +96 -124
- package/dist/react.d.ts +139 -0
- package/dist/react.js +255 -0
- package/package.json +22 -2
package/dist/react.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/ts/react/components/Input.tsx
|
|
2
|
+
import React, { useRef, useEffect, useMemo, useState, useCallback, useImperativeHandle } from "react";
|
|
3
|
+
|
|
4
|
+
// src/ts/react/utils/consts.ts
|
|
5
|
+
var parentBeacon = "data-t007-input-show-error";
|
|
6
|
+
var violationKeys = ["valueMissing", "typeMismatch", "patternMismatch", "stepMismatch", "tooShort", "tooLong", "rangeUnderflow", "rangeOverflow", "badInput", "customError"];
|
|
7
|
+
var unsafeProps = ["nativeIcon", "passwordMeter", "eyeToggler", "passwordHiddenIcon", "passwordVisibleIcon", "options", "indeterminate", "maxSize", "minSize", "maxTotalSize", "minTotalSize"];
|
|
8
|
+
|
|
9
|
+
// src/ts/react/utils/fn.ts
|
|
10
|
+
var fireInput = (el) => el?.dispatchEvent?.(new Event("input", { bubbles: true }));
|
|
11
|
+
var formatWordsHelperText = (template = "", count, maxCount) => template.replace(/%left%/g, String(Math.max(0, maxCount - count))).replace(/%max%/g, String(maxCount)).replace(/%count%/g, String(count)).replace(/%excess%/g, String(Math.max(0, count - maxCount)));
|
|
12
|
+
function rExclude(obj, keys) {
|
|
13
|
+
const copy = { ...obj };
|
|
14
|
+
keys.forEach((k) => delete copy[k]);
|
|
15
|
+
return copy;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/ts/utils/fn.ts
|
|
19
|
+
import { formatSize } from "@t007/utils";
|
|
20
|
+
function getStrengthLevel(value, minLength = 0) {
|
|
21
|
+
value = value.trim();
|
|
22
|
+
let level = 0;
|
|
23
|
+
if (value.length < minLength) level = 1;
|
|
24
|
+
else {
|
|
25
|
+
if (/[a-z]/.test(value)) level++;
|
|
26
|
+
if (/[A-Z]/.test(value)) level++;
|
|
27
|
+
if (/[0-9]/.test(value)) level++;
|
|
28
|
+
if (/[\W_]/.test(value)) level++;
|
|
29
|
+
}
|
|
30
|
+
return Math.min(level, 4);
|
|
31
|
+
}
|
|
32
|
+
function getFilesHelper(files, opts) {
|
|
33
|
+
if (!files || !files.length) return { violation: null, message: "" };
|
|
34
|
+
const totalFiles = files.length;
|
|
35
|
+
let totalSize = 0, currFiles = 0;
|
|
36
|
+
const setMaxError = (size, max, n = 0) => ({ violation: "rangeOverflow", message: n ? `File ${files.length > 1 ? n : ""} size of ${formatSize(size)} exceeds the per file maximum of ${formatSize(max)}` : `Total files size of ${formatSize(size)} exceeds the total maximum of ${formatSize(max)}` });
|
|
37
|
+
const setMinError = (size, min, n = 0) => ({ violation: "rangeUnderflow", message: n ? `File ${files.length > 1 ? n : ""} size of ${formatSize(size)} is less than the per file minimum of ${formatSize(min)}` : `Total files size of ${formatSize(size)} is less than the total minimum of ${formatSize(min)}` });
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
currFiles++;
|
|
40
|
+
totalSize += file.size;
|
|
41
|
+
if (opts.accept) {
|
|
42
|
+
const acceptedTypes = opts.accept.split(",").map((type) => type.trim().replace(/^[*\.]+|[*\.]+$/g, "")).filter(Boolean) || [];
|
|
43
|
+
if (!acceptedTypes.some((type) => file.type.includes(type))) return { violation: "typeMismatch", message: `File${currFiles > 1 ? currFiles : ""} type of '${file.type}' is not accepted.` };
|
|
44
|
+
}
|
|
45
|
+
if (opts.maxSize && file.size > opts.maxSize) return setMaxError(file.size, opts.maxSize, currFiles);
|
|
46
|
+
if (opts.minSize && file.size < opts.minSize) return setMinError(file.size, opts.minSize, currFiles);
|
|
47
|
+
if (opts.multiple) {
|
|
48
|
+
if (opts.maxTotalSize && totalSize > opts.maxTotalSize) return setMaxError(totalSize, opts.maxTotalSize);
|
|
49
|
+
if (opts.minTotalSize && totalSize < opts.minTotalSize) return setMinError(totalSize, opts.minTotalSize);
|
|
50
|
+
if (opts.maxLength && totalFiles > opts.maxLength) return { violation: "tooLong", message: `Selected ${totalFiles} files exceeds the maximum of ${opts.maxLength} allowed file${opts.maxLength == 1 ? "" : "s"}` };
|
|
51
|
+
if (opts.minLength && totalFiles < opts.minLength) return { violation: "tooShort", message: `Selected ${totalFiles} files is less than the minimum of ${opts.minLength} allowed file${opts.minLength == 1 ? "" : "s"}` };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return { violation: null, message: "" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/ts/utils/consts.ts
|
|
58
|
+
var dateTypes = ["date", "time", "datetime-local", "month"];
|
|
59
|
+
var nativeIconTypes = [...dateTypes];
|
|
60
|
+
var isNativeIconType = (t) => nativeIconTypes.includes(t);
|
|
61
|
+
|
|
62
|
+
// src/ts/react/components/Input.tsx
|
|
63
|
+
import { useScrollAssist } from "@t007/utils/hooks/react";
|
|
64
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
65
|
+
var Input = React.forwardRef(function Input2(props, ref) {
|
|
66
|
+
const { isWrapper = false, label = "", type = "text", helperText, error, custom = "", className, fieldClassName, children, endIcon, ...otherProps } = props;
|
|
67
|
+
let options;
|
|
68
|
+
let indeterminate = false;
|
|
69
|
+
let minLength;
|
|
70
|
+
let maxLength;
|
|
71
|
+
let maxSize;
|
|
72
|
+
let minSize;
|
|
73
|
+
let maxTotalSize;
|
|
74
|
+
let minTotalSize;
|
|
75
|
+
let nativeIcon;
|
|
76
|
+
let passwordMeter = false;
|
|
77
|
+
let eyeToggler = false;
|
|
78
|
+
let passwordHiddenIcon;
|
|
79
|
+
let passwordVisibleIcon;
|
|
80
|
+
switch (type) {
|
|
81
|
+
case "select":
|
|
82
|
+
options = props.options ?? [];
|
|
83
|
+
break;
|
|
84
|
+
case "checkbox":
|
|
85
|
+
indeterminate = props.indeterminate ?? false;
|
|
86
|
+
break;
|
|
87
|
+
case "file": {
|
|
88
|
+
const { maxSize: maxS, minSize: minS, maxTotalSize: maxT, minTotalSize: minT } = props;
|
|
89
|
+
maxSize = maxS;
|
|
90
|
+
minSize = minS;
|
|
91
|
+
maxTotalSize = maxT;
|
|
92
|
+
minTotalSize = minT;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case "password": {
|
|
96
|
+
const { passwordMeter: pm, eyeToggler: et, passwordHiddenIcon: phi, passwordVisibleIcon: pvi } = props;
|
|
97
|
+
passwordMeter = pm ?? true;
|
|
98
|
+
eyeToggler = et ?? true;
|
|
99
|
+
passwordHiddenIcon = phi;
|
|
100
|
+
passwordVisibleIcon = pvi;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
if (isNativeIconType(type)) nativeIcon = props.nativeIcon;
|
|
105
|
+
else if (type !== "textarea" && type !== "select") {
|
|
106
|
+
minLength = props.minLength;
|
|
107
|
+
maxLength = props.maxLength;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const helperTextMap = useMemo(() => typeof helperText == "boolean" ? {} : helperText, [helperText]);
|
|
111
|
+
const inputRef = useRef(null);
|
|
112
|
+
const helperTextWrapperRef = useRef(null);
|
|
113
|
+
const [filled, setFilled] = useState(!!(otherProps.value || otherProps.defaultValue));
|
|
114
|
+
const [visible, setVisible] = useState(false);
|
|
115
|
+
const [violation, setViolation] = useState(null);
|
|
116
|
+
const [violationMessage, setViolationMessage] = useState("");
|
|
117
|
+
const [strengthLevel, setStrengthLevel] = useState(1);
|
|
118
|
+
const [flagError, setFlagError] = useState(false);
|
|
119
|
+
const [renotify, setRenotify] = useState(false);
|
|
120
|
+
const isFile = type === "file";
|
|
121
|
+
const isRadioOrCheckbox = type === "radio" || type === "checkbox";
|
|
122
|
+
const hasValue = useCallback((el) => !!(isFile ? el.files?.length : isRadioOrCheckbox ? el.checked : el.value.trim() !== ""), [isFile, isRadioOrCheckbox]);
|
|
123
|
+
const togglePassword = () => setVisible((prev) => !prev);
|
|
124
|
+
const updatePasswordStrength = useCallback((value) => type === "password" && passwordMeter && setStrengthLevel(getStrengthLevel(value, minLength)), [minLength, passwordMeter, type]);
|
|
125
|
+
const validateInput = useCallback(
|
|
126
|
+
(input, flag) => {
|
|
127
|
+
let currentViolation = null;
|
|
128
|
+
if (input.type === "file") {
|
|
129
|
+
const fileInput = input;
|
|
130
|
+
const { violation: violation2, message } = getFilesHelper(Array.from(fileInput.files ?? []), { accept: fileInput.accept, multiple: fileInput.multiple, maxSize, minSize, maxTotalSize, minTotalSize, maxLength, minLength });
|
|
131
|
+
fileInput.setCustomValidity(message);
|
|
132
|
+
currentViolation = violation2;
|
|
133
|
+
}
|
|
134
|
+
currentViolation = violationKeys.find((violation2) => violation2 === currentViolation || input.validity?.[violation2]) || null;
|
|
135
|
+
setViolation(currentViolation);
|
|
136
|
+
setViolationMessage(currentViolation ? helperTextMap?.[currentViolation] || input.validationMessage : "");
|
|
137
|
+
const errorBool = !!currentViolation;
|
|
138
|
+
const formFlag = JSON.parse(inputRef.current?.closest(`[${parentBeacon}]`)?.getAttribute(parentBeacon) ?? "false");
|
|
139
|
+
const shouldFlagError = flagError ? errorBool : (formFlag || flag) && errorBool;
|
|
140
|
+
const shouldRenotify = (formFlag || flag) && shouldFlagError;
|
|
141
|
+
setFlagError(shouldFlagError), setRenotify(shouldRenotify);
|
|
142
|
+
shouldRenotify && setTimeout(() => setRenotify(false), 520);
|
|
143
|
+
},
|
|
144
|
+
[minSize, maxSize, maxTotalSize, minTotalSize, minLength, maxLength, flagError, helperTextMap]
|
|
145
|
+
);
|
|
146
|
+
const handleInput = useCallback(() => {
|
|
147
|
+
const el = inputRef.current;
|
|
148
|
+
if (!el) return;
|
|
149
|
+
const form = el.closest("form");
|
|
150
|
+
setFilled(hasValue(el));
|
|
151
|
+
updatePasswordStrength(el.value);
|
|
152
|
+
if (custom === "confirm_password") {
|
|
153
|
+
const passwordInput = form?.querySelector("[custom='password']");
|
|
154
|
+
if (passwordInput) el.setCustomValidity(el.value.trim() === passwordInput.value.trim() ? "" : "Both passwords do not match");
|
|
155
|
+
} else if (custom === "password") {
|
|
156
|
+
const confirmInput = form?.querySelector("[custom='confirm_password']");
|
|
157
|
+
if (confirmInput) confirmInput.setCustomValidity(el.value.trim() === confirmInput.value.trim() ? "" : "Both passwords do not match");
|
|
158
|
+
}
|
|
159
|
+
if (el.type === "radio") el?.closest("form")?.querySelectorAll(".t007-input[name='radio']").forEach(fireInput);
|
|
160
|
+
validateInput(el);
|
|
161
|
+
}, [custom, hasValue, updatePasswordStrength, validateInput]);
|
|
162
|
+
useEffect(() => void fireInput(inputRef.current), []);
|
|
163
|
+
useEffect(() => inputRef.current?.setAttribute("custom", custom), [custom]);
|
|
164
|
+
useScrollAssist(helperTextWrapperRef, { pxPerSecond: 80, assistClassName: "t007-input-scroll-assist" });
|
|
165
|
+
useImperativeHandle(ref, () => inputRef.current);
|
|
166
|
+
const Wrapper = isWrapper ? "div" : "label";
|
|
167
|
+
return /* @__PURE__ */ jsxs("div", { className: `t007-input-field ${fieldClassName}${isWrapper ? " t007-input-is-wrapper" : ""}${indeterminate ? " t007-input-indeterminate" : ""}${nativeIcon ? " t007-input-icon-override" : ""}${helperText === false ? " t007-input-no-helper" : ""}`, children: [
|
|
168
|
+
/* @__PURE__ */ jsxs(Wrapper, { className: type === "checkbox" || type === "radio" ? `t007-input-${type}-wrapper` : "t007-input-wrapper", children: [
|
|
169
|
+
type === "checkbox" || type === "radio" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
170
|
+
/* @__PURE__ */ jsx("span", { className: `t007-input-${type}-box`, children: /* @__PURE__ */ jsx("span", { className: `t007-input-${type}-tag` }) }),
|
|
171
|
+
/* @__PURE__ */ jsx("span", { className: `t007-input-${type}-label`, children: label })
|
|
172
|
+
] }) : /* @__PURE__ */ jsxs("span", { className: "t007-input-outline", children: [
|
|
173
|
+
/* @__PURE__ */ jsx("span", { className: "t007-input-outline-leading" }),
|
|
174
|
+
/* @__PURE__ */ jsx("span", { className: "t007-input-outline-notch", children: /* @__PURE__ */ jsx("span", { className: `t007-input-floating-label${renotify ? " t007-input-shake" : ""}`, onTransitionEnd: () => setRenotify(false), children: label }) }),
|
|
175
|
+
/* @__PURE__ */ jsx("span", { className: "t007-input-outline-trailing" })
|
|
176
|
+
] }),
|
|
177
|
+
isWrapper ? children : type === "select" ? /* @__PURE__ */ jsxs("select", { ...rExclude(otherProps, unsafeProps), ref: inputRef, className: `t007-input ${className}`, "data-filled": filled || void 0, "data-error": flagError || !!error || void 0, onBlur: (e) => (validateInput(e.target, true), otherProps.onBlur?.(e)), onInput: (e) => (handleInput(), otherProps.onInput?.(e)), children: [
|
|
178
|
+
options?.map((option, i) => /* @__PURE__ */ jsx("option", { value: typeof option === "string" ? option : option.value, children: typeof option === "string" ? option : option.option }, i)),
|
|
179
|
+
React.Children.map(children, (child) => React.isValidElement(child) && (child.type === "option" || child.type === "optgroup") ? child : null)
|
|
180
|
+
] }) : type === "textarea" ? /* @__PURE__ */ jsx("textarea", { ...rExclude(otherProps, unsafeProps), ref: inputRef, className: `t007-input ${className}`, "data-filled": filled || void 0, "data-error": flagError || !!error || void 0, placeholder: otherProps.placeholder || "", onBlur: (e) => (validateInput(e.target, true), otherProps.onBlur?.(e)), onInput: (e) => (handleInput(), otherProps.onInput?.(e)), children: typeof children === "string" || typeof children === "number" ? children : void 0 }) : /* @__PURE__ */ jsx("input", { ...rExclude(otherProps, unsafeProps), ref: inputRef, className: `t007-input ${className}`, "data-filled": filled || void 0, "data-error": flagError || !!error || void 0, placeholder: otherProps.placeholder || "", type: type === "password" && visible ? "text" : type, min: custom === "onward_date" ? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] : otherProps.min, max: custom === "past_date" ? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] : otherProps.max, onBlur: (e) => (validateInput(e.target, true), otherProps.onBlur?.(e)), onInput: (e) => (handleInput(), otherProps.onInput?.(e)) }),
|
|
181
|
+
isNativeIconType(type) && nativeIcon ? /* @__PURE__ */ jsx("i", { className: "t007-input-icon", children: nativeIcon }) : endIcon ? /* @__PURE__ */ jsx("i", { className: "t007-input-icon", children: endIcon }) : null,
|
|
182
|
+
eyeToggler && type === "password" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
183
|
+
/* @__PURE__ */ jsx("i", { className: "t007-input-icon t007-input-password-visible-icon", onClick: togglePassword, "aria-label": "Show password", role: "button", children: passwordVisibleIcon || /* @__PURE__ */ jsx("svg", { width: "24", height: "24", children: /* @__PURE__ */ jsx("path", { fill: "rgba(0,0,0,.54)", d: "M12 16q1.875 0 3.188-1.312Q16.5 13.375 16.5 11.5q0-1.875-1.312-3.188Q13.875 7 12 7q-1.875 0-3.188 1.312Q7.5 9.625 7.5 11.5q0 1.875 1.312 3.188Q10.125 16 12 16Zm0-1.8q-1.125 0-1.912-.788Q9.3 12.625 9.3 11.5t.788-1.913Q10.875 8.8 12 8.8t1.913.787q.787.788.787 1.913t-.787 1.912q-.788.788-1.913.788Zm0 4.8q-3.65 0-6.65-2.038-3-2.037-4.35-5.462 1.35-3.425 4.35-5.463Q8.35 4 12 4q3.65 0 6.65 2.037 3 2.038 4.35 5.463-1.35 3.425-4.35 5.462Q15.65 19 12 19Z" }) }) }),
|
|
184
|
+
/* @__PURE__ */ jsx("i", { className: "t007-input-icon t007-input-password-hidden-icon", onClick: togglePassword, "aria-label": "Hide password", role: "button", children: passwordHiddenIcon || /* @__PURE__ */ jsx("svg", { width: "24", height: "24", children: /* @__PURE__ */ jsx("path", { fill: "rgba(0,0,0,.54)", d: "m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" }) }) })
|
|
185
|
+
] })
|
|
186
|
+
] }),
|
|
187
|
+
helperText !== false && /* @__PURE__ */ jsx("div", { className: "t007-input-helper-line", children: /* @__PURE__ */ jsx("div", { ref: helperTextWrapperRef, className: "t007-input-helper-text-wrapper", children: violation && flagError ? /* @__PURE__ */ jsx("p", { className: "t007-input-helper-text t007-input-show", "data-violation": violation || "auto", children: violationMessage }) : error ? /* @__PURE__ */ jsx("p", { className: "t007-input-helper-text t007-input-show", "data-violation": "error", children: error }) : helperTextMap?.info ? /* @__PURE__ */ jsx("p", { className: "t007-input-helper-text", "data-violation": "none", children: helperTextMap.info }) : null }) }),
|
|
188
|
+
type === "password" && passwordMeter && /* @__PURE__ */ jsx("div", { className: "t007-input-password-meter", "data-strength-level": strengthLevel, children: /* @__PURE__ */ jsxs("div", { className: "t007-input-password-strength-meter", children: [
|
|
189
|
+
/* @__PURE__ */ jsx("div", { className: "t007-input-p-weak" }),
|
|
190
|
+
/* @__PURE__ */ jsx("div", { className: "t007-input-p-fair" }),
|
|
191
|
+
/* @__PURE__ */ jsx("div", { className: "t007-input-p-strong" }),
|
|
192
|
+
/* @__PURE__ */ jsx("div", { className: "t007-input-p-very-strong" })
|
|
193
|
+
] }) })
|
|
194
|
+
] });
|
|
195
|
+
});
|
|
196
|
+
Input.displayName = "Input";
|
|
197
|
+
|
|
198
|
+
// src/ts/react/components/WordsInput.tsx
|
|
199
|
+
import React2, { useState as useState2 } from "react";
|
|
200
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
201
|
+
var WordsInput = React2.forwardRef(({ maxCount = 1e4, showCount = "You have used %count% of the maximum %max% words. %left% words remaining.", allowOverflow = false, emitEventOnChange = false, onChange, ...props }, ref) => {
|
|
202
|
+
const [value, setValue] = useState2(`${props.defaultValue ?? props.value ?? ""}`);
|
|
203
|
+
const count = value.trim().split(/\s+/).filter(Boolean).length;
|
|
204
|
+
const overLimit = count > maxCount;
|
|
205
|
+
const info = showCount ? formatWordsHelperText(showCount, count, maxCount) : "";
|
|
206
|
+
const error = allowOverflow && overLimit ? formatWordsHelperText(props.errorHelperText ?? "Please shorten this text to %max% or less words. (You are currently using %count% words).", count, maxCount) : "";
|
|
207
|
+
const handleChange = (e) => {
|
|
208
|
+
let v = e.target.value;
|
|
209
|
+
if (!allowOverflow && overLimit) {
|
|
210
|
+
const words = v.match(/\S+/g) || [];
|
|
211
|
+
v = words.slice(0, maxCount).join(" ") + (v.endsWith(" ") ? " " : "");
|
|
212
|
+
e.target.value = v;
|
|
213
|
+
}
|
|
214
|
+
if (allowOverflow) e.target.setCustomValidity(error);
|
|
215
|
+
setValue(v), onChange?.(emitEventOnChange ? e : v);
|
|
216
|
+
};
|
|
217
|
+
return /* @__PURE__ */ jsx2(Input, { ...props, ref, value, defaultValue: void 0, onChange: handleChange, helperText: { info } });
|
|
218
|
+
});
|
|
219
|
+
WordsInput.displayName = "WordsInput";
|
|
220
|
+
|
|
221
|
+
// src/ts/react/useFormManager.ts
|
|
222
|
+
import { useCallback as useCallback2 } from "react";
|
|
223
|
+
function useFormManager(onSubmit, formRef) {
|
|
224
|
+
const validate = useCallback2(
|
|
225
|
+
(formEl) => {
|
|
226
|
+
const form = formRef?.current || formEl;
|
|
227
|
+
if (!form) return false;
|
|
228
|
+
let hasErrors = false;
|
|
229
|
+
form.querySelectorAll(".t007-input").forEach((i) => hasErrors = hasErrors || !i.checkValidity());
|
|
230
|
+
form.setAttribute(parentBeacon, `${hasErrors}`);
|
|
231
|
+
form.querySelectorAll(".t007-input").forEach(fireInput);
|
|
232
|
+
form.querySelector(`input:is(:invalid,[data-error])`)?.focus();
|
|
233
|
+
form.addEventListener("focusin", () => form.setAttribute(parentBeacon, "false"));
|
|
234
|
+
return !hasErrors;
|
|
235
|
+
},
|
|
236
|
+
[formRef]
|
|
237
|
+
);
|
|
238
|
+
const handleSubmit = useCallback2(
|
|
239
|
+
(e) => {
|
|
240
|
+
const form = formRef?.current || e.currentTarget;
|
|
241
|
+
if (!form) return;
|
|
242
|
+
e?.preventDefault();
|
|
243
|
+
if (!validate(form)) return;
|
|
244
|
+
if (onSubmit) onSubmit(e);
|
|
245
|
+
else if (form instanceof HTMLFormElement) form.submit();
|
|
246
|
+
},
|
|
247
|
+
[formRef, validate, onSubmit]
|
|
248
|
+
);
|
|
249
|
+
return { handleSubmit, validate, fireInput };
|
|
250
|
+
}
|
|
251
|
+
export {
|
|
252
|
+
Input,
|
|
253
|
+
WordsInput,
|
|
254
|
+
useFormManager
|
|
255
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t007/input",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "A lightweight, pure JS input system.",
|
|
5
5
|
"author": "Oketade Oluwatobiloba <tobioketade007@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"import": "./dist/index.js",
|
|
30
30
|
"default": "./dist/index.js"
|
|
31
31
|
},
|
|
32
|
+
"./react": {
|
|
33
|
+
"types": "./dist/react.d.ts",
|
|
34
|
+
"import": "./dist/react.js",
|
|
35
|
+
"default": "./dist/react.js"
|
|
36
|
+
},
|
|
32
37
|
"./style.css": "./dist/index.css"
|
|
33
38
|
},
|
|
34
39
|
"publishConfig": {
|
|
@@ -46,6 +51,7 @@
|
|
|
46
51
|
"ecosystem",
|
|
47
52
|
"ui",
|
|
48
53
|
"vanilla-js",
|
|
54
|
+
"react",
|
|
49
55
|
"input",
|
|
50
56
|
"form",
|
|
51
57
|
"form-validation",
|
|
@@ -54,7 +60,21 @@
|
|
|
54
60
|
"password-meter",
|
|
55
61
|
"monkey-patch"
|
|
56
62
|
],
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/react": "^18.0.0",
|
|
65
|
+
"@types/react-dom": "^18.0.0",
|
|
66
|
+
"react": "^18.3.1",
|
|
67
|
+
"react-dom": "^18.3.1"
|
|
68
|
+
},
|
|
57
69
|
"dependencies": {
|
|
58
|
-
"@t007/utils": "^0.0.
|
|
70
|
+
"@t007/utils": "^0.0.27"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"react": "^18.0.0"
|
|
74
|
+
},
|
|
75
|
+
"peerDependenciesMeta": {
|
|
76
|
+
"react": {
|
|
77
|
+
"optional": true
|
|
78
|
+
}
|
|
59
79
|
}
|
|
60
80
|
}
|