PageSourceSearch

https://ajanswork.com/assets/js/framework/maskas.js

js ajanswork.com collected 2026-10-01 15:03:36 UTC 12,587 bytes, 445 lines download raw bytes

1var APP_LOCALE = (
2	document.documentElement.getAttribute("data-locale") ||
3	document.documentElement.lang ||
4	"tr"
5).toLowerCase();
6
7var LOCALES = {
8	tr: {
9		messages: {
10			invalid: "Geçersiz değer",
11			invalid_amount: "Geçersiz tutar",
12			invalid_email: "E-posta formatı geçersiz",
13			invalid_phone: "Geçersiz telefon numarası",
14			invalid_username: "Geçersiz kullanıcı adı",
15		},
16	},
17	en: {
18		messages: {
19			invalid: "Invalid value",
20			invalid_amount: "Invalid amount",
21			invalid_email: "Invalid email format",
22			invalid_phone: "Invalid phone number",
23			invalid_username: "Invalid username",
24		},
25	},
26};
27
28$(function () {
29	$("[data-mask-as]").each(function () {
30		const $el = $(this);
31
32		// --------- idempotent guard ----------
33		if ($el.data("imaskInitialized")) return;
34		$el.data("imaskInitialized", true);
35
36		// --------- derive type from data-mask only ----------
37		const type = String($el.data("mask-as") || "").toLowerCase();
38
39		// ignore junk seeds like value === "phone"/"email"/"username"
40		const seed = ($el.val() ?? "").toString().trim();
41		if (seed.toLowerCase() === type) $el.val("");
42
43		// --------- hidden input handling ----------
44		const originalName = $el.attr("name");
45		let $hidden = null;
46		if (originalName) {
47			$hidden = $("<input>", {
48				type: "hidden",
49				name: originalName,
50			}).val(seed);
51			$el.before($hidden);
52			$el.removeAttr("name");
53		}
54
55		// --------- build mask config from per-type data-* options ----------
56		const cfg = maskConfigFactory(type, $el);
57
58		// --------- attach IMask ----------
59		const mask = IMask($el[0], cfg.imask || { mask: /.*/ });
60		$el.data("imask", mask);
61
62		// initial set
63		if (seed && typeof cfg.setInitial === "function") {
64			cfg.setInitial(mask, seed, $hidden);
65		} else if (seed && mask.unmaskedValue != null) {
66			mask.unmaskedValue = seed.replace(/\s+/g, "");
67		}
68
69		// keep hidden synced (if exists)
70		// keep hidden synced (if exists)
71		const writeHidden = () => {
72			if (!$hidden) return; // if no name, no hidden
73
74			// If this mask defines a completeness rule, enforce it
75			if (typeof cfg.isComplete === "function" && !cfg.isComplete(mask)) {
76				$hidden.val(""); // <-- do not accept partials
77				return;
78			}
79
80			const raw =
81				typeof cfg.getUnmasked === "function"
82					? cfg.getUnmasked(mask)
83					: (mask.unmaskedValue ?? mask.value);
84			$hidden.val(raw);
85		};
86		mask.on("accept", writeHidden);
87		writeHidden(); // initial
88
89		// validation/normalization on blur
90		$el.on("blur", () => {
91			if (typeof cfg.onBlur === "function")
92				cfg.onBlur($el, mask, $hidden);
93		});
94
95		// --------- handle external changes (autofill/.val()/reset) ----------
96		const resync = () => {
97			if ($el.val() !== mask.value) mask.updateValue();
98			writeHidden();
99		};
100		// immediate resync after attach
101		mask.updateValue();
102		setTimeout(resync, 0);
103		$el.on("input change", resync);
104
105		const $form = $el.closest("form");
106		if ($form.length) {
107			bindFormSubmitManager($form);
108
109			// whenever this field is interacted with, re-evaluate submit state
110			$el.on("input change blur", () =>
111				$form.trigger("vb:validity-change"),
112			);
113
114			// keep your reset logic
115			$form.on("reset", () =>
116				setTimeout(() => {
117					resync();
118					$form.trigger("vb:validity-change");
119				}, 0),
120			);
121		}
122	});
123
124	function maskConfigFactory(type, $el) {
125		switch (type) {
126			// ---------------- CURRENCY ----------------
127			case "currency": {
128				const symbol = $el.data("currency-symbol") ?? "₺";
129				const thousands = $el.data("thousands") ?? ".";
130				const radix = $el.data("radix") ?? ",";
131				const scale = Number($el.data("scale")) || 2;
132
133				return {
134					imask: {
135						mask: Number,
136						scale,
137						signed: false,
138						thousandsSeparator: thousands,
139						padFractionalZeros: true,
140						normalizeZeros: true,
141						mapToRadix: ["."],
142						radix,
143						suffix: symbol ? ` ${symbol}` : "",
144					},
145					getUnmasked: (m) =>
146						String(m.unmaskedValue || "").replace(/[^\d.]/g, ""),
147					setInitial: (m, init) => {
148						const cleaned = String(init).replace(/[^\d,.\-]/g, "");
149						m.unmaskedValue = cleaned.replace(",", ".");
150					},
151					onBlur: ($el, m, $hid) =>
152						setValidity(
153							$el,
154							!!($hid && $hid.val()),
155							LOCALES[APP_LOCALE]?.messages.invalid_amount ||
156								"Invalid amount",
157						),
158				};
159			}
160
161			// ---------------- NUMBER ----------------
162			// ---------------- NUMBER (Turkish-style formatting, like currency but no symbol) ----------------
163			case "number": {
164				const thousands = $el.data("thousands") ?? ".";
165				const radix = $el.data("radix") ?? ",";
166				const scale = Number($el.data("scale")) || 0;
167				// jQuery normalizes data-allow-negative -> allowNegative
168				const allowNegative =
169					$el.data("allowNegative") === true ||
170					$el.data("allowNegative") === "true";
171				console.log(allowNegative);
172				return {
173					imask: {
174						mask: Number, // <-- IMask Number, not your own “Number”
175						scale,
176						signed: allowNegative, // <-- enables minus
177						thousandsSeparator: thousands,
178						radix, // "," for TR
179						mapToRadix: ["."], // allow "." input -> becomes ","
180						padFractionalZeros: true,
181						normalizeZeros: true,
182					},
183					// Preserve minus exactly as IMask gives it (e.g. "-1234.56")
184					getUnmasked: (m) => m.unmaskedValue,
185					setInitial: (m, init) => {
186						const s = String(init ?? "")
187							.trim()
188							.replace(/\s+/g, "")
189							.replace(",", ".");
190						m.unmaskedValue = s; // let IMask validate the sign
191					},
192					isComplete: (m) => {
193						const raw = m.unmaskedValue;
194						if (raw == null || raw === "") return false;
195						const n = Number(raw);
196						if (!isFinite(n)) return false;
197						if (!allowNegative && n < 0) return false; // <<< key line
198						return true;
199					},
200					onBlur: ($el, m, $hid) => {
201						const ok =
202							typeof m.compiled?.isComplete === "function"
203								? m.compiled.isComplete(m) // IMask internal, may be undefined
204								: // fallback to our rule
205									(() => {
206										const raw = m.unmaskedValue;
207										if (raw == null || raw === "")
208											return false;
209										const n = Number(raw);
210										return (
211											isFinite(n) &&
212											(allowNegative || n >= 0)
213										);
214									})();
215
216						if ($hid) $hid.val(ok ? m.unmaskedValue : "");
217						setValidity(
218							$el,
219							ok,
220							LOCALES[APP_LOCALE]?.messages.invalid_amount ||
221								"Invalid amount",
222						);
223					},
224				};
225			}
226			// ---------------- EMAIL ----------------
227			case "email": {
228				const emailTypingMask = /^\S*@?\S*$/;
229				return {
230					imask: {
231						mask: emailTypingMask,
232						prepare: (str) => str.toLowerCase(),
233						commit: (val, masked) =>
234							(masked._value = val.toLowerCase()),
235					},
236					getUnmasked: (m) => (m.value || "").trim().toLowerCase(),
237					onBlur: ($el, m, $hid) => {
238						const v = (m.value || "").trim().toLowerCase();
239						const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
240						if ($hid && !ok) $hid.val("");
241						setValidity(
242							$el,
243							ok,
244							LOCALES[APP_LOCALE]?.messages.invalid_email ||
245								"Invalid email format",
246						);
247					},
248				};
249			}
250
251			// ---------------- PHONE / PHONENUMBER ----------------
252			// Options via data-phone-mask, data-phone-cc
253			case "phone":
254			case "phonenumber": {
255				const pattern =
256					$el.data("phone-mask") || "+{9\\0} {5}00 000 00 00"; // ← changed here
257				const cc = String($el.data("phone-cc") || "90").replace(
258					/\D/g,
259					"",
260				);
261				return {
262					imask: {
263						mask: pattern,
264						lazy: false,
265					},
266					// accept pasting full number like +905xxxxxxxxx
267					setInitial: (m, init) => {
268						let d = String(init || "").replace(/\D/g, ""); // keep digits only
269						// Normalize to start with country code
270						if (d.startsWith("0090")) d = d.slice(4);
271						if (d.startsWith("90")) {
272							// ok
273						} else if (d.startsWith("0")) {
274							d = cc + d.slice(1); // 0XXXXXXXXXX -> 90XXXXXXXXXX
275						} else if (d.length === 10) {
276							d = cc + d; // national -> add cc
277						}
278						m.unmaskedValue = d; // IMask will format to +90 5__ ___ __ __
279					},
280					getUnmasked: (m) => {
281						const digits = (m.unmaskedValue || "").replace(
282							/\D/g,
283							"",
284						);
285						const normalized = digits.startsWith(cc)
286							? digits
287							: cc + digits;
288						console.log(m);
289						return "+" + normalized; // +905xxxxxxxxx
290					},
291					onBlur: ($el, m, $hid) => {
292						if (!$hid) return;
293						const raw = String($hid.val() || "");
294						const ok = /^\+905\d{9}$/.test(raw); // TR mobile only
295						if (!ok) $hid.val("");
296						setValidity(
297							$el,
298							ok,
299							LOCALES[APP_LOCALE]?.messages.invalid_phone ||
300								"Invalid phone number",
301						);
302					},
303				};
304			}
305
306			// ---------------- USERNAME ----------------
307			// No options other than the type; stores handle without '@'
308			case "username": {
309				return {
310					imask: {
311						mask: /^[a-z0-9._]*$/,
312						prepare: (str) => str.toLowerCase().replace(/\s+/g, ""),
313						commit: (val, masked) =>
314							(masked._value = val.toLowerCase()),
315					},
316					getUnmasked: (m) =>
317						(m.value || "").trim().toLowerCase().replace(/^@+/, ""),
318					onBlur: ($el, m, $hid) => {
319						let v = (m.value || "")
320							.trim()
321							.toLowerCase()
322							.replace(/^@+/, "");
323						const ok =
324							v.length >= 1 &&
325							v.length <= 30 &&
326							/^[a-z0-9._]+$/.test(v) &&
327							!v.startsWith(".") &&
328							!/\.\./.test(v);
329						if ($hid) $hid.val(ok ? v : "");
330						setValidity(
331							$el,
332							ok,
333							LOCALES[APP_LOCALE]?.messages.invalid_username ||
334								"Invalid username",
335						);
336					},
337				};
338			}
339
340			// ---------------- DEFAULT ----------------
341			default:
342				return {
343					imask: { mask: /.*/ },
344					getUnmasked: (m) => m.value,
345				};
346		}
347	}
348
349	function setValidity($el, isValid, message) {
350		const invalidStateContainer = String(
351			$el.data("mask-invalid-container") || "",
352		).trim();
353		$el.toggleClass("is-invalid", !isValid).toggleClass(
354			"is-valid",
355			isValid,
356		);
357		const cls = "invalid-feedback vb-mask";
358		let $fb = $el.siblings("." + cls.replace(/\s/g, ".")).first();
359		// if we have invalidStateContainer, use that instead
360		if (invalidStateContainer) {
361			$fb = $(invalidStateContainer).first();
362			$(invalidStateContainer).addClass("vb-mask mb-3");
363			// move feedback into that container if not already there
364			$(invalidStateContainer)
365				.parent()
366				.find($el.parent())
367				.removeClass("mb-3");
368		}
369		if (!isValid) {
370			if (!$fb.length)
371				$fb = $(`<div class="${cls}"></div>`).insertAfter($el);
372			$fb.text(
373				message ||
374					LOCALES[APP_LOCALE]?.messages.invalid ||
375					"Invalid value",
376			);
377		} else {
378			if (invalidStateContainer) {
379				$(invalidStateContainer).removeClass("vb-mask mb-3");
380				$(invalidStateContainer)
381					.parent()
382					.find($el.parent())
383					.addClass("mb-3");
384			}
385
386			$fb.remove();
387		}
388		const $form = $el.closest("form");
389		if ($form.length) $form.trigger("vb:validity-change");
390	}
391	function getFormSubmitButtons($form) {
392		// common submit targets (buttons + inputs)
393		return $form.find('button[type="submit"], input[type="submit"]');
394	}
395
396	function updateFormSubmitState($form) {
397		if (!$form || !$form.length) return;
398
399		const $btns = getFormSubmitButtons($form);
400		if (!$btns.length) return;
401
402		// Any element marked invalid blocks submit
403		const hasInvalid = $form.find(".is-invalid").length > 0;
404
405		// Optional: required hidden inputs (your real values) must be present too
406		// Only applies if you use required on your original field.
407		const hasMissingRequiredHidden = $form
408			.find("[data-mask-as][required]")
409			.toArray()
410			.some((el) => {
411				const $masked = $(el);
412				const $hidden = $masked.prev('input[type="hidden"][name]');
413				if (!$hidden.length) return false;
414				return String($hidden.val() || "").trim() === "";
415			});
416
417		const disable = hasInvalid || hasMissingRequiredHidden;
418
419		$btns.prop("disabled", disable);
420
421		// Optional UX: add a class so you can style disabled submit if you want
422		$btns.toggleClass("vb-submit-disabled", disable);
423	}
424
425	function bindFormSubmitManager($form) {
426		if (!$form || !$form.length) return;
427
428		// idempotent
429		if ($form.data("vbSubmitManager")) return;
430		$form.data("vbSubmitManager", true);
431
432		// react to our own custom event + typical input changes
433		$form.on("vb:validity-change input change", () =>
434			updateFormSubmitState($form),
435		);
436
437		// on reset, wait for masks resync then update
438		$form.on("reset", () =>
439			setTimeout(() => updateFormSubmitState($form), 0),
440		);
441
442		// initial
443		updateFormSubmitState($form);
444	}
445});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.