UNPKG

csdsolutions-csdjs

Version:

Libreria per i progetti di CSD Solutions

747 lines (624 loc) 23.3 kB
import { Sortable } from './csd-lib.js'; // Layout Components function initializeModal(element) { if (!element) return; const $modal = $CSD(element); const $closeButtons = $modal.find('.csd-modal-close'); const $overlay = $modal.find('.csd-modal-overlay'); function closeModal() { $modal.removeClass('show'); setTimeout(() => { $modal.hide(); }, 300); } function openModal() { $modal.show(); setTimeout(() => { $modal.addClass('show'); }, 50); } $closeButtons.on('click', closeModal); $overlay.on('click', closeModal); // Store the modal instance on the element element.csdModal = { open: openModal, close: closeModal }; return element.csdModal; } function initializeConfirmDialog(element) { if (!element) return; const $dialog = $CSD(element); const $confirmBtn = $dialog.find('.csd-confirm-btn'); const $cancelBtn = $dialog.find('.csd-cancel-btn, .csd-modal-close'); const $overlay = $dialog.find('.csd-modal-overlay'); let resolvePromise; function closeDialog(result) { $dialog.removeClass('show'); setTimeout(() => { $dialog.hide(); if (resolvePromise) { resolvePromise(result); resolvePromise = null; } }, 300); } function openDialog() { $dialog.show(); setTimeout(() => { $dialog.addClass('show'); }, 50); return new Promise(resolve => { resolvePromise = resolve; }); } $confirmBtn.on('click', () => closeDialog(true)); $cancelBtn.on('click', () => closeDialog(false)); $overlay.on('click', () => closeDialog(false)); // Store the dialog instance on the element element.csdConfirmDialog = { open: openDialog, close: closeDialog }; return element.csdConfirmDialog; } function initializePopover(element) { if (!element) return; const $popover = $CSD(element); const $trigger = $CSD($popover.data('trigger')); const placement = $popover.data('placement') || 'bottom'; const trigger = $popover.data('trigger-event') || 'click'; const isConfirm = $popover.hasClass('csd-popover-confirm'); let resolvePromise; let $currentPopover = null; function createPopover() { // Remove any existing popover if ($currentPopover) { $currentPopover.remove(); } // Clone the template $currentPopover = $popover.clone(true); // Add to body $CSD('body').append($currentPopover); return $currentPopover; } function positionPopover($p) { if (!$trigger.length || !$p) return; const triggerRect = $trigger[0].getBoundingClientRect(); const popoverRect = $p[0].getBoundingClientRect(); const spacing = 12; let top, left; switch (placement) { case 'top': top = triggerRect.top - popoverRect.height - spacing; left = triggerRect.left + (triggerRect.width / 2) - (popoverRect.width / 2); break; case 'bottom': top = triggerRect.bottom + 0; left = triggerRect.left + (triggerRect.width / 2) - (popoverRect.width / 2); break; case 'left': top = triggerRect.top + (triggerRect.height / 2) - (popoverRect.height / 2); left = triggerRect.left - popoverRect.width - spacing; break; case 'right': top = triggerRect.top + (triggerRect.height / 2) - (popoverRect.height / 2); left = triggerRect.right + spacing; break; } top += window.scrollY; left += window.scrollX; const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; if (left < spacing) { left = spacing; } else if (left + popoverRect.width > viewportWidth - spacing) { left = viewportWidth - popoverRect.width - spacing; } if (top < spacing) { top = spacing; } else if (top + popoverRect.height > viewportHeight - spacing) { top = viewportHeight - popoverRect.height - spacing; } $p.css({ top: `${Math.round(top)}px`, left: `${Math.round(left)}px` }); } function showPopover() { // Remove any existing popovers $CSD('.csd-popover.show').remove(); // Create new popover const $p = createPopover(); // Show and position it $p.addClass('show'); requestAnimationFrame(() => positionPopover($p)); // Set focus handlers setupFocusHandlers($p); } function setupFocusHandlers($p) { let isProcessing = false; function handleConfirmAction(isConfirm) { if (isProcessing) return; isProcessing = true; if (resolvePromise) { resolvePromise(isConfirm); resolvePromise = null; } window.toast.show( isConfirm ? 'Operazione confermata' : 'Operazione annullata', isConfirm ? 'success' : 'info' ); hidePopover(); } // Track focus within popover const checkFocus = (e) => { if (isConfirm) { // Check if clicked element is confirm or cancel button const isConfirmBtn = e.target.classList.contains('csd-popover-confirm-btn'); const isCancelBtn = e.target.classList.contains('csd-popover-cancel-btn'); if (isConfirmBtn) { handleConfirmAction(true); return; } if (isCancelBtn) { handleConfirmAction(false); return; } } // For non-confirm popovers, hide when clicking outside if (!$p[0].contains(e.target) && !$trigger[0].contains(e.target)) { hidePopover(); } }; // Add click handlers for confirm/cancel buttons if (isConfirm) { $p.find('.csd-popover-confirm-btn').on('click', () => handleConfirmAction(true)); $p.find('.csd-popover-cancel-btn').on('click', () => handleConfirmAction(false)); } // Use both mousedown and focusin for better coverage document.addEventListener('mousedown', checkFocus); document.addEventListener('focusin', checkFocus); // Cleanup when popover is hidden const cleanup = () => { document.removeEventListener('mousedown', checkFocus); document.removeEventListener('focusin', checkFocus); $p.off('hidden.csd.popover', cleanup); if (isConfirm) { $p.find('.csd-popover-confirm-btn').off('click'); $p.find('.csd-popover-cancel-btn').off('click'); } }; $p.on('hidden.csd.popover', cleanup); } function hidePopover() { if ($currentPopover) { // Trigger cleanup event to remove all listeners $currentPopover.trigger('hidden.csd.popover'); $currentPopover.removeClass('show'); setTimeout(() => { // Remove all event listeners $currentPopover.off(); $CSD(document).off('mousedown.csd.popover'); $CSD(document).off('focusin.csd.popover'); $CSD(window).off('resize.csd.popover scroll.csd.popover'); $currentPopover.remove(); $currentPopover = null; }, 200); // Match transition duration if (resolvePromise) { resolvePromise(false); resolvePromise = null; } } } function confirmAction() { if (resolvePromise) { resolvePromise(true); resolvePromise = null; } hidePopover(); } // Hide original template $popover.remove(); // Event Bindings if (trigger === 'hover') { let hideTimeout; $trigger.on('mouseenter', () => { clearTimeout(hideTimeout); showPopover(); }); $trigger.on('mouseleave', (e) => { const toElement = e.relatedTarget; if ($currentPopover && !$currentPopover[0].contains(toElement)) { hideTimeout = setTimeout(hidePopover, 100); } }); // Hover handlers will be set up on the new popover when created } else { $trigger.on('click', (e) => { e.stopPropagation(); if (isConfirm) { showPopover(); return new Promise(resolve => { resolvePromise = resolve; }); } else { const isVisible = $currentPopover && $currentPopover.hasClass('show'); if (isVisible) { hidePopover(); } else { showPopover(); } } }); } // Update position on window resize and scroll $CSD(window).on('resize.csd.popover scroll.csd.popover', () => { if ($currentPopover && $currentPopover.hasClass('show')) { debounce(() => positionPopover($currentPopover), 100)(); } }); // Store the popover instance on the element element.csdPopover = { show: showPopover, hide: hidePopover, updatePosition: () => $currentPopover && positionPopover($currentPopover) }; return element.csdPopover; } function initializeToast() { const toastContainer = $CSD('<div>').addClass('csd-toast-container'); $CSD('body').append(toastContainer); function createToast(message, type = 'info', duration = 3000) { const toast = $CSD('<div>') .addClass('csd-toast') .addClass(`csd-toast-${type}`) .text(message); toastContainer.append(toast); setTimeout(() => { toast.addClass('show'); }, 50); setTimeout(() => { toast.removeClass('show'); setTimeout(() => { toast.remove(); }, 300); }, duration); return toast; } return { show: createToast }; } function initializeSplitter(element) { if (!element || element.initialized) return; const $splitter = $CSD(element); let activeSplitter = null; let startPosition = null; let startSizes = null; function updatePanelSize($panel, size) { $panel.data('size', size); $panel.css('flex', `0 0 ${size}%`); } function handleMouseMove(e) { if (!activeSplitter) return; const { $prevPanel, $nextPanel, isHorizontal } = activeSplitter; const splitRect = $splitter[0].getBoundingClientRect(); const currentPosition = isHorizontal ? e.clientX : e.clientY; const startOffset = isHorizontal ? splitRect.left : splitRect.top; const totalSize = isHorizontal ? splitRect.width : splitRect.height; // Calculate the position relative to the splitter const position = currentPosition - startOffset; const percentage = (position / totalSize) * 100; // Calculate new sizes ensuring they sum to 100% const totalPercentage = startSizes.prev + startSizes.next; let newPrevSize = percentage; let newNextSize = totalPercentage - percentage; // Enforce minimum size (10%) const minSize = 10; if (newPrevSize < minSize) { newPrevSize = minSize; newNextSize = totalPercentage - minSize; } else if (newNextSize < minSize) { newNextSize = minSize; newPrevSize = totalPercentage - minSize; } // Update panel sizes updatePanelSize($prevPanel, newPrevSize); updatePanelSize($nextPanel, newNextSize); // Prevent text selection while dragging e.preventDefault(); } function handleMouseUp(e) { if (!activeSplitter) return; // Remove dragging class and cursor activeSplitter.$gutter.removeClass('dragging'); $CSD('body').css('cursor', ''); // Unbind move and up handlers $CSD(document) .off('mousemove.csd.splitter') .off('mouseup.csd.splitter'); // Clear state activeSplitter = null; startPosition = null; startSizes = null; // Prevent any pending events e.preventDefault(); } // Initialize nested splitters $splitter.find('.csd-splitter').each(function() { if (!this.initialized) { initializeSplitter(this); } }); // Set initial sizes for panels const $panels = $splitter.find('.csd-split-panel'); const $gutter = $splitter.find('.csd-split-gutter'); const isHorizontal = $splitter.hasClass('horizontal'); $panels.each(function() { const $panel = $CSD(this); if (!$panel.data('size')) { $panel.data('size', 50); // Default to 50% each } updatePanelSize($panel, $panel.data('size')); }); // Handle gutter mousedown $gutter.on('mousedown', function(e) { e.preventDefault(); activeSplitter = { $gutter: $CSD(this), $prevPanel: $CSD(this).prev('.csd-split-panel'), $nextPanel: $CSD(this).next('.csd-split-panel'), isHorizontal: isHorizontal }; startPosition = isHorizontal ? e.clientX : e.clientY; startSizes = { prev: activeSplitter.$prevPanel.data('size'), next: activeSplitter.$nextPanel.data('size') }; activeSplitter.$gutter.addClass('dragging'); $CSD('body').css('cursor', isHorizontal ? 'col-resize' : 'row-resize'); // Bind move and up handlers directly to document $CSD(document).on('mousemove', handleMouseMove) $CSD(document).on('mouseup', handleMouseUp) }); // Update position on window resize and scroll $CSD(window).on('resize.csd.splitter scroll.csd.splitter', () => { if ($splitter.find('.csd-split-panel').length) { $splitter.find('.csd-split-panel').each(function() { updatePanelSize($CSD(this), $CSD(this).data('size')); }); } }); // Mark as initialized element.initialized = true; // Return cleanup function return function destroy() { $gutter.off('.csd.splitter'); $CSD(document) .off('mousemove') .off('mouseup'); element.initialized = false; }; } function initializeList(element) { if (!element || element.initialized) return; const $list = $CSD(element); const $items = $list.find('.csd-item'); // Aggiungi interazione hover e click agli items $items.each(function(_, item) { const $item = $CSD(item); $item.on('click', function(e) { // Emetti un evento custom quando un item viene cliccato const event = new CustomEvent('csd-item-click', { detail: { text: $item.find('span').text(), element: item } }); element.dispatchEvent(event); }); }); element.initialized = true; return element; } function initializeReorder() { // Inizializza i gruppi const reorderGroups = document.querySelectorAll('.csd-reorder-group'); reorderGroups.forEach(group => { // Configura le opzioni di base per Sortable const sortableOptions = { handle: '.handle', draggable: '.csd-reorder', ghostClass: 'csd-reorder-ghost', dragClass: 'csd-reorder-drag', animation: 150, // Opzioni touch specifiche touchStartThreshold: 0, supportPointer: true, fallbackTolerance: 0, delayOnTouchOnly: true, delay: 100, // Prevenzione scroll durante il drag preventScroll: true, // Feedback aptico su mobile forceFallback: false, // Callback onStart: (evt) => { evt.item.style.touchAction = 'none'; group.classList.add('reordering'); // Disabilita lo scroll del body durante il drag document.body.style.overflow = 'hidden'; }, onEnd: (evt) => { evt.item.style.touchAction = ''; group.classList.remove('reordering'); // Riabilita lo scroll del body document.body.style.overflow = ''; }, onMove: (evt) => { // Previeni lo scroll durante il movimento evt.preventDefault && evt.preventDefault(); return true; } }; // Inizializza il gruppo principale new Sortable(group, sortableOptions); // Inizializza i sottogruppi const groupItems = group.querySelectorAll('.csd-group-item'); groupItems.forEach(groupItem => { new Sortable(groupItem, { ...sortableOptions, onStart: (evt) => { evt.item.style.touchAction = 'none'; groupItem.classList.add('reordering'); document.body.style.overflow = 'hidden'; }, onEnd: (evt) => { evt.item.style.touchAction = ''; groupItem.classList.remove('reordering'); document.body.style.overflow = ''; } }); }); }); } // Initialize all splitters on document ready $CSD(document).ready(() => { $CSD('.csd-splitter').each(function() { initializeSplitter(this); }); }); // Initialize event handlers for layout components $CSD(document).ready(() => { // Initialize toast container if not exists if (!$CSD('.csd-toast-container').length) { $CSD('body').append('<div class="csd-toast-container"></div>'); } // Toast buttons $CSD(document).on('click', '.csd-btn-toast', function() { const type = $CSD(this).data('type'); const message = $CSD(this).data('message'); const $toast = $CSD('<div>') .addClass('csd-toast') .addClass(`csd-toast-${type}`) .html(` <div class="csd-toast-content"> <ion-icon name="${getToastIcon(type)}"></ion-icon> <span>${message}</span> </div> <button type="button" class="csd-toast-close"> <ion-icon name="close-outline"></ion-icon> </button> `); const $container = $CSD('.csd-toast-container'); $container.append($toast); // Show with animation setTimeout(() => { $toast.addClass('show'); }, 100); // Auto close after 3 seconds const autoCloseTimeout = setTimeout(() => { closeToast($toast); }, 2500); // Close button handler $toast.find('.csd-toast-close').on('click', function() { clearTimeout(autoCloseTimeout); closeToast($toast); }); }); function closeToast($toast) { $toast.removeClass('show'); setTimeout(() => { $toast.remove(); }, 300); } function getToastIcon(type) { switch(type) { case 'success': return 'checkmark-circle-outline'; case 'warning': return 'warning-outline'; case 'error': return 'alert-circle-outline'; case 'info': return 'information-circle-outline'; default: return 'information-circle-outline'; } } // Initialize modals and confirm dialogs $CSD('.csd-modal, .csd-modal-confirm').each(function() { initializeModal(this); }); // Modal and confirm dialog open buttons $CSD(document).on('click', '.js-open-modal, .js-open-confirm', function(e) { e.preventDefault(); const targetId = $CSD(this).data('target'); const $modal = $CSD(targetId); if ($modal.length) { $modal.show(); setTimeout(() => { $modal.addClass('show'); }, 50); } }); // Modal close buttons and overlay $CSD(document).on('click', '.csd-modal-close, .csd-modal-overlay', function(e) { e.preventDefault(); const $modal = $CSD(this).closest('.csd-modal'); $modal.removeClass('show'); }); // Handle confirm dialog buttons $CSD(document).on('click', '.csd-confirm-btn', function(e) { e.preventDefault(); const $modal = $CSD(this).closest('.csd-modal-confirm'); if ($modal.length) { console.log('Confirmed'); $modal.removeClass('show'); setTimeout(() => { $modal.hide(); }, 300); } }); $CSD(document).on('click', '.csd-cancel-btn', function(e) { e.preventDefault(); const $modal = $CSD(this).closest('.csd-modal-confirm'); if ($modal.length) { console.log('Cancelled'); $modal.removeClass('show'); setTimeout(() => { $modal.hide(); }, 300); } }); // Form submit handler $CSD('.js-form-submit').on('click', function(e) { e.preventDefault(); const $modal = $CSD(this).closest('.csd-modal'); const $form = $modal.find('form'); // Check form validity if ($form[0].checkValidity()) { // Collect form data const formData = {}; $form.find('.csd-form-control').each(function() { const $input = $CSD(this); formData[$input.attr('name')] = $input.val(); }); // Handle form submission console.log('Form data:', formData); window.toast.show('Dati salvati con successo!', 'success'); // Clear form and close modal $form[0].reset(); $modal.removeClass('show'); } else { // Trigger HTML5 validation $form[0].reportValidity(); } }); // Handle escape key $CSD(document).on('keydown', function(e) { if (e.key === 'Escape') { const $modal = $CSD('.csd-modal.show'); if ($modal.length) { $modal.removeClass('show'); } } }); }); export { initializeModal, initializePopover, initializeSplitter, initializeConfirmDialog, initializeToast, initializeList, initializeReorder };