@t007/input 0.0.1 → 0.0.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t007/input",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "A lightweight, pure JS input system.",
5
5
  "author": "Oketade Oluwatobiloba <tobioketade007@gmail.com>",
6
6
  "license": "MIT",
@@ -16,8 +16,8 @@
16
16
  "type": "module",
17
17
  "main": "./dist/index.js",
18
18
  "module": "./dist/index.js",
19
- "browser": "./dist/index.global.js",
20
19
  "unpkg": "./dist/index.global.js",
20
+ "jsdelivr": "./dist/index.global.js",
21
21
  "types": "./src/ts/types/index.d.ts",
22
22
  "style": "./src/css/index.css",
23
23
  "sideEffects": [
@@ -27,19 +27,21 @@
27
27
  ".": {
28
28
  "types": "./src/ts/types/index.d.ts",
29
29
  "import": "./dist/index.js",
30
- "default": "./dist/index.global.js"
30
+ "default": "./dist/index.js"
31
31
  },
32
+ "./standalone": "./dist/standalone.js",
33
+ "./global": "./dist/index.global.js",
32
34
  "./style.css": "./src/css/index.css"
33
35
  },
34
36
  "scripts": {
35
- "build": "tsup src/index.js --format esm,iife --clean"
37
+ "build": "tsup --config ../../tsup.config.ts"
36
38
  },
37
39
  "files": [
38
40
  "dist",
39
41
  "./src/ts/types/index.d.ts",
40
42
  "./src/css/index.css"
41
43
  ],
42
- "keywords": [
44
+ "keywords": [
43
45
  "input",
44
46
  "form",
45
47
  "form-validation",
@@ -54,4 +56,4 @@
54
56
  "dependencies": {
55
57
  "@t007/utils": "*"
56
58
  }
57
- }
59
+ }
@@ -27,7 +27,7 @@ export interface FieldOptions extends Partial<
27
27
  passwordHiddenIcon?: string;
28
28
  }
29
29
 
30
- export interface T007FormManager {
30
+ export interface FormManager {
31
31
  forms: HTMLCollectionOf<HTMLFormElement>;
32
32
  violationKeys: string[];
33
33
  init(): void;
@@ -47,21 +47,18 @@ export interface T007FormManager {
47
47
  }
48
48
 
49
49
  // BUNDLE EXPORTS & GLOBAL DECLARATIONS
50
- export const T007_Form_Manager: T007FormManager;
51
- export const field = T007FormManager["field"];
52
- export const handleFormValidation = T007FormManager["handleFormValidation"];
50
+ export const formManager: FormManager;
51
+ export const field = FormManager["field"];
52
+ export const handleFormValidation = FormManager["handleFormValidation"];
53
53
 
54
54
  declare global {
55
55
  interface T007Namespace {
56
- FM: T007FormManager;
57
- field?: T007FormManager["field"];
58
- handleFormValidation?: T007FormManager["handleFormValidation"];
56
+ FM: FormManager;
57
+ field?: FormManager["field"];
58
+ handleFormValidation?: FormManager["handleFormValidation"];
59
59
  }
60
60
 
61
61
  interface Window {
62
- T007_INPUT_CSS_SRC?: string;
63
- T007_INPUT_JS_SRC?: string;
64
-
65
62
  field?: T007Namespace["field"];
66
63
  handleFormValidation?: T007Namespace["handleFormValidation"];
67
64
  }
@@ -1,426 +0,0 @@
1
- "use strict";
2
- (() => {
3
- // ../utils/dist/index.js
4
- function isSameURL(src1, src2) {
5
- if (typeof src1 !== "string" || typeof src2 !== "string" || !src1 || !src2) return false;
6
- try {
7
- const u1 = new URL(src1, window.location.href), u2 = new URL(src2, window.location.href);
8
- return decodeURIComponent(u1.origin + u1.pathname) === decodeURIComponent(u2.origin + u2.pathname);
9
- } catch {
10
- return src1.replace(/\\/g, "/").split("?")[0].trim() === src2.replace(/\\/g, "/").split("?")[0].trim();
11
- }
12
- }
13
- function createEl(tag, props = {}, dataset = {}, styles = {}) {
14
- return assignEl(tag ? document?.createElement(tag) : void 0, props, dataset, styles) ?? null;
15
- }
16
- function assignEl(el, props = {}, dataset = {}, styles = {}) {
17
- if (!el) return;
18
- for (const k of Object.keys(props)) if (props[k] !== void 0) el[k] = props[k];
19
- for (const k of Object.keys(dataset)) if (dataset[k] !== void 0) el.dataset[k] = String(dataset[k]);
20
- for (const k of Object.keys(styles)) if (styles[k] !== void 0) el.style[k] = styles[k];
21
- }
22
- function loadResource(src, type = "style", { module, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, attempts = 3, retryKey = false } = {}, w = window) {
23
- w.t007 ??= {}, w.t007._resourceCache ??= {};
24
- if (w.t007._resourceCache[src]) return w.t007._resourceCache[src];
25
- const existing = type === "script" ? Array.prototype.find.call(w.document.scripts, (s) => isSameURL(s.src, src)) : type === "style" ? Array.prototype.find.call(w.document.styleSheets, (s) => isSameURL(s.href, src)) : null;
26
- if (existing) return w.t007._resourceCache[src] = Promise.resolve(existing);
27
- w.t007._resourceCache[src] = new Promise((resolve, reject) => {
28
- (function tryLoad(remaining, el) {
29
- const onerror = () => {
30
- el?.remove?.();
31
- if (remaining > 1) {
32
- setTimeout(tryLoad, 1e3, remaining - 1);
33
- console.warn(`Retrying ${type} load (${attempts - remaining + 1}): ${src}...`);
34
- } else {
35
- delete w.t007._resourceCache[src];
36
- reject(new Error(`${type} load failed after ${attempts} attempts: ${src}`));
37
- }
38
- };
39
- const url = retryKey && remaining < attempts ? `${src}${src.includes("?") ? "&" : "?"}_${retryKey}=${Date.now()}` : src;
40
- if (type === "script") w.document.body.append(el = createEl("script", { src: url, type: module ? "module" : "text/javascript", crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, onload: () => resolve(el), onerror }) || "");
41
- else if (type === "style") w.document.head.append(el = createEl("link", { rel: "stylesheet", href: url, media, crossOrigin, integrity, referrerPolicy, nonce, fetchPriority, onload: () => resolve(el), onerror }) || "");
42
- else reject(new Error(`Unsupported resource type: ${type}`));
43
- })(attempts);
44
- });
45
- return w.t007._resourceCache[src];
46
- }
47
- var _SCROLLERS = /* @__PURE__ */ new WeakMap();
48
- var _SCROLLER_R_OBSERVER = typeof window !== "undefined" ? new ResizeObserver((entries) => entries.forEach(({ target }) => _SCROLLERS.get(target)?.update())) : null;
49
- var _SCROLLER_M_OBSERVER = typeof window !== "undefined" ? new MutationObserver((entries) => {
50
- const els = /* @__PURE__ */ new Set();
51
- for (const entry of entries) {
52
- let node = entry.target instanceof Element ? entry.target : null;
53
- while (node && !_SCROLLERS.has(node)) node = node.parentElement;
54
- if (node) els.add(node);
55
- }
56
- for (const el of els) _SCROLLERS.get(el)?.update();
57
- }) : null;
58
- function initScrollAssist(el, { pxPerSecond = 80, assistClassName = "tmg-video-controls-scroll-assist", vertical = true, horizontal = true } = {}) {
59
- const parent = el?.parentElement;
60
- if (!parent || _SCROLLERS.has(el)) return;
61
- const assist = {};
62
- let scrollId = null, last = performance.now(), assistWidth = 20, assistHeight = 20;
63
- const update = () => {
64
- const hasInteractive = !!parent.querySelector('button, a[href], input, select, textarea, [contenteditable="true"], [tabindex]:not([tabindex="-1"])');
65
- if (horizontal) {
66
- const w = assist.left?.offsetWidth || assistWidth, check = hasInteractive ? el.clientWidth < w * 2 : false;
67
- assist.left.style.display = check ? "none" : el.scrollLeft > 0 ? "block" : "none";
68
- assist.right.style.display = check ? "none" : el.scrollLeft + el.clientWidth < el.scrollWidth - 1 ? "block" : "none";
69
- assistWidth = w;
70
- }
71
- if (vertical) {
72
- const h = assist.up?.offsetHeight || assistHeight, check = hasInteractive ? el.clientHeight < h * 2 : false;
73
- assist.up.style.display = check ? "none" : el.scrollTop > 0 ? "block" : "none";
74
- assist.down.style.display = check ? "none" : el.scrollTop + el.clientHeight < el.scrollHeight - 1 ? "block" : "none";
75
- assistHeight = h;
76
- }
77
- };
78
- const scroll = (dir) => {
79
- const frame = () => {
80
- const now = performance.now(), dt = now - last;
81
- last = now;
82
- const d = pxPerSecond * dt / 1e3;
83
- if (dir === "left") el.scrollLeft = Math.max(0, el.scrollLeft - d);
84
- if (dir === "right") el.scrollLeft = Math.min(el.scrollWidth - el.clientWidth, el.scrollLeft + d);
85
- if (dir === "up") el.scrollTop = Math.max(0, el.scrollTop - d);
86
- if (dir === "down") el.scrollTop = Math.min(el.scrollHeight - el.clientHeight, el.scrollTop + d);
87
- scrollId = requestAnimationFrame(frame);
88
- };
89
- last = performance.now();
90
- frame();
91
- };
92
- const stop = () => (cancelAnimationFrame(scrollId ?? 0), scrollId = null);
93
- const addAssist = (dir) => {
94
- const div = createEl("div", { className: assistClassName }, { scrollDirection: dir }, { display: "none" });
95
- if (!div) return;
96
- ["pointerenter", "dragenter"].forEach((evt) => div.addEventListener(evt, () => scroll(dir)));
97
- ["pointerleave", "pointerup", "pointercancel", "dragleave", "dragend"].forEach((evt) => div.addEventListener(evt, stop));
98
- dir === "left" || dir === "up" ? parent.insertBefore(div, el) : parent.append(div);
99
- assist[dir] = div;
100
- };
101
- if (horizontal) ["left", "right"].forEach(addAssist);
102
- if (vertical) ["up", "down"].forEach(addAssist);
103
- el.addEventListener("scroll", update);
104
- _SCROLLER_R_OBSERVER.observe(el);
105
- _SCROLLER_M_OBSERVER.observe(el, { childList: true, subtree: true, characterData: true });
106
- _SCROLLERS.set(el, {
107
- update,
108
- destroy() {
109
- stop();
110
- el.removeEventListener("scroll", update);
111
- _SCROLLER_R_OBSERVER.unobserve(el);
112
- _SCROLLERS.delete(el);
113
- Object.values(assist).forEach((a) => a.remove());
114
- }
115
- });
116
- update();
117
- return _SCROLLERS.get(el);
118
- }
119
-
120
- // src/index.js
121
- var T007_Form_Manager = {
122
- forms: document.getElementsByClassName("t007-input-form"),
123
- violationKeys: ["valueMissing", "typeMismatch", "patternMismatch", "stepMismatch", "tooShort", "tooLong", "rangeUnderflow", "rangeOverflow", "badInput", "customError"],
124
- init() {
125
- t007.FM.observeDOMForFields();
126
- Array.from(t007.FM.forms).forEach(t007.FM.handleFormValidation);
127
- },
128
- observeDOMForFields() {
129
- new MutationObserver((mutations) => {
130
- for (const mutation of mutations) {
131
- for (const node of mutation.addedNodes) {
132
- if (!node.tagName || !(node?.classList?.contains("t007-input-field") || node?.querySelector?.(".t007-input-field"))) continue;
133
- for (const field of [...node.querySelector(".t007-input-field") ? node.querySelectorAll(".t007-input-field") : [node]]) t007.FM.setUpField(field);
134
- }
135
- }
136
- }).observe(document.body, { childList: true, subtree: true });
137
- },
138
- getFilesHelper(files, opts) {
139
- if (!files || !files.length) return { violation: null, message: "" };
140
- const totalFiles = files.length;
141
- let totalSize = 0;
142
- let currFiles = 0;
143
- 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)}` });
144
- 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)}` });
145
- for (const file of files) {
146
- currFiles++;
147
- totalSize += file.size;
148
- if (opts.accept) {
149
- const acceptedTypes = opts.accept.split(",").map((type) => type.trim().replace(/^[*\.]+|[*\.]+$/g, "")).filter(Boolean) || [];
150
- if (!acceptedTypes.some((type) => file.type.includes(type))) return { violation: "typeMismatch", message: `File${currFiles > 1 ? currFiles : ""} type of '${file.type}' is not accepted.` };
151
- }
152
- if (opts.maxSize && file.size > opts.maxSize) return setMaxError(file.size, opts.maxSize, currFiles);
153
- if (opts.minSize && file.size < opts.minSize) return setMinError(file.size, opts.minSize, currFiles);
154
- if (opts.multiple) {
155
- if (opts.maxTotalSize && totalSize > opts.maxTotalSize) return setMaxError(totalSize, opts.maxTotalSize);
156
- if (opts.minTotalSize && totalSize < opts.minTotalSize) return setMinError(totalSize, opts.minTotalSize);
157
- 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"}` };
158
- 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"}` };
159
- }
160
- }
161
- return { violation: null, message: "" };
162
- },
163
- formatSize(size, decimals = 3, base = 1e3) {
164
- if (size < base) return `${size} byte${size == 1 ? "" : "s"}`;
165
- 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);
166
- return `${(size / Math.pow(base, exponent)).toFixed(decimals).replace(/\.0+$/, "")} ${units[exponent]}`;
167
- },
168
- togglePasswordType: (input) => input.type = input.type === "password" ? "text" : "password",
169
- toggleFilled: (input) => input?.toggleAttribute("data-filled", input.type === "checkbox" || input.type === "radio" ? input.checked : input.value !== "" || input.files?.length > 0),
170
- setFallbackHelper(field) {
171
- const helperTextWrapper = field?.querySelector(".t007-input-helper-text-wrapper");
172
- if (!helperTextWrapper || helperTextWrapper.querySelector(".t007-input-helper-text[data-violation='auto']")) return;
173
- helperTextWrapper.append(createEl("p", { className: "t007-input-helper-text" }, { violation: "auto" }));
174
- },
175
- setFieldListeners(field) {
176
- if (!field) return;
177
- const input = field.querySelector(".t007-input"), floatingLabel = field.querySelector(".t007-input-floating-label"), eyeOpen = field.querySelector(".t007-input-password-visible-icon"), eyeClosed = field.querySelector(".t007-input-password-hidden-icon");
178
- if (input.type === "file")
179
- input.addEventListener("input", async () => {
180
- const file = input.files?.[0], img = new Image();
181
- img.onload = () => {
182
- input.style.setProperty("--t007-input-image-src", `url(${src})`);
183
- input.classList.add("t007-input-image-selected");
184
- setTimeout(() => URL.revokeObjectURL(src), 1e3);
185
- };
186
- img.onerror = () => {
187
- input.style.removeProperty("--t007-input-image-src");
188
- input.classList.remove("t007-input-image-selected");
189
- URL.revokeObjectURL(src);
190
- };
191
- let src;
192
- if (file?.type?.startsWith("image")) src = URL.createObjectURL(file);
193
- else if (file?.type?.startsWith("video")) {
194
- src = await new Promise((resolve) => {
195
- let video = createEl("video"), canvas = createEl("canvas"), context = canvas.getContext("2d");
196
- video.ontimeupdate = () => {
197
- context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
198
- canvas.toBlob((blob) => resolve(URL.createObjectURL(blob)));
199
- URL.revokeObjectURL(video.src);
200
- video = video.src = video.onloadedmetadata = video.ontimeupdate = null;
201
- };
202
- video.onloadeddata = () => video.currentTime = 3;
203
- video.src = URL.createObjectURL(file);
204
- });
205
- }
206
- if (!src) {
207
- input.style.removeProperty("--t007-input-image-src");
208
- input.classList.remove("t007-input-image-selected");
209
- return;
210
- }
211
- img.src = src;
212
- });
213
- if (floatingLabel) floatingLabel.ontransitionend = () => floatingLabel.classList.remove("t007-input-shake");
214
- if (eyeOpen && eyeClosed) eyeOpen.onclick = eyeClosed.onclick = () => t007.FM.togglePasswordType(input);
215
- initScrollAssist(field.querySelector(".t007-input-helper-text-wrapper"), { vertical: false });
216
- },
217
- setUpField(field) {
218
- if (field.dataset.setUp) return;
219
- t007.FM.toggleFilled(field.querySelector(".t007-input"));
220
- t007.FM.setFallbackHelper(field);
221
- t007.FM.setFieldListeners(field);
222
- field.dataset.setUp = "true";
223
- },
224
- 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 }) {
225
- const isSelect = type === "select", isTextArea = type === "textarea", isCheckboxOrRadio = type === "checkbox" || type === "radio", field = createEl("div", { className: `t007-input-field${isWrapper ? " t007-input-is-wrapper" : ""}${indeterminate ? " t007-input-indeterminate" : ""}${!!nativeIcon ? " t007-input-icon-override" : ""}${helperText === false ? " t007-input-no-helper" : ""}${fieldClassName ? ` ${fieldClassName}` : ""}` }), labelEl = createEl("label", { className: isCheckboxOrRadio ? `t007-input-${type}-wrapper` : "t007-input-wrapper" });
226
- field.append(labelEl);
227
- if (isCheckboxOrRadio) {
228
- labelEl.innerHTML = `
229
- <span class="t007-input-${type}-box">
230
- <span class="t007-input-${type}-tag"></span>
231
- </span>
232
- <span class="t007-input-${type}-label">${label}</span>
233
- `;
234
- } else {
235
- const outline = createEl("span", { className: "t007-input-outline" });
236
- outline.innerHTML = `
237
- <span class="t007-input-outline-leading"></span>
238
- <span class="t007-input-outline-notch">
239
- <span class="t007-input-floating-label">${label}</span>
240
- </span>
241
- <span class="t007-input-outline-trailing"></span>
242
- `;
243
- labelEl.append(outline);
244
- }
245
- const inputEl = field.inputEl = createEl(isTextArea ? "textarea" : isSelect ? "select" : "input", { className: `t007-input${className ? ` ${className}` : ""}`, placeholder });
246
- if (isSelect && Array.isArray(options)) inputEl.innerHTML = options.map((opt) => typeof opt === "string" ? `<option value="${opt}">${opt}</option>` : `<option value="${opt.value}">${opt.option}</option>`).join("");
247
- if (!isSelect && !isTextArea) inputEl.type = type;
248
- if (custom) inputEl.setAttribute("custom", custom);
249
- if (minSize) inputEl.setAttribute("minsize", minSize);
250
- if (maxSize) inputEl.setAttribute("maxsize", maxSize);
251
- if (minTotalSize) inputEl.setAttribute("mintotalsize", minTotalSize);
252
- if (maxTotalSize) inputEl.setAttribute("maxtotalsize", maxTotalSize);
253
- Object.keys(otherProps).forEach((key) => inputEl[key] = otherProps[key]);
254
- labelEl.append(!isWrapper ? inputEl : children);
255
- const nativeTypes = ["date", "time", "month", "datetime-local"];
256
- if (nativeTypes.includes(type) && nativeIcon) labelEl.append(createEl("i", { className: "t007-input-icon t007-input-native-icon", innerHTML: nativeIcon }));
257
- else if (endIcon) labelEl.append(createEl("i", { className: "t007-input-icon", innerHTML: endIcon }));
258
- if (type === "password" && eyeToggler) {
259
- 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>` }));
260
- 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>` }));
261
- }
262
- if (helperText !== false) {
263
- const helperLine = createEl("div", { className: "t007-input-helper-line" }), helperWrapper = createEl("div", { className: "t007-input-helper-text-wrapper", tabIndex: "-1" });
264
- if (helperText.info) helperWrapper.append(createEl("p", { className: "t007-input-helper-text", textContent: helperText.info }, { violation: "none" }));
265
- t007.FM?.violationKeys?.forEach((key) => helperText[key] && helperWrapper.append(createEl("p", { className: "t007-input-helper-text", textContent: helperText[key] }, { violation: key })));
266
- helperLine.append(helperWrapper);
267
- field.append(helperLine);
268
- }
269
- if (passwordMeter && type === "password") {
270
- const meter = createEl("div", { className: "t007-input-password-meter" }, { strengthLevel: "1" });
271
- meter.innerHTML = `
272
- <div class="t007-input-password-strength-meter">
273
- <div class="t007-input-p-weak"></div>
274
- <div class="t007-input-p-fair"></div>
275
- <div class="t007-input-p-strong"></div>
276
- <div class="t007-input-p-very-strong"></div>
277
- </div>
278
- `;
279
- field.append(meter);
280
- }
281
- return field;
282
- },
283
- handleFormValidation(form) {
284
- if (!form?.classList.contains("t007-input-form") || form.dataset?.isValidating) return;
285
- form.dataset.isValidating = "true";
286
- form.validateOnClient = validateFormOnClient;
287
- form.toggleGlobalError = toggleFormGlobalError;
288
- const fields = form.getElementsByClassName("t007-input-field"), inputs = form.getElementsByClassName("t007-input");
289
- Array.from(fields).forEach(t007.FM.setUpField);
290
- form.addEventListener("input", ({ target }) => {
291
- t007.FM.toggleFilled(target);
292
- validateInput(target);
293
- });
294
- form.addEventListener("focusout", ({ target }) => validateInput(target, true));
295
- form.addEventListener("submit", async (e) => {
296
- toggleSubmitLoader(true);
297
- try {
298
- e.preventDefault();
299
- if (!validateFormOnClient()) return;
300
- if (form.validateOnServer && !await form.validateOnServer()) {
301
- toggleFormGlobalError(true);
302
- form.addEventListener("input", () => toggleFormGlobalError(false), { once: true, useCapture: true });
303
- return;
304
- }
305
- form.onSubmit ? form.onSubmit() : form.submit();
306
- } catch (error) {
307
- console.error(error);
308
- }
309
- toggleSubmitLoader(false);
310
- });
311
- function toggleSubmitLoader(bool) {
312
- form.classList.toggle("t007-input-submit-loading", bool);
313
- }
314
- function toggleError(input, bool, flag = false) {
315
- const field = input.closest(".t007-input-field"), floatingLabel = field.querySelector(".t007-input-floating-label");
316
- if (bool && flag) {
317
- input.setAttribute("data-error", "");
318
- floatingLabel?.classList.add("t007-input-shake");
319
- } else if (!bool) input.removeAttribute("data-error");
320
- toggleHelper(input, input.hasAttribute("data-error"));
321
- }
322
- function toggleHelper(input, bool) {
323
- const field = input.closest(".t007-input-field"), violation = t007.FM.violationKeys.find((violation2) => input.Validity?.[violation2] || input.validity[violation2]) ?? "", helper = field.querySelector(`.t007-input-helper-text[data-violation="${violation}"]`), fallbackHelper = field.querySelector(`.t007-input-helper-text[data-violation="auto"]`);
324
- input.closest(".t007-input-field").querySelectorAll(`.t007-input-helper-text:not([data-violation="${violation}"])`).forEach((helper2) => helper2?.classList.remove("t007-input-show"));
325
- if (helper) helper.classList.toggle("t007-input-show", bool);
326
- else if (fallbackHelper) {
327
- fallbackHelper.textContent = input.validationMessage;
328
- fallbackHelper.classList.toggle("t007-input-show", bool);
329
- }
330
- }
331
- function forceRevalidate(input) {
332
- input.checkValidity();
333
- input.dispatchEvent(new Event("input"));
334
- }
335
- function updatePasswordMeter(input) {
336
- const passwordMeter = input.closest(".t007-input-field").querySelector(".t007-input-password-meter");
337
- if (!passwordMeter) return;
338
- const value = input.value?.trim();
339
- let strengthLevel = 0;
340
- if (value.length < Number(input.minLength ?? 0)) strengthLevel = 1;
341
- else {
342
- if (/[a-z]/.test(value)) strengthLevel++;
343
- if (/[A-Z]/.test(value)) strengthLevel++;
344
- if (/[0-9]/.test(value)) strengthLevel++;
345
- if (/[\W_]/.test(value)) strengthLevel++;
346
- }
347
- passwordMeter.dataset.strengthLevel = strengthLevel;
348
- }
349
- function validateInput(input, flag = false) {
350
- if (form.dataset.globalError || !input?.classList.contains("t007-input")) return;
351
- updatePasswordMeter(input);
352
- let value, errorBool;
353
- switch (input.custom ?? input.getAttribute("custom")) {
354
- case "password":
355
- value = input.value?.trim();
356
- if (value === "") break;
357
- const confirmPasswordInput = Array.from(inputs).find((input2) => (input2.custom ?? input2.getAttribute("custom")) === "confirm-password");
358
- if (!confirmPasswordInput) break;
359
- const confirmPasswordValue = confirmPasswordInput.value?.trim();
360
- confirmPasswordInput.setCustomValidity(value !== confirmPasswordValue ? "Both passwords do not match" : "");
361
- toggleError(confirmPasswordInput, value !== confirmPasswordValue, flag);
362
- break;
363
- case "confirm_password":
364
- value = input.value?.trim();
365
- if (value === "") break;
366
- const passwordInput = Array.from(inputs).find((input2) => (input2.custom ?? input2.getAttribute("custom")) === "password");
367
- if (!passwordInput) break;
368
- const passwordValue = passwordInput.value?.trim();
369
- errorBool = value !== passwordValue;
370
- input.setCustomValidity(errorBool ? "Both passwords do not match" : "");
371
- break;
372
- case "onward_date":
373
- if (input.min) break;
374
- input.min = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
375
- forceRevalidate(input);
376
- break;
377
- }
378
- if (input.type === "file") {
379
- input.Validity = {};
380
- const { violation, message } = t007.FM.getFilesHelper(input.files ?? [], {
381
- accept: input.accept,
382
- multiple: input.multiple,
383
- maxSize: input.maxSize ?? Number(input.getAttribute("maxsize")),
384
- minSize: input.minSize ?? Number(input.getAttribute("minsize")),
385
- maxTotalSize: input.maxTotalSize ?? Number(input.getAttribute("maxtotalsize")),
386
- minTotalSize: input.minTotalSize ?? Number(input.getAttribute("mintotalsize")),
387
- maxLength: input.maxLength ?? Number(input.getAttribute("maxlength")),
388
- minLength: input.minLength ?? Number(input.getAttribute("minLength"))
389
- });
390
- errorBool = !!message;
391
- input.setCustomValidity(message);
392
- if (violation) input.Validity[violation] = true;
393
- }
394
- errorBool = errorBool ?? !input.validity?.valid;
395
- toggleError(input, errorBool, flag);
396
- if (errorBool) return;
397
- if (input.type === "radio")
398
- Array.from(inputs)?.filter((i) => i.name == input.name)?.forEach((radio) => toggleError(radio, errorBool, flag));
399
- }
400
- function validateFormOnClient() {
401
- Array.from(inputs).forEach((input) => validateInput(input, true));
402
- form.querySelector("input:invalid")?.focus();
403
- return Array.from(inputs).every((input) => input.checkValidity());
404
- }
405
- function toggleFormGlobalError(bool) {
406
- form.toggleAttribute("data-global-error", bool);
407
- form.querySelectorAll(".t007-input-field").forEach((field) => {
408
- field.querySelector(".t007-input")?.toggleAttribute("data-error", bool);
409
- if (bool) field.querySelector(".t007-input-floating-label")?.classList.add("t007-input-shake");
410
- });
411
- }
412
- }
413
- };
414
- if (typeof window !== "undefined") {
415
- window.t007 ??= {};
416
- t007.FM = T007_Form_Manager;
417
- t007.field = t007.FM.field;
418
- t007.handleFormValidation = t007.FM.handleFormValidation;
419
- window.T007_INPUT_CSS_SRC ??= `https://unpkg.com/@t007/input@latest/style.css`;
420
- window.field ??= t007.field;
421
- window.handleFormValidation ??= t007.handleFormValidation;
422
- console.log("%cT007 Input helpers attached to window!", "color: darkturquoise");
423
- loadResource(T007_INPUT_CSS_SRC);
424
- t007.FM.init();
425
- }
426
- })();
package/dist/index.js DELETED
@@ -1,307 +0,0 @@
1
- // src/index.js
2
- import { createEl, loadResource, initScrollAssist } from "@t007/utils";
3
- var T007_Form_Manager = {
4
- forms: document.getElementsByClassName("t007-input-form"),
5
- violationKeys: ["valueMissing", "typeMismatch", "patternMismatch", "stepMismatch", "tooShort", "tooLong", "rangeUnderflow", "rangeOverflow", "badInput", "customError"],
6
- init() {
7
- t007.FM.observeDOMForFields();
8
- Array.from(t007.FM.forms).forEach(t007.FM.handleFormValidation);
9
- },
10
- observeDOMForFields() {
11
- new MutationObserver((mutations) => {
12
- for (const mutation of mutations) {
13
- for (const node of mutation.addedNodes) {
14
- if (!node.tagName || !(node?.classList?.contains("t007-input-field") || node?.querySelector?.(".t007-input-field"))) continue;
15
- for (const field of [...node.querySelector(".t007-input-field") ? node.querySelectorAll(".t007-input-field") : [node]]) t007.FM.setUpField(field);
16
- }
17
- }
18
- }).observe(document.body, { childList: true, subtree: true });
19
- },
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
- },
50
- togglePasswordType: (input) => input.type = input.type === "password" ? "text" : "password",
51
- toggleFilled: (input) => input?.toggleAttribute("data-filled", input.type === "checkbox" || input.type === "radio" ? input.checked : input.value !== "" || input.files?.length > 0),
52
- setFallbackHelper(field) {
53
- const helperTextWrapper = field?.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" }));
56
- },
57
- setFieldListeners(field) {
58
- if (!field) return;
59
- const input = field.querySelector(".t007-input"), floatingLabel = field.querySelector(".t007-input-floating-label"), eyeOpen = field.querySelector(".t007-input-password-visible-icon"), eyeClosed = field.querySelector(".t007-input-password-hidden-icon");
60
- if (input.type === "file")
61
- input.addEventListener("input", async () => {
62
- 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
- };
73
- let src;
74
- if (file?.type?.startsWith("image")) src = URL.createObjectURL(file);
75
- else if (file?.type?.startsWith("video")) {
76
- src = await new Promise((resolve) => {
77
- let video = createEl("video"), canvas = createEl("canvas"), context = canvas.getContext("2d");
78
- video.ontimeupdate = () => {
79
- context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight);
80
- canvas.toBlob((blob) => resolve(URL.createObjectURL(blob)));
81
- URL.revokeObjectURL(video.src);
82
- video = video.src = video.onloadedmetadata = video.ontimeupdate = null;
83
- };
84
- video.onloadeddata = () => video.currentTime = 3;
85
- video.src = URL.createObjectURL(file);
86
- });
87
- }
88
- if (!src) {
89
- input.style.removeProperty("--t007-input-image-src");
90
- input.classList.remove("t007-input-image-selected");
91
- return;
92
- }
93
- img.src = src;
94
- });
95
- if (floatingLabel) floatingLabel.ontransitionend = () => floatingLabel.classList.remove("t007-input-shake");
96
- if (eyeOpen && eyeClosed) eyeOpen.onclick = eyeClosed.onclick = () => t007.FM.togglePasswordType(input);
97
- initScrollAssist(field.querySelector(".t007-input-helper-text-wrapper"), { vertical: false });
98
- },
99
- setUpField(field) {
100
- if (field.dataset.setUp) return;
101
- t007.FM.toggleFilled(field.querySelector(".t007-input"));
102
- t007.FM.setFallbackHelper(field);
103
- t007.FM.setFieldListeners(field);
104
- field.dataset.setUp = "true";
105
- },
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 }) {
107
- const isSelect = type === "select", isTextArea = type === "textarea", isCheckboxOrRadio = type === "checkbox" || type === "radio", field = createEl("div", { className: `t007-input-field${isWrapper ? " t007-input-is-wrapper" : ""}${indeterminate ? " t007-input-indeterminate" : ""}${!!nativeIcon ? " t007-input-icon-override" : ""}${helperText === false ? " t007-input-no-helper" : ""}${fieldClassName ? ` ${fieldClassName}` : ""}` }), labelEl = createEl("label", { className: isCheckboxOrRadio ? `t007-input-${type}-wrapper` : "t007-input-wrapper" });
108
- field.append(labelEl);
109
- if (isCheckboxOrRadio) {
110
- labelEl.innerHTML = `
111
- <span class="t007-input-${type}-box">
112
- <span class="t007-input-${type}-tag"></span>
113
- </span>
114
- <span class="t007-input-${type}-label">${label}</span>
115
- `;
116
- } else {
117
- const outline = createEl("span", { className: "t007-input-outline" });
118
- outline.innerHTML = `
119
- <span class="t007-input-outline-leading"></span>
120
- <span class="t007-input-outline-notch">
121
- <span class="t007-input-floating-label">${label}</span>
122
- </span>
123
- <span class="t007-input-outline-trailing"></span>
124
- `;
125
- labelEl.append(outline);
126
- }
127
- const inputEl = field.inputEl = createEl(isTextArea ? "textarea" : isSelect ? "select" : "input", { className: `t007-input${className ? ` ${className}` : ""}`, placeholder });
128
- if (isSelect && Array.isArray(options)) inputEl.innerHTML = options.map((opt) => typeof opt === "string" ? `<option value="${opt}">${opt}</option>` : `<option value="${opt.value}">${opt.option}</option>`).join("");
129
- if (!isSelect && !isTextArea) inputEl.type = type;
130
- if (custom) inputEl.setAttribute("custom", custom);
131
- if (minSize) inputEl.setAttribute("minsize", minSize);
132
- if (maxSize) inputEl.setAttribute("maxsize", maxSize);
133
- if (minTotalSize) inputEl.setAttribute("mintotalsize", minTotalSize);
134
- if (maxTotalSize) inputEl.setAttribute("maxtotalsize", maxTotalSize);
135
- Object.keys(otherProps).forEach((key) => inputEl[key] = otherProps[key]);
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 }));
139
- 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
- }
144
- if (helperText !== false) {
145
- const helperLine = createEl("div", { className: "t007-input-helper-line" }), helperWrapper = createEl("div", { className: "t007-input-helper-text-wrapper", tabIndex: "-1" });
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
- field.append(helperLine);
150
- }
151
- if (passwordMeter && type === "password") {
152
- const meter = createEl("div", { className: "t007-input-password-meter" }, { strengthLevel: "1" });
153
- meter.innerHTML = `
154
- <div class="t007-input-password-strength-meter">
155
- <div class="t007-input-p-weak"></div>
156
- <div class="t007-input-p-fair"></div>
157
- <div class="t007-input-p-strong"></div>
158
- <div class="t007-input-p-very-strong"></div>
159
- </div>
160
- `;
161
- field.append(meter);
162
- }
163
- return field;
164
- },
165
- handleFormValidation(form) {
166
- if (!form?.classList.contains("t007-input-form") || form.dataset?.isValidating) return;
167
- form.dataset.isValidating = "true";
168
- form.validateOnClient = validateFormOnClient;
169
- form.toggleGlobalError = toggleFormGlobalError;
170
- 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
- });
176
- form.addEventListener("focusout", ({ target }) => validateInput(target, true));
177
- form.addEventListener("submit", async (e) => {
178
- toggleSubmitLoader(true);
179
- try {
180
- e.preventDefault();
181
- 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();
188
- } catch (error) {
189
- console.error(error);
190
- }
191
- toggleSubmitLoader(false);
192
- });
193
- function toggleSubmitLoader(bool) {
194
- form.classList.toggle("t007-input-submit-loading", bool);
195
- }
196
- function toggleError(input, bool, flag = false) {
197
- const field = input.closest(".t007-input-field"), floatingLabel = field.querySelector(".t007-input-floating-label");
198
- if (bool && flag) {
199
- input.setAttribute("data-error", "");
200
- floatingLabel?.classList.add("t007-input-shake");
201
- } else if (!bool) input.removeAttribute("data-error");
202
- toggleHelper(input, input.hasAttribute("data-error"));
203
- }
204
- function toggleHelper(input, bool) {
205
- const field = input.closest(".t007-input-field"), violation = t007.FM.violationKeys.find((violation2) => input.Validity?.[violation2] || input.validity[violation2]) ?? "", helper = field.querySelector(`.t007-input-helper-text[data-violation="${violation}"]`), fallbackHelper = field.querySelector(`.t007-input-helper-text[data-violation="auto"]`);
206
- input.closest(".t007-input-field").querySelectorAll(`.t007-input-helper-text:not([data-violation="${violation}"])`).forEach((helper2) => helper2?.classList.remove("t007-input-show"));
207
- 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"));
216
- }
217
- function updatePasswordMeter(input) {
218
- 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;
230
- }
231
- function validateInput(input, flag = false) {
232
- if (form.dataset.globalError || !input?.classList.contains("t007-input")) return;
233
- updatePasswordMeter(input);
234
- let value, errorBool;
235
- switch (input.custom ?? input.getAttribute("custom")) {
236
- case "password":
237
- value = input.value?.trim();
238
- if (value === "") break;
239
- const confirmPasswordInput = Array.from(inputs).find((input2) => (input2.custom ?? input2.getAttribute("custom")) === "confirm-password");
240
- if (!confirmPasswordInput) break;
241
- const confirmPasswordValue = confirmPasswordInput.value?.trim();
242
- confirmPasswordInput.setCustomValidity(value !== confirmPasswordValue ? "Both passwords do not match" : "");
243
- toggleError(confirmPasswordInput, value !== confirmPasswordValue, flag);
244
- break;
245
- case "confirm_password":
246
- value = input.value?.trim();
247
- if (value === "") break;
248
- const passwordInput = Array.from(inputs).find((input2) => (input2.custom ?? input2.getAttribute("custom")) === "password");
249
- if (!passwordInput) break;
250
- const passwordValue = passwordInput.value?.trim();
251
- errorBool = value !== passwordValue;
252
- input.setCustomValidity(errorBool ? "Both passwords do not match" : "");
253
- break;
254
- case "onward_date":
255
- if (input.min) break;
256
- input.min = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
257
- forceRevalidate(input);
258
- break;
259
- }
260
- if (input.type === "file") {
261
- 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
- });
272
- errorBool = !!message;
273
- input.setCustomValidity(message);
274
- if (violation) input.Validity[violation] = true;
275
- }
276
- errorBool = errorBool ?? !input.validity?.valid;
277
- toggleError(input, errorBool, flag);
278
- if (errorBool) return;
279
- if (input.type === "radio")
280
- Array.from(inputs)?.filter((i) => i.name == input.name)?.forEach((radio) => toggleError(radio, errorBool, flag));
281
- }
282
- function validateFormOnClient() {
283
- Array.from(inputs).forEach((input) => validateInput(input, true));
284
- form.querySelector("input:invalid")?.focus();
285
- return Array.from(inputs).every((input) => input.checkValidity());
286
- }
287
- function toggleFormGlobalError(bool) {
288
- form.toggleAttribute("data-global-error", bool);
289
- form.querySelectorAll(".t007-input-field").forEach((field) => {
290
- field.querySelector(".t007-input")?.toggleAttribute("data-error", bool);
291
- if (bool) field.querySelector(".t007-input-floating-label")?.classList.add("t007-input-shake");
292
- });
293
- }
294
- }
295
- };
296
- if (typeof window !== "undefined") {
297
- window.t007 ??= {};
298
- t007.FM = T007_Form_Manager;
299
- t007.field = t007.FM.field;
300
- t007.handleFormValidation = t007.FM.handleFormValidation;
301
- window.T007_INPUT_CSS_SRC ??= `https://unpkg.com/@t007/input@latest/style.css`;
302
- window.field ??= t007.field;
303
- window.handleFormValidation ??= t007.handleFormValidation;
304
- console.log("%cT007 Input helpers attached to window!", "color: darkturquoise");
305
- loadResource(T007_INPUT_CSS_SRC);
306
- t007.FM.init();
307
- }