PageSourceSearch

https://panteashop.ir/wp-content/plugins/persian-elementor/assets/js/datepicker-init.js?ver=2.8.4

js panteashop.ir collected 2026-10-01 10:39:09 UTC 23,952 bytes, 598 lines download raw bytes

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.