@t007/input 0.0.22 → 0.0.24

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/dist/index.js CHANGED
@@ -1,58 +1,74 @@
1
- // src/index.js
2
- import { isArr, isStr, createEl, loadResource, initScrollAssist } from "@t007/utils";
1
+ // src/js/index.js
2
+ import { isArr, isStr, createEl, loadResource, formatSize as formatFileSize } from "@t007/utils";
3
+ import { initScrollAssist } from "@t007/utils/hooks/vanilla";
4
+
5
+ // src/ts/utils/consts.ts
6
+ var violationKeys = ["valueMissing", "typeMismatch", "patternMismatch", "stepMismatch", "tooShort", "tooLong", "rangeUnderflow", "rangeOverflow", "badInput", "customError"];
7
+ var dateTypes = ["date", "time", "datetime-local", "month"];
8
+ var nativeIconTypes = [...dateTypes];
9
+
10
+ // src/ts/utils/fn.ts
11
+ import { formatSize } from "@t007/utils";
12
+ function getStrengthLevel(value, minLength = 0) {
13
+ value = value.trim();
14
+ let level = 0;
15
+ if (value.length < minLength) level = 1;
16
+ else {
17
+ if (/[a-z]/.test(value)) level++;
18
+ if (/[A-Z]/.test(value)) level++;
19
+ if (/[0-9]/.test(value)) level++;
20
+ if (/[\W_]/.test(value)) level++;
21
+ }
22
+ return Math.min(level, 4);
23
+ }
24
+ function getFilesHelper(files, opts) {
25
+ if (!files || !files.length) return { violation: null, message: "" };
26
+ const totalFiles = files.length;
27
+ let totalSize = 0, currFiles = 0;
28
+ 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)}` });
29
+ 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)}` });
30
+ for (const file of files) {
31
+ currFiles++;
32
+ totalSize += file.size;
33
+ if (opts.accept) {
34
+ const acceptedTypes = opts.accept.split(",").map((type) => type.trim().replace(/^[*\.]+|[*\.]+$/g, "")).filter(Boolean) || [];
35
+ if (!acceptedTypes.some((type) => file.type.includes(type))) return { violation: "typeMismatch", message: `File${currFiles > 1 ? currFiles : ""} type of '${file.type}' is not accepted.` };
36
+ }
37
+ if (opts.maxSize && file.size > opts.maxSize) return setMaxError(file.size, opts.maxSize, currFiles);
38
+ if (opts.minSize && file.size < opts.minSize) return setMinError(file.size, opts.minSize, currFiles);
39
+ if (opts.multiple) {
40
+ if (opts.maxTotalSize && totalSize > opts.maxTotalSize) return setMaxError(totalSize, opts.maxTotalSize);
41
+ if (opts.minTotalSize && totalSize < opts.minTotalSize) return setMinError(totalSize, opts.minTotalSize);
42
+ 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"}` };
43
+ 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"}` };
44
+ }
45
+ }
46
+ return { violation: null, message: "" };
47
+ }
48
+
49
+ // src/js/index.js
3
50
  var formManager = {
4
51
  forms: document.getElementsByClassName("t007-input-form"),
5
- violationKeys: ["valueMissing", "typeMismatch", "patternMismatch", "stepMismatch", "tooShort", "tooLong", "rangeUnderflow", "rangeOverflow", "badInput", "customError"],
52
+ violationKeys,
6
53
  init() {
7
- t007.FM.observeDOMForFields();
8
- Array.from(t007.FM.forms).forEach(t007.FM.handleFormValidation);
54
+ t007.FM.observeDOMForFields(), Array.prototype.forEach.call(t007.FM.forms, t007.FM.handleFormValidation);
9
55
  },
10
56
  observeDOMForFields() {
11
57
  new MutationObserver((mutations) => {
12
- for (const mutation of mutations) {
58
+ for (const mutation of mutations)
13
59
  for (const node of mutation.addedNodes) {
14
- if (!node.tagName || !(node?.classList?.contains("t007-input-field") || node?.querySelector?.(".t007-input-field"))) continue;
60
+ if (!node.tagName || !(node.classList.contains("t007-input-field") || node.querySelector(".t007-input-field"))) continue;
15
61
  for (const field2 of [...node.querySelector(".t007-input-field") ? node.querySelectorAll(".t007-input-field") : [node]]) t007.FM.setUpField(field2);
16
62
  }
17
- }
18
63
  }).observe(document.body, { childList: true, subtree: true });
19
64
  },
20
- getFilesHelper(files, opts) {
21
- if (!files || !files.length) return { violation: null, message: "" };
22
- const totalFiles = files.length;
23
- let totalSize = 0;
24
- let currFiles = 0;
25
- const setMaxError = (size, max, n = 0) => ({ violation: "rangeOverflow", message: n ? `File ${files.length > 1 ? n : ""} size of ${t007.FM.formatSize(size)} exceeds the per file maximum of ${t007.FM.formatSize(max)}` : `Total files size of ${t007.FM.formatSize(size)} exceeds the total maximum of ${t007.FM.formatSize(max)}` });
26
- const setMinError = (size, min, n = 0) => ({ violation: "rangeUnderflow", message: n ? `File ${files.length > 1 ? n : ""} size of ${t007.FM.formatSize(size)} is less than the per file minimum of ${t007.FM.formatSize(min)}` : `Total files size of ${t007.FM.formatSize(size)} is less than the total minimum of ${t007.FM.formatSize(min)}` });
27
- for (const file of files) {
28
- currFiles++;
29
- totalSize += file.size;
30
- if (opts.accept) {
31
- const acceptedTypes = opts.accept.split(",").map((type) => type.trim().replace(/^[*\.]+|[*\.]+$/g, "")).filter(Boolean) || [];
32
- if (!acceptedTypes.some((type) => file.type.includes(type))) return { violation: "typeMismatch", message: `File${currFiles > 1 ? currFiles : ""} type of '${file.type}' is not accepted.` };
33
- }
34
- if (opts.maxSize && file.size > opts.maxSize) return setMaxError(file.size, opts.maxSize, currFiles);
35
- if (opts.minSize && file.size < opts.minSize) return setMinError(file.size, opts.minSize, currFiles);
36
- if (opts.multiple) {
37
- if (opts.maxTotalSize && totalSize > opts.maxTotalSize) return setMaxError(totalSize, opts.maxTotalSize);
38
- if (opts.minTotalSize && totalSize < opts.minTotalSize) return setMinError(totalSize, opts.minTotalSize);
39
- 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"}` };
40
- 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"}` };
41
- }
42
- }
43
- return { violation: null, message: "" };
44
- },
45
- formatSize(size, decimals = 3, base = 1e3) {
46
- if (size < base) return `${size} byte${size == 1 ? "" : "s"}`;
47
- const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], exponent = Math.min(Math.floor(Math.log(size) / Math.log(base)), units.length - 1);
48
- return `${(size / Math.pow(base, exponent)).toFixed(decimals).replace(/\.0+$/, "")} ${units[exponent]}`;
49
- },
65
+ getFilesHelper: (files, opts) => getFilesHelper(files, opts),
66
+ formatSize: (size, decimals = 3, base = 1e3) => formatFileSize(size, decimals, base),
50
67
  togglePasswordType: (input) => input.type = input.type === "password" ? "text" : "password",
51
68
  toggleFilled: (input) => input?.toggleAttribute("data-filled", input.type === "checkbox" || input.type === "radio" ? input.checked : input.value !== "" || input.files?.length > 0),
52
69
  setFallbackHelper(field2) {
53
70
  const helperTextWrapper = field2?.querySelector(".t007-input-helper-text-wrapper");
54
- if (!helperTextWrapper || helperTextWrapper.querySelector(".t007-input-helper-text[data-violation='auto']")) return;
55
- helperTextWrapper.append(createEl("p", { className: "t007-input-helper-text" }, { violation: "auto" }));
71
+ !helperTextWrapper?.querySelector(".t007-input-helper-text[data-violation='auto']") && helperTextWrapper?.append(createEl("p", { className: "t007-input-helper-text" }, { violation: "auto" }));
56
72
  },
57
73
  setFieldListeners(field2) {
58
74
  if (!field2) return;
@@ -60,47 +76,31 @@ var formManager = {
60
76
  if (input.type === "file")
61
77
  input.addEventListener("input", async () => {
62
78
  const file = input.files?.[0], img = new Image();
63
- img.onload = () => {
64
- input.style.setProperty("--t007-input-image-src", `url(${src})`);
65
- input.classList.add("t007-input-image-selected");
66
- setTimeout(() => URL.revokeObjectURL(src), 1e3);
67
- };
68
- img.onerror = () => {
69
- input.style.removeProperty("--t007-input-image-src");
70
- input.classList.remove("t007-input-image-selected");
71
- URL.revokeObjectURL(src);
72
- };
79
+ img.onload = () => (input.style.setProperty("--t007-input-image-src", `url(${src})`), input.classList.add("t007-input-image-selected"), setTimeout(() => URL.revokeObjectURL(src), 1e3));
80
+ img.onerror = () => (input.style.removeProperty("--t007-input-image-src"), input.classList.remove("t007-input-image-selected"), URL.revokeObjectURL(src));
73
81
  let src;
74
82
  if (file?.type?.startsWith("image")) src = URL.createObjectURL(file);
75
- else if (file?.type?.startsWith("video")) {
83
+ else if (file?.type?.startsWith("video"))
76
84
  src = await new Promise((resolve) => {
77
85
  let video = createEl("video"), canvas = createEl("canvas"), context = canvas.getContext("2d");
78
86
  video.ontimeupdate = () => {
79
87
  context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
80
- canvas.toBlob((blob) => resolve(URL.createObjectURL(blob)));
81
- URL.revokeObjectURL(video.src);
88
+ canvas.toBlob((blob) => resolve(URL.createObjectURL(blob))), URL.revokeObjectURL(video.src);
82
89
  video = video.src = video.onloadedmetadata = video.ontimeupdate = null;
83
90
  };
84
91
  video.onloadeddata = () => video.currentTime = 3;
85
92
  video.src = URL.createObjectURL(file);
86
93
  });
87
- }
88
- if (!src) {
89
- input.style.removeProperty("--t007-input-image-src");
90
- input.classList.remove("t007-input-image-selected");
91
- return;
92
- }
94
+ if (!src) return input.style.removeProperty("--t007-input-image-src"), input.classList.remove("t007-input-image-selected");
93
95
  img.src = src;
94
96
  });
95
97
  if (floatingLabel) floatingLabel.ontransitionend = () => floatingLabel.classList.remove("t007-input-shake");
96
98
  if (eyeOpen && eyeClosed) eyeOpen.onclick = eyeClosed.onclick = () => t007.FM.togglePasswordType(input);
97
- initScrollAssist(field2.querySelector(".t007-input-helper-text-wrapper"), { vertical: false });
99
+ initScrollAssist(field2.querySelector(".t007-input-helper-text-wrapper"), { vertical: false, assistClassName: "t007-input-scroll-assist" });
98
100
  },
99
101
  setUpField(field2) {
100
102
  if (field2.dataset.setUp) return;
101
- t007.FM.toggleFilled(field2.querySelector(".t007-input"));
102
- t007.FM.setFallbackHelper(field2);
103
- t007.FM.setFieldListeners(field2);
103
+ t007.FM.toggleFilled(field2.querySelector(".t007-input")), t007.FM.setFallbackHelper(field2), t007.FM.setFieldListeners(field2);
104
104
  field2.dataset.setUp = "true";
105
105
  },
106
106
  field({ isWrapper = false, label = "", type = "text", placeholder = "", custom = "", minSize, maxSize, minTotalSize, maxTotalSize, options = [], indeterminate = false, eyeToggler = true, passwordMeter = true, helperText = {}, className = "", fieldClassName = "", children, startIcon = "", endIcon = "", nativeIcon = "", passwordVisibleIcon = "", passwordHiddenIcon = "", ...otherProps }) {
@@ -134,19 +134,18 @@ var formManager = {
134
134
  if (maxTotalSize) inputEl.setAttribute("maxtotalsize", maxTotalSize);
135
135
  Object.keys(otherProps).forEach((key) => inputEl[key] = otherProps[key]);
136
136
  labelEl.append(!isWrapper ? inputEl : children);
137
- const nativeTypes = ["date", "time", "month", "datetime-local"];
138
- if (nativeTypes.includes(type) && nativeIcon) labelEl.append(createEl("i", { className: "t007-input-icon t007-input-native-icon", innerHTML: nativeIcon }));
137
+ if (nativeIconTypes.includes(type) && nativeIcon) labelEl.append(createEl("i", { className: "t007-input-icon t007-input-native-icon", innerHTML: nativeIcon }));
139
138
  else if (endIcon) labelEl.append(createEl("i", { className: "t007-input-icon", innerHTML: endIcon }));
140
- if (type === "password" && eyeToggler) {
141
- labelEl.append(createEl("i", { role: "button", ariaLabel: "Show password", className: "t007-input-icon t007-input-password-visible-icon", innerHTML: passwordVisibleIcon || `<svg width="24" height="24"><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"/></svg>` }));
142
- labelEl.append(createEl("i", { role: "button", ariaLabel: "Hide password", className: "t007-input-icon t007-input-password-hidden-icon", innerHTML: passwordHiddenIcon || `<svg width="24" height="24"><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"/></svg>` }));
143
- }
139
+ if (type === "password" && eyeToggler)
140
+ labelEl.append(
141
+ createEl("i", { role: "button", ariaLabel: "Show password", className: "t007-input-icon t007-input-password-visible-icon", innerHTML: passwordVisibleIcon || `<svg width="24" height="24"><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"/></svg>` }),
142
+ createEl("i", { role: "button", ariaLabel: "Hide password", className: "t007-input-icon t007-input-password-hidden-icon", innerHTML: passwordHiddenIcon || `<svg width="24" height="24"><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"/></svg>` })
143
+ );
144
144
  if (helperText !== false) {
145
145
  const helperLine = createEl("div", { className: "t007-input-helper-line" }), helperWrapper = createEl("div", { className: "t007-input-helper-text-wrapper", tabIndex: "-1" });
146
146
  if (helperText.info) helperWrapper.append(createEl("p", { className: "t007-input-helper-text", textContent: helperText.info }, { violation: "none" }));
147
- t007.FM?.violationKeys?.forEach((key) => helperText[key] && helperWrapper.append(createEl("p", { className: "t007-input-helper-text", textContent: helperText[key] }, { violation: key })));
148
- helperLine.append(helperWrapper);
149
- field2.append(helperLine);
147
+ t007.FM.violationKeys?.forEach((key) => helperText[key] && helperWrapper.append(createEl("p", { className: "t007-input-helper-text", textContent: helperText[key] }, { violation: key })));
148
+ helperLine.append(helperWrapper), field2.append(helperLine);
150
149
  }
151
150
  if (passwordMeter && type === "password") {
152
151
  const meter = createEl("div", { className: "t007-input-password-meter" }, { strengthLevel: "1" });
@@ -168,36 +167,26 @@ var formManager = {
168
167
  form.validateOnClient = validateFormOnClient;
169
168
  form.toggleGlobalError = toggleFormGlobalError;
170
169
  const fields = form.getElementsByClassName("t007-input-field"), inputs = form.getElementsByClassName("t007-input");
171
- Array.from(fields).forEach(t007.FM.setUpField);
172
- form.addEventListener("input", ({ target }) => {
173
- t007.FM.toggleFilled(target);
174
- validateInput(target);
175
- });
170
+ Array.prototype.forEach.call(fields, t007.FM.setUpField);
171
+ form.addEventListener("input", ({ target }) => (t007.FM.toggleFilled(target), validateInput(target)));
176
172
  form.addEventListener("focusout", ({ target }) => validateInput(target, true));
177
173
  form.addEventListener("submit", async (e) => {
178
- toggleSubmitLoader(true);
174
+ form.classList.toggle("t007-input-submit-loading", true);
179
175
  try {
180
176
  e.preventDefault();
181
177
  if (!validateFormOnClient()) return;
182
- if (form.validateOnServer && !await form.validateOnServer()) {
183
- toggleFormGlobalError(true);
184
- form.addEventListener("input", () => toggleFormGlobalError(false), { once: true, useCapture: true });
185
- return;
186
- }
187
- form.onSubmit ? form.onSubmit() : form.submit();
178
+ if (form.validateOnServer && !await form.validateOnServer()) return toggleFormGlobalError(true), form.addEventListener("input", () => toggleFormGlobalError(false), { once: true, useCapture: true });
179
+ form.onSubmit ? form.onSubmit(e) : form.submit();
188
180
  } catch (error) {
189
181
  console.error(error);
190
182
  }
191
- toggleSubmitLoader(false);
183
+ form.classList.toggle("t007-input-submit-loading", false);
192
184
  });
193
- function toggleSubmitLoader(bool) {
194
- form.classList.toggle("t007-input-submit-loading", bool);
195
- }
196
185
  function toggleError(input, bool, flag = false) {
197
186
  const field2 = input.closest(".t007-input-field"), floatingLabel = field2.querySelector(".t007-input-floating-label");
198
187
  if (bool && flag) {
199
188
  input.setAttribute("data-error", "");
200
- floatingLabel?.classList.add("t007-input-shake");
189
+ floatingLabel?.classList.add("t007-input-shake"), setTimeout(() => floatingLabel?.classList.remove("t007-input-shake"), 520);
201
190
  } else if (!bool) input.removeAttribute("data-error");
202
191
  toggleHelper(input, input.hasAttribute("data-error"));
203
192
  }
@@ -205,28 +194,11 @@ var formManager = {
205
194
  const field2 = input.closest(".t007-input-field"), violation = t007.FM.violationKeys.find((violation2) => input.Validity?.[violation2] || input.validity[violation2]) ?? "", helper = field2.querySelector(`.t007-input-helper-text[data-violation="${violation}"]`), fallbackHelper = field2.querySelector(`.t007-input-helper-text[data-violation="auto"]`);
206
195
  input.closest(".t007-input-field").querySelectorAll(`.t007-input-helper-text:not([data-violation="${violation}"])`).forEach((helper2) => helper2?.classList.remove("t007-input-show"));
207
196
  if (helper) helper.classList.toggle("t007-input-show", bool);
208
- else if (fallbackHelper) {
209
- fallbackHelper.textContent = input.validationMessage;
210
- fallbackHelper.classList.toggle("t007-input-show", bool);
211
- }
212
- }
213
- function forceRevalidate(input) {
214
- input.checkValidity();
215
- input.dispatchEvent(new Event("input"));
197
+ else if (fallbackHelper) fallbackHelper.textContent = input.validationMessage, fallbackHelper.classList.toggle("t007-input-show", bool);
216
198
  }
217
199
  function updatePasswordMeter(input) {
218
200
  const passwordMeter = input.closest(".t007-input-field").querySelector(".t007-input-password-meter");
219
- if (!passwordMeter) return;
220
- const value = input.value?.trim();
221
- let strengthLevel = 0;
222
- if (value.length < Number(input.minLength ?? 0)) strengthLevel = 1;
223
- else {
224
- if (/[a-z]/.test(value)) strengthLevel++;
225
- if (/[A-Z]/.test(value)) strengthLevel++;
226
- if (/[0-9]/.test(value)) strengthLevel++;
227
- if (/[\W_]/.test(value)) strengthLevel++;
228
- }
229
- passwordMeter.dataset.strengthLevel = strengthLevel;
201
+ if (passwordMeter) passwordMeter.dataset.strengthLevel = `${getStrengthLevel(input.value, Number(input.minLength ?? 0))}`;
230
202
  }
231
203
  function validateInput(input, flag = false) {
232
204
  if (form.dataset.globalError || !input?.classList.contains("t007-input")) return;
@@ -236,7 +208,7 @@ var formManager = {
236
208
  case "password":
237
209
  value = input.value?.trim();
238
210
  if (value === "") break;
239
- const confirmPasswordInput = Array.from(inputs).find((input2) => (input2.custom ?? input2.getAttribute("custom")) === "confirm-password");
211
+ const confirmPasswordInput = Array.prototype.find.call(inputs, (input2) => (input2.custom ?? input2.getAttribute("custom")) === "confirm-password");
240
212
  if (!confirmPasswordInput) break;
241
213
  const confirmPasswordValue = confirmPasswordInput.value?.trim();
242
214
  confirmPasswordInput.setCustomValidity(value !== confirmPasswordValue ? "Both passwords do not match" : "");
@@ -245,7 +217,7 @@ var formManager = {
245
217
  case "confirm_password":
246
218
  value = input.value?.trim();
247
219
  if (value === "") break;
248
- const passwordInput = Array.from(inputs).find((input2) => (input2.custom ?? input2.getAttribute("custom")) === "password");
220
+ const passwordInput = Array.prototype.find.call(inputs, (input2) => (input2.custom ?? input2.getAttribute("custom")) === "password");
249
221
  if (!passwordInput) break;
250
222
  const passwordValue = passwordInput.value?.trim();
251
223
  errorBool = value !== passwordValue;
@@ -254,21 +226,17 @@ var formManager = {
254
226
  case "onward_date":
255
227
  if (input.min) break;
256
228
  input.min = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
257
- forceRevalidate(input);
229
+ input.checkValidity(), input.dispatchEvent(new Event("input"));
230
+ break;
231
+ case "past_date":
232
+ if (input.max) break;
233
+ input.max = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
234
+ input.checkValidity(), input.dispatchEvent(new Event("input"));
258
235
  break;
259
236
  }
260
237
  if (input.type === "file") {
261
238
  input.Validity = {};
262
- const { violation, message } = t007.FM.getFilesHelper(input.files ?? [], {
263
- accept: input.accept,
264
- multiple: input.multiple,
265
- maxSize: input.maxSize ?? Number(input.getAttribute("maxsize")),
266
- minSize: input.minSize ?? Number(input.getAttribute("minsize")),
267
- maxTotalSize: input.maxTotalSize ?? Number(input.getAttribute("maxtotalsize")),
268
- minTotalSize: input.minTotalSize ?? Number(input.getAttribute("mintotalsize")),
269
- maxLength: input.maxLength ?? Number(input.getAttribute("maxlength")),
270
- minLength: input.minLength ?? Number(input.getAttribute("minLength"))
271
- });
239
+ const { violation, message } = t007.FM.getFilesHelper(input.files ?? [], { accept: input.accept, multiple: input.multiple, maxSize: input.maxSize ?? Number(input.getAttribute("maxsize")), minSize: input.minSize ?? Number(input.getAttribute("minsize")), maxTotalSize: input.maxTotalSize ?? Number(input.getAttribute("maxtotalsize")), minTotalSize: input.minTotalSize ?? Number(input.getAttribute("mintotalsize")), maxLength: input.maxLength ?? Number(input.getAttribute("maxlength")), minLength: input.minLength ?? Number(input.getAttribute("minLength")) });
272
240
  errorBool = !!message;
273
241
  input.setCustomValidity(message);
274
242
  if (violation) input.Validity[violation] = true;
@@ -277,18 +245,22 @@ var formManager = {
277
245
  toggleError(input, errorBool, flag);
278
246
  if (errorBool) return;
279
247
  if (input.type === "radio")
280
- Array.from(inputs)?.filter((i) => i.name == input.name)?.forEach((radio) => toggleError(radio, errorBool, flag));
248
+ Array.prototype.forEach.call(
249
+ Array.prototype.filter.call(inputs, (i) => i.name == input.name),
250
+ (radio) => toggleError(radio, errorBool, flag)
251
+ );
281
252
  }
282
253
  function validateFormOnClient() {
283
- Array.from(inputs).forEach((input) => validateInput(input, true));
254
+ Array.prototype.forEach.call(inputs, (input) => validateInput(input, true));
284
255
  form.querySelector("input:invalid")?.focus();
285
- return Array.from(inputs).every((input) => input.checkValidity());
256
+ return Array.prototype.every.call(inputs, (input) => input.checkValidity());
286
257
  }
287
258
  function toggleFormGlobalError(bool) {
288
259
  form.toggleAttribute("data-global-error", bool);
289
260
  form.querySelectorAll(".t007-input-field").forEach((field2) => {
290
261
  field2.querySelector(".t007-input")?.toggleAttribute("data-error", bool);
291
- if (bool) field2.querySelector(".t007-input-floating-label")?.classList.add("t007-input-shake");
262
+ const floatingLabel = field2.querySelector(".t007-input-floating-label");
263
+ floatingLabel?.classList.toggle("t007-input-shake", bool), bool && setTimeout(() => floatingLabel?.classList.remove("t007-input-shake"), 520);
292
264
  });
293
265
  }
294
266
  }
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+
3
+ /** Browser date-like input types supported by input field helpers. */
4
+ declare const dateTypes: readonly ["date", "time", "datetime-local", "month"];
5
+ /** Union of all supported native date-like input types. */
6
+ type DateType = (typeof dateTypes)[number];
7
+
8
+ /** React change event alias used by input helpers. */
9
+ type CE<T> = React.ChangeEvent<T>;
10
+ /** React input event alias used by input helpers. */
11
+ type IE<T> = React.InputEvent<T>;
12
+ /** React change handler alias used by input helpers. */
13
+ type CEH<T> = React.ChangeEventHandler<T>;
14
+ /** React input handler alias used by input helpers. */
15
+ type IEH<T> = React.InputEventHandler<T>;
16
+
17
+ /** Base input element attributes used by non-select/non-textarea fields. */
18
+ type InputAttributes = React.InputHTMLAttributes<HTMLInputElement>;
19
+ /** Select element attributes used by select fields. */
20
+ type SelectAttributes = React.SelectHTMLAttributes<HTMLSelectElement>;
21
+ /** Textarea element attributes used by textarea fields. */
22
+ type TextareaAttributes = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
23
+
24
+ /** Union type for all input elements */
25
+ type t007InputElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
26
+
27
+ /** Define all possible `ValidityState` keys mapped to string messages */
28
+ interface HelperTextMap extends Partial<Record<keyof ValidityState, string>> {
29
+ /** Informational helper text shown when there is no validation violation. */
30
+ info?: string;
31
+ }
32
+
33
+ interface BaseProps {
34
+ /** Wrap the field in its own container. */
35
+ isWrapper?: boolean;
36
+ /** Visible label text. */
37
+ label?: React.ReactNode;
38
+ /** Custom tokens for feature presets. */
39
+ custom?: "confirm_password" | "password" | "onward_date" | "past_date";
40
+ /** Class applied to the root field control. */
41
+ fieldClassName?: string;
42
+ /** Helper text shown under the field. */
43
+ helperText?: HelperTextMap | boolean;
44
+ /** External resolver error text. When provided, the field is forced into error visuals even without native validity violations. */
45
+ error?: string;
46
+ /** Start icon rendered inside the field control. */
47
+ // startIcon?: React.ReactNode;
48
+ /** End icon rendered inside the field control. */
49
+ endIcon?: React.ReactNode;
50
+ // onFlagError?: (args: { violation: keyof ValidityState | null; helper: string }) => void;
51
+ }
52
+ type PasswordInputProps = BaseProps & InputAttributes & PasswordInputAddon;
53
+ type FileInputProps = BaseProps & InputAttributes & FileInputAddon;
54
+ type CheckboxInputProps = BaseProps & InputAttributes & CheckboxInputAddon;
55
+ type DateInputProps = BaseProps & InputAttributes & DateInputAddon;
56
+ type GenericInputProps = BaseProps & InputAttributes & { type?: Exclude<React.HTMLInputTypeAttribute, "password" | "file" | "checkbox" | DateType> };
57
+ type TextareaElementProps = BaseProps & TextareaAttributes & { type: "textarea" };
58
+ type SelectElementProps = BaseProps & SelectAttributes & SelectElementAddon;
59
+
60
+ /** Union type for all input props, discriminated by the `type` property. */
61
+ type InputProps = PasswordInputProps | FileInputProps | CheckboxInputProps | DateInputProps | GenericInputProps | TextareaElementProps | SelectElementProps;
62
+
63
+ type WordsInputBaseProps = InputProps & {
64
+ /** Maximum count of words allowed. */
65
+ maxCount?: number;
66
+ /** Phrase template with %left%, %max%, %count% */
67
+ showCount?: string;
68
+ /** Whether to send the original change event instead of the plain string value. */
69
+ emitEventOnChange?: boolean;
70
+ };
71
+ type WordsInputStrictModeProps = WordsInputBaseProps & {
72
+ /** Whether to allow overflow of the word count. */
73
+ allowOverflow?: false;
74
+ };
75
+ type WordsInputLenientModeProps = WordsInputBaseProps & {
76
+ /** Whether to allow overflow of the word count. */
77
+ allowOverflow: true;
78
+ /** Helper text shown when the word count is exceeded. */
79
+ errorHelperText?: string;
80
+ };
81
+
82
+ /** Union type for all words input props. */
83
+ type WordsInputProps = WordsInputStrictModeProps | WordsInputLenientModeProps;
84
+
85
+ // Addon types for field options
86
+
87
+ interface PasswordInputAddon {
88
+ type: "password";
89
+ /** Show the password visibility toggler. */
90
+ eyeToggler?: boolean;
91
+ /** Enable the password strength meter. */
92
+ passwordMeter?: boolean;
93
+ passwordVisibleIcon?: React.ReactNode;
94
+ passwordHiddenIcon?: React.ReactNode;
95
+ }
96
+ interface FileInputAddon {
97
+ type: "file";
98
+ /** Maximum value length or count. */
99
+ maxSize?: number;
100
+ /** Minimum value length or count. */
101
+ minSize?: number;
102
+ /** Maximum total size allowed across the field value. */
103
+ maxTotalSize?: number;
104
+ /** Minimum total size allowed across the field value. */
105
+ minTotalSize?: number;
106
+ }
107
+ interface CheckboxInputAddon {
108
+ type: "checkbox";
109
+ /** Whether the checkbox is a multi-state checkbox. */
110
+ indeterminate?: boolean;
111
+ }
112
+ interface DateInputAddon {
113
+ type: DateType;
114
+ /** Native icon rendered by the browser control. */
115
+ nativeIcon?: React.ReactNode;
116
+ }
117
+ interface SelectElementAddon {
118
+ type: "select";
119
+ /** Options used by select-like fields. */
120
+ options?: string[] | readonly string[] | Array<{ value: string; option: string }>;
121
+ }
122
+
123
+ declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<t007InputElement>>;
124
+
125
+ declare const WordsInput: React.ForwardRefExoticComponent<WordsInputProps & React.RefAttributes<t007InputElement>>;
126
+
127
+ /**
128
+ * FormManager hook to validate form-level inputs, does not force a form setup though.
129
+ * @param onSubmit Optional callback invoked after successful validation on submit.
130
+ * @param formRef Optional form ref; when omitted, the form passed to handlers is used.
131
+ * @returns handleSubmit: submit handler to attach to form elements, validate: function to trigger validation manually, fireInput: utility to trigger input events on fields.
132
+ */
133
+ declare function useFormManager(onSubmit?: (e: React.FormEvent<HTMLFormElement>) => void, formRef?: React.RefObject<HTMLElement>): {
134
+ handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
135
+ validate: (formEl?: HTMLElement) => boolean;
136
+ fireInput: (el?: t007InputElement | null) => boolean | undefined;
137
+ };
138
+
139
+ export { type BaseProps, type CE, type CEH, type HelperTextMap, type IE, type IEH, Input, type InputProps, WordsInput, type WordsInputBaseProps, type WordsInputLenientModeProps, type WordsInputProps, type WordsInputStrictModeProps, type t007InputElement, useFormManager };