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 * Show calendar popup 77 */ 78 function showCalendar(input) { 79 if (typeof PersianElementorCalendar === 'undefined') { 80 console.error('Persian Elementor: PersianElementorCalendar not loaded'); 81 return; 82 } 83 84 // If already open for a different input, close it first 85 if (isCalendarOpen() && currentInput !== input) { 86 hideCalendar(); 87 } 88 89 // If already open for this input, don't reopen 90 if (isCalendarOpen() && currentInput === input) { 91 return; 92 } 93 94 var popup = getOrCreatePopup(); 95 96 // Find parent popup to avoid triggering click-outside close handlers 97 var parentPopup = input.closest('.elementor-popup-modal, .jet-popup, .dialog-widget, .jet-popup-container'); 98 if (parentPopup) { 99 parentPopup.appendChild(popup); 100 } else if (popup.parentNode !== document.body) { 101 document.body.appendChild(popup); 102 } 103 104 currentInput = input; 105 lastOpenTime = Date.now(); 106 107 // Clear previous calendar and destroy instance 108 if (activeCalendars.has(popup)) { 109 var oldCalendar = activeCalendars.get(popup); 110 if (oldCalendar && typeof oldCalendar.destroy === 'function') { 111 oldCalendar.destroy(); 112 } 113 activeCalendars.delete(popup); 114 } 115 popup.innerHTML = ''; 116 117 // Parse existing value if any (format: YYYY/MM/DD) 118 var targetInput = input; 119 var selectedDate = new Date();
120 var existingValue = targetInput.value; 121 if (existingValue && typeof PersianElementorDateConverter !== 'undefined') { 122 var parts = existingValue.split('/'); 123 if (parts.length === 3) { 124 var jy = parseInt(parts[0], 10); 125 var jm = parseInt(parts[1], 10); 126 var jd = parseInt(parts[2], 10); 127 var gregorian = PersianElementorDateConverter.jalaliToGregorian(jy, jm, jd); 128 if (gregorian[0] > 0) { 129 selectedDate = new Date(gregorian[0], gregorian[1] - 1, gregorian[2]); 130 } 131 } 132 } 133 134 // Create calendar with closure for this specific input 135 var calendar = new PersianElementorCalendar(popup, { 136 selectedDate: selectedDate, 137 title: 'Ø§ÙØªØ®Ø§Ø¨ ØªØ§Ø±ÛØ®', 138 showCloseButton: true, 139 onDateSelect: function (dateInfo) { 140 var jalali = dateInfo.jalali; 141 var formatted = jalali.year + '/' + 142 String(jalali.month).padStart(2, '0') + '/' + 143 String(jalali.day).padStart(2, '0'); 144 145 // Use the captured targetInput reference 146 targetInput.value = formatted; 147 148 // Trigger change event 149 var event = new Event('change', { bubbles: true }); 150 targetInput.dispatchEvent(event); 151 152 hideCalendar(); 153 }, 154 onClose: function () { 155 hideCalendar(); 156 } 157 }); 158 159 activeCalendars.set(popup, calendar); 160 161 // Set default font to Vazirmatn 162 popup.style.fontFamily = 'VazirMatn, Vazir, Vazirmatn, VazirmatnVF, sans-serif'; 163 164 // Prevent clicks inside popup from closing other popups 165 ['click', 'mousedown', 'mouseup', 'pointerdown', 'pointerup', 'touchstart', 'touchend'].forEach(function (event) { 166 popup.addEventListener(event, function (e) { 167 e.stopPropagation(); 168 }); 169 }); 170 171 // Show popup 172 popup.style.display = 'block'; 173 positionPopup(input); 174 175 // Mark input as active 176 input.setAttribute('data-calendar-open', 'true'); 177 } 178 179 /** 180 * Hide calendar popup 181 */ 182 function hideCalendar() { 183 if (calendarPopup) { 184 calendarPopup.style.display = 'none'; 185 calendarPopup.innerHTML = ''; 186 } 187 if (currentInput) { 188 currentInput.removeAttribute('data-calendar-open'); 189 currentInput = null; 190 } 191 } 192 193 /** 194 * Initialize a single date field 195 */ 196 function initPersianDateField(inputOrId) { 197 var input = typeof inputOrId === 'string' ? 198 document.getElementById(inputOrId) : inputOrId; 199 200 if (!input) return false; 201 if (input.getAttribute('data-persian-date-initialized')) return true; 202 203 input.setAttribute('data-persian-date-initialized', 'true'); 204 205 // Only use click event, not focus 206 input.addEventListener('click', function (e) { 207 e.preventDefault(); 208 e.stopPropagation(); 209 210 // Toggle calendar 211 if (isCalendarOpen() && currentInput === input) { 212 hideCalendar(); 213 } else { 214 showCalendar(input); 215 } 216 }); 217 218 // Prevent focus from doing anything 219 input.addEventListener('focus', function (e) { 220 // Don't auto-open on focus, only on click 221 }); 222 223 return true; 224 } 225 226 /** 227 * Initialize all date fields on page 228 */ 229 function initAllDateFields() { 230 var fields = document.querySelectorAll('[data-persian-date]:not([data-persian-date-initialized])'); 231 fields.forEach(function (field) { 232 initPersianDateField(field); 233 }); 234 } 235 236 /** 237 * Close calendar when clicking outside 238 */ 239 document.addEventListener('click', function (e) { 240 if (!isCalendarOpen()) return; 241 242 // Don't close if clicked within 100ms of opening (prevents race condition) 243 if (Date.now() - lastOpenTime < 100) return; 244 245 // Don't close if clicking inside calendar 246 if (calendarPopup.contains(e.target)) return; 247 248 // Don't close if clicking the input that opened it 249 if (currentInput && e.target === currentInput) return; 250
251 hideCalendar(); 252 }); 253 254 // Initialize when DOM is ready 255 if (document.readyState === 'loading') { 256 document.addEventListener('DOMContentLoaded', function () { 257 initAllDateFields(); 258 }); 259 } else { 260 initAllDateFields(); 261 } 262 263 // MutationObserver for dynamic content 264 var observer = new MutationObserver(function (mutations) { 265 var shouldReinit = false; 266 267 mutations.forEach(function (mutation) { 268 if (mutation.type === 'childList') { 269 mutation.addedNodes.forEach(function (node) { 270 if (node.nodeType === Node.ELEMENT_NODE) { 271 if (node.matches && node.matches('[data-persian-date]')) { 272 shouldReinit = true; 273 } else if (node.querySelector && node.querySelector('[data-persian-date]')) { 274 shouldReinit = true; 275 } 276 } 277 }); 278 } 279 }); 280 281 if (shouldReinit) { 282 initAllDateFields(); 283 } 284 }); 285 286 observer.observe(document.body, { 287 childList: true, 288 subtree: true 289 }); 290 291 // Expose functions globally 292 window.initPersianDateField = initPersianDateField; 293 window.initAllPersianDateFields = initAllDateFields; 294 window.hidePersianCalendar = hideCalendar; 295 296 // Hook into Elementor and JetPopup events to re-initialize cloned inputs inside popups 297 if (typeof window !== 'undefined' && window.jQuery) { 298 window.jQuery(document).on('elementor/popup/show', function(event, id, instance) { 299 if (instance && instance.$element) { 300 instance.$element.find('[data-persian-date]').removeAttr('data-persian-date-initialized'); 301 initAllDateFields(); 302 } 303 }); 304 window.jQuery(document).on('jet-popup/show-event', function(event, $popup) { 305 if ($popup) { 306 $popup.find('[data-persian-date]').removeAttr('data-persian-date-initialized'); 307 initAllDateFields(); 308 } 309 }); 310 } 311 312})();
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.