@t007/input 0.0.2 → 0.0.4

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