1(function () { 2 'use strict'; 3 4 // Track active calendar instances 5 var activeCalendars = new WeakMap(); 6 var calendarPopup = null; 7 var currentInput = null; 8 var lastOpenTime = 0; // Track when calendar was opened 9 10 /** 11 * Create popup container for calendar 12 */ 13 function getOrCreatePopup() { 14 if (!calendarPopup) { 15 calendarPopup = document.createElement('div'); 16 calendarPopup.className = 'persian-date-popup'; 17 calendarPopup.style.cssText = 'position:absolute;z-index:999999;display:none;'; 18 document.body.appendChild(calendarPopup); 19 } 20 return calendarPopup; 21 } 22 23 /** 24 * Position popup near input element 25 */ 26 function positionPopup(input) { 27 var popup = getOrCreatePopup(); 28 var rect = input.getBoundingClientRect(); 29 var scrollTop = window.pageYOffset || document.documentElement.scrollTop; 30 var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft; 31 32 var targetTop = rect.bottom + scrollTop + 5; 33 var targetLeft = rect.left + scrollLeft; 34 35 // Adjust position relative to parent if inside a popup modal 36 var parent = popup.parentNode; 37 var parentRect = null; 38 if (parent && parent !== document.body) { 39 parentRect = parent.getBoundingClientRect(); 40 targetTop -= (parentRect.top + scrollTop); 41 targetLeft -= (parentRect.left + scrollLeft); 42 } 43 44 popup.style.top = targetTop + 'px'; 45 popup.style.left = targetLeft + 'px'; 46 47 // Check if popup goes off screen 48 setTimeout(function () { 49 var popupRect = popup.getBoundingClientRect(); 50 if (popupRect.right > window.innerWidth) { 51 var newLeft = window.innerWidth - popupRect.width - 10 + scrollLeft; 52 if (parentRect) { 53 newLeft -= (parentRect.left + scrollLeft); 54 } 55 popup.style.left = newLeft + 'px'; 56 } 57 if (popupRect.bottom > window.innerHeight) { 58 var newTop = rect.top + scrollTop - popupRect.height - 5; 59 if (parentRect) { 60 newTop -= (parentRect.top + scrollTop); 61 } 62 popup.style.top = newTop + 'px'; 63 } 64 }, 0); 65 } 66 67 68 /** 69 * Check if calendar is open 70 */ 71 function isCalendarOpen() { 72 return calendarPopup && calendarPopup.style.display !== 'none'; 73 } 74 75 /** 76 * Convert Gregorian date string to Jalali date string 77 */ 78 function gregorianToJalaliString(gregStr) { 79 if (!gregStr || typeof PersianElementorDateConverter === 'undefined') return gregStr; 80 var parts = gregStr.trim().split(' '); 81 var datePart = parts[0] || ''; 82 var timePart = parts[1] || ''; 83 84 var dParts = datePart.split(/[\/\-]/); 85 if (dParts.length === 3) { 86 var gy = parseInt(dParts[0], 10); 87 var gm = parseInt(dParts[1], 10); 88 var gd = parseInt(dParts[2], 10); 89 if (gy > 1700) { 90 var jalali = PersianElementorDateConverter.gregorianToJalali(gy, gm, gd); 91 if (jalali[0] > 0) { 92 var jStr = jalali[0] + '-' + 93 String(jalali[1]).padStart(2, '0') + '-' + 94 String(jalali[2]).padStart(2, '0'); 95 if (timePart) { 96 jStr += ' ' + timePart; 97 } 98 return jStr; 99 } 100 } 101 } 102 return gregStr; 103 } 104 105 /** 106 * Show calendar popup 107 */ 108 function showCalendar(displayInput, realInput) { 109 if (!realInput) realInput = displayInput; 110 if (typeof PersianElementorCalendar === 'undefined') { 111 console.error('Persian Elementor: PersianElementorCalendar not loaded'); 112 return; 113 } 114 115 // Suppress native Flatpickr popup if active 116 suppressFlatpickr(realInput); 117 118 // If already open for a different input, close it first 119 if (isCalendarOpen() && currentInput !== displayInput) {
120 hideCalendar(); 121 } 122 123 // If already open for this input, don't reopen 124 if (isCalendarOpen() && currentInput === displayInput) { 125 return; 126 } 127 128 var popup = getOrCreatePopup(); 129 130 // Find parent popup to avoid triggering click-outside close handlers 131 var parentPopup = displayInput.closest('.elementor-popup-modal, .jet-popup, .dialog-widget, .jet-popup-container'); 132 if (parentPopup) { 133 parentPopup.appendChild(popup); 134 } else if (popup.parentNode !== document.body) { 135 document.body.appendChild(popup); 136 } 137 138 currentInput = displayInput; 139 lastOpenTime = Date.now(); 140 141 // Clear previous calendar and destroy instance 142 if (activeCalendars.has(popup)) { 143 var oldCalendar = activeCalendars.get(popup); 144 if (oldCalendar && typeof oldCalendar.destroy === 'function') { 145 oldCalendar.destroy(); 146 } 147 activeCalendars.delete(popup); 148 } 149 popup.innerHTML = ''; 150 151 // Check if field is an Elementor Editor Panel Control (inside editor sidebar/panel) 152 var isEditorControl = realInput.hasAttribute('data-setting') || 153 (realInput.closest && realInput.closest('.elementor-control-input-wrapper, .elementor-control')); 154 155 var enableTime = false; 156 var enableTimeAttr = realInput.getAttribute('data-enable-time'); 157 var settingAttr = realInput.getAttribute('data-setting') || ''; 158 var dateFormat = realInput.getAttribute('data-date-format') || realInput.getAttribute('placeholder') || ''; 159 var existingVal = realInput.value ? realInput.value.trim() : ''; 160 161 if (enableTimeAttr === 'true' || enableTimeAttr === '1') { 162 enableTime = true; 163 } else if (enableTimeAttr === 'false' || enableTimeAttr === '0') { 164 enableTime = false; 165 } else if (settingAttr === 'due_date' || realInput.classList.contains('elementor-time-field')) { 166 enableTime = true; 167 } else if (dateFormat && /[HiGgK:]/.test(dateFormat)) { 168 enableTime = true; 169 } else if (existingVal && existingVal.indexOf(':') !== -1) { 170 enableTime = true; 171 } else { 172 enableTime = false; 173 } 174 175 // Parse existing value from realInput 176 var selectedDate = new Date(); 177 var selectedHour = selectedDate.getHours(); 178 var selectedMinute = selectedDate.getMinutes(); 179 180 if (existingVal && typeof PersianElementorDateConverter !== 'undefined') { 181 var parts = existingVal.split(' '); 182 var datePart = parts[0] || ''; 183 var timePart = parts[1] || ''; 184 185 if (timePart) { 186 var tParts = timePart.split(':'); 187 if (tParts.length >= 2) { 188 selectedHour = parseInt(tParts[0], 10) || 0; 189 selectedMinute = parseInt(tParts[1], 10) || 0; 190 } 191 } 192 193 var dParts = datePart.split(/[\/\-]/); 194 if (dParts.length === 3) { 195 var y = parseInt(dParts[0], 10); 196 var m = parseInt(dParts[1], 10); 197 var d = parseInt(dParts[2], 10); 198 if (y > 1700) { 199 selectedDate = new Date(y, m - 1, d, selectedHour, selectedMinute); 200 } else if (y > 1300) { 201 var gregorian = PersianElementorDateConverter.jalaliToGregorian(y, m, d); 202 if (gregorian[0] > 0) { 203 selectedDate = new Date(gregorian[0], gregorian[1] - 1, gregorian[2], selectedHour, selectedMinute); 204 } 205 } 206 } 207 } 208 209 // Create calendar with closure for this specific input 210 var calendar = new PersianElementorCalendar(popup, { 211 selectedDate: selectedDate, 212 enableTime: enableTime, 213 selectedHour: selectedHour, 214 selectedMinute: selectedMinute, 215 title: enableTime ? 'Ø§ÙØªØ®Ø§Ø¨ ØªØ§Ø±ÛØ® ٠ز٠اÙ' : 'Ø§ÙØªØ®Ø§Ø¨ ØªØ§Ø±ÛØ®', 216 showCloseButton: true, 217 onDateSelect: function (dateInfo) { 218 var jalali = dateInfo.jalali; 219 var gregorian = dateInfo.gregorian; 220
221 // Format Jalali string (YYYY/MM/DD or YYYY/MM/DD HH:mm) 222 var jFormatted = jalali.year + '/' + 223 String(jalali.month).padStart(2, '0') + '/' + 224 String(jalali.day).padStart(2, '0'); 225 if (enableTime) { 226 jFormatted += ' ' + String(jalali.hour).padStart(2, '0') + ':' + 227 String(jalali.minute).padStart(2, '0'); 228 } 229 230 if (isEditorControl) { 231 // For Editor Panel Controls (e.g., Countdown due_date): 232 // Gregorian value for realInput (Elementor model) 233 var gFormatted = gregorian.year + '-' + 234 String(gregorian.month).padStart(2, '0') + '-' + 235 String(gregorian.day).padStart(2, '0'); 236 if (enableTime) { 237 gFormatted += ' ' + String(jalali.hour).padStart(2, '0') + ':' + 238 String(jalali.minute).padStart(2, '0'); 239 } 240 241 // Jalali string (YYYY-MM-DD) for displayInput mirror 242 var jDisplay = jalali.year + '-' + 243 String(jalali.month).padStart(2, '0') + '-' + 244 String(jalali.day).padStart(2, '0'); 245 if (enableTime) { 246 jDisplay += ' ' + String(jalali.hour).padStart(2, '0') + ':' + 247 String(jalali.minute).padStart(2, '0'); 248 } 249 250 if (displayInput !== realInput) { 251 displayInput.value = jDisplay; 252 } 253 realInput.value = gFormatted; 254 } else { 255 // For Form Fields / Frontend: set realInput value directly to Jalali date! 256 realInput.value = jFormatted; 257 } 258 259 // Trigger change and input events on realInput 260 var eventInput = new Event('input', { bubbles: true }); 261 var eventChange = new Event('change', { bubbles: true }); 262 realInput.dispatchEvent(eventInput); 263 realInput.dispatchEvent(eventChange); 264 265 if (window.jQuery) { 266 window.jQuery(realInput).trigger('input').trigger('change'); 267 } 268 269 if (!enableTime) { 270 hideCalendar(); 271 } 272 }, 273 onClose: function () { 274 hideCalendar(); 275 } 276 }); 277 278 activeCalendars.set(popup, calendar); 279 280 // Set default font to Vazirmatn 281 popup.style.fontFamily = 'VazirMatn, Vazir, Vazirmatn, VazirmatnVF, sans-serif'; 282 283 // Prevent clicks inside popup from closing other popups 284 ['click', 'mousedown', 'mouseup', 'pointerdown', 'pointerup', 'touchstart', 'touchend'].forEach(function (event) { 285 popup.addEventListener(event, function (e) { 286 e.stopPropagation(); 287 }); 288 }); 289 290 // Show popup 291 popup.style.display = 'block'; 292 positionPopup(displayInput); 293 294 // Mark input as active 295 displayInput.setAttribute('data-calendar-open', 'true'); 296 297 // Ensure outside-click listeners are attached to preview canvas iframe 298 attachAllIframeListeners(); 299 } 300 301 /** 302 * Hide calendar popup 303 */ 304 function hideCalendar() { 305 if (calendarPopup) { 306 calendarPopup.style.display = 'none'; 307 calendarPopup.innerHTML = ''; 308 } 309 if (currentInput) { 310 currentInput.removeAttribute('data-calendar-open'); 311 currentInput = null; 312 } 313 } 314 315 /** 316 * Suppress native Flatpickr popup for target input only 317 */ 318 function suppressFlatpickr(input) { 319 if (!input) return; 320 if (input._flatpickr) { 321 try { 322 input._flatpickr.close(); 323 if (input._flatpickr.calendarContainer) { 324 input._flatpickr.calendarContainer.style.display = 'none'; 325 } 326 } catch (ex) {} 327 } 328 } 329 330 /** 331 * Initialize a single date field 332 */ 333 function initPersianDateField(inputOrId) { 334 var realInput = typeof inputOrId === 'string' ? 335 document.getElementById(inputOrId) : inputOrId; 336 337 if (!realInput || realInput.hasAttribute('data-persian-date-initialized')) return false; 338 339 // Skip if this is already a display input created by us 340 if (realInput.hasAttribute('data-persian-display-input')) return false; 341 342 realInput.setAttribute('data-persian-date-initialized', 'true'); 343 344 var displayInput = realInput; 345 346 // ONLY create visual display mirror for Editor Panel Controls (sidebar/panel options)! 347 var isEditorControl = realInput.hasAttribute('data-setting') || 348 (realInput.closest && realInput.closest('.elementor-control-input-wrapper, .elementor-control')); 349 350 if (isEditorControl && realInput.parentNode) { 351 displayInput = document.createElement('input'); 352 displayInput.type = 'text'; 353 displayInput.className = realInput.className + ' persian-datepicker-display'; 354 displayInput.placeholder = realInput.placeholder || ''; 355 if (realInput.hasAttribute('readonly')) { 356 displayInput.setAttribute('readonly', 'readonly'); 357 } 358 displayInput.setAttribute('data-persian-display-input', 'true'); 359 displayInput.setAttribute('data-persian-date-initialized', 'true'); 360 361 // Hide real input visually (keep in DOM for Elementor model) 362 realInput.style.display = 'none'; 363 364 // Insert displayInput right before realInput in DOM 365 realInput.parentNode.insertBefore(displayInput, realInput); 366 367 // Sync initial value: Convert Gregorian value from realInput to Jalali for displayInput 368 if (realInput.value) { 369 displayInput.value = gregorianToJalaliString(realInput.value); 370 } 371 372 // Sync changes if realInput value is updated programmatically 373 realInput.addEventListener('change', function () { 374 displayInput.value = gregorianToJalaliString(realInput.value); 375 }); 376 } 377 378 // Suppress native flatpickr on realInput 379 suppressFlatpickr(realInput); 380 381 var blockFlatpickr = function (e) { 382 suppressFlatpickr(realInput); 383 }; 384 385 displayInput.addEventListener('focus', blockFlatpickr, true); 386 displayInput.addEventListener('mousedown', blockFlatpickr, true); 387 388 // Click handler to toggle Jalali calendar 389 displayInput.addEventListener('click', function (e) { 390 e.preventDefault(); 391 e.stopPropagation(); 392 suppressFlatpickr(realInput); 393 394 if (isCalendarOpen() && currentInput === displayInput) {
395 hideCalendar(); 396 } else { 397 showCalendar(displayInput, realInput); 398 } 399 }); 400 401 return true; 402 } 403 404 /** 405 * Initialize all date fields on page 406 */ 407 function initAllDateFields() { 408 var selectorList = [ 409 '[data-persian-date]:not([data-persian-date-initialized])' 410 ]; 411 if (window.persianElementorDatepickerEnabled) { 412 selectorList.push('.elementor-date-time-picker:not([data-persian-date-initialized])'); 413 selectorList.push('.flatpickr:not([data-persian-date-initialized])'); 414 selectorList.push('input[data-setting="due_date"]:not([data-persian-date-initialized])'); 415 } 416 var selector = selectorList.join(','); 417 var fields = document.querySelectorAll(selector); 418 fields.forEach(function (field) { 419 initPersianDateField(field); 420 }); 421 } 422 423 /** 424 * Close calendar when clicking outside in parent document 425 */ 426 function handleOutsideClick(e) { 427 if (!isCalendarOpen()) return; 428 429 // Don't close if clicked within 100ms of opening (prevents race condition) 430 if (Date.now() - lastOpenTime < 100) return; 431 432 // Don't close if clicking inside calendar 433 if (calendarPopup && calendarPopup.contains(e.target)) return; 434 435 // Don't close if clicking the input that opened it 436 if (currentInput && (e.target === currentInput || currentInput.contains(e.target))) return; 437 438 hideCalendar(); 439 } 440 441 /** 442 * Close calendar when clicking or interacting inside any preview iframe (e.g. Elementor canvas) 443 */ 444 function handleIframeInteraction() { 445 if (!isCalendarOpen()) return; 446 447 // Don't close if clicked within 100ms of opening (prevents race condition) 448 if (Date.now() - lastOpenTime < 100) return; 449 450 hideCalendar(); 451 } 452 453 454 /** 455 * Attach outside-click listeners to all iframes (Elementor preview canvas, etc.) 456 */ 457 function attachAllIframeListeners() { 458 try { 459 var iframes = document.querySelectorAll('iframe'); 460 iframes.forEach(function (iframe) { 461 try { 462 var iframeDoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); 463 if (iframeDoc) { 464 ['pointerdown', 'mousedown', 'click', 'touchstart'].forEach(function (evt) { 465 iframeDoc.removeEventListener(evt, handleIframeInteraction, true); 466 iframeDoc.addEventListener(evt, handleIframeInteraction, true); 467 }); 468 } 469 iframe.removeEventListener('load', attachAllIframeListeners); 470 iframe.addEventListener('load', attachAllIframeListeners); 471 } catch (ex) { 472 // Cross-origin iframe security restriction, safe to ignore 473 } 474 }); 475 } catch (e) {} 476 } 477 478 /** 479 * Hook into Elementor Editor events to close calendar on selection / panel changes 480 */ 481 var elementorHooksAttached = false; 482 function initElementorEditorIntegration() { 483 var el = window.elementor || (window.top && window.top.elementor); 484 if (!el || elementorHooksAttached) return; 485 elementorHooksAttached = true; 486 487 if (el.on) { 488 try { 489 el.on('preview:loaded', function () { 490 attachAllIframeListeners(); 491 hideCalendar(); 492 }); 493 el.on('panel:open_editor', function () { 494 hideCalendar(); 495 }); 496 } catch (e) {} 497 } 498 499 if (el.channels && el.channels.editor) { 500 try { 501 el.channels.editor.on('section:activated', hideCalendar); 502 el.channels.editor.on('element:selected', hideCalendar); 503 } catch (e) {} 504 } 505 506 if (el.selection && typeof el.selection.on === 'function') { 507 try { 508 el.selection.on('change', hideCalendar); 509 } catch (e) {} 510 } 511 } 512 513 // Attach document-level outside click and key listeners 514 document.addEventListener('click', handleOutsideClick); 515 document.addEventListener('pointerdown', handleOutsideClick, true); 516 517 // Initialize when DOM is ready 518 if (document.readyState === 'loading') { 519 document.addEventListener('DOMContentLoaded', function () { 520 initAllDateFields(); 521 attachAllIframeListeners(); 522 initElementorEditorIntegration(); 523 }); 524 } else { 525 initAllDateFields(); 526 attachAllIframeListeners(); 527 initElementorEditorIntegration(); 528 } 529 530 // Also listen for Elementor initialization event 531 window.addEventListener('elementor/init', initElementorEditorIntegration); 532 if (typeof window !== 'undefined' && window.jQuery) { 533 window.jQuery(window).on('elementor:init', initElementorEditorIntegration); 534 } 535 536 // MutationObserver for dynamic content & Elementor panel changes 537 var observer = new MutationObserver(function (mutations) { 538 // Auto-close if the input that opened the calendar was removed from DOM 539 if (isCalendarOpen() && currentInput && !document.body.contains(currentInput)) {
540 hideCalendar(); 541 } 542 543 var shouldReinit = false; 544 545 mutations.forEach(function (mutation) { 546 if (mutation.type === 'childList') { 547 mutation.addedNodes.forEach(function (node) { 548 if (node.nodeType === Node.ELEMENT_NODE) { 549 if (node.tagName === 'IFRAME' || (node.querySelector && node.querySelector('iframe'))) { 550 attachAllIframeListeners(); 551 } 552 var hasMatch = false; 553 if (node.matches) { 554 if (node.matches('[data-persian-date]')) hasMatch = true; 555 if (window.persianElementorDatepickerEnabled && (node.matches('.elementor-date-time-picker') || node.matches('.flatpickr') || node.matches('input[data-setting="due_date"]'))) hasMatch = true; 556 } 557 if (!hasMatch && node.querySelector) { 558 if (node.querySelector('[data-persian-date]')) hasMatch = true; 559 if (window.persianElementorDatepickerEnabled && node.querySelector('.elementor-date-time-picker, .flatpickr, input[data-setting="due_date"]')) hasMatch = true; 560 } 561 if (hasMatch) shouldReinit = true; 562 } 563 }); 564 } 565 }); 566 567 if (shouldReinit) { 568 initAllDateFields(); 569 } 570 }); 571 572 observer.observe(document.body, { 573 childList: true, 574 subtree: true 575 }); 576 577 // Expose functions globally 578 window.initPersianDateField = initPersianDateField; 579 window.initAllPersianDateFields = initAllDateFields; 580 window.hidePersianCalendar = hideCalendar; 581 582 // Hook into Elementor and JetPopup events to re-initialize cloned inputs inside popups 583 if (typeof window !== 'undefined' && window.jQuery) { 584 window.jQuery(document).on('elementor/popup/show', function(event, id, instance) { 585 if (instance && instance.$element) { 586 instance.$element.find('[data-persian-date]').removeAttr('data-persian-date-initialized'); 587 initAllDateFields(); 588 } 589 }); 590 window.jQuery(document).on('jet-popup/show-event', function(event, $popup) { 591 if ($popup) { 592 $popup.find('[data-persian-date]').removeAttr('data-persian-date-initialized'); 593 initAllDateFields(); 594 } 595 }); 596 } 597 598})();
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.