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.