Project

General

Profile

Actions

Padrão #33

open

Checkbox diretiva hover e click

Added by jean sodré 6 months ago. Updated 6 months ago.

Status:
concluído
Priority:
normal
Assignee:
Category:
-
Start date:
07/09/2025
Due date:
% Done:

0%

Estimated time:

Description

Identifiquei um bug, quando tento selecionar uma opção de select dentro de um container e o checkbox está marcado, ao clicar no select, desmarca o checkbox


Related issues 1 (1 open0 closed)

Copied from Padrão #31: Ajustar diretiva Popover Para Funcionar com campos Selectconcluídojean sodré07/09/2025

Actions
Actions #1

Updated by jean sodré 6 months ago

  • Copied from Padrão #31: Ajustar diretiva Popover Para Funcionar com campos Select added
Actions #2

Updated by jean sodré 6 months ago

  • Status changed from a fazer to em execução
Actions #3

Updated by jean sodré 6 months ago · Edited

  • Status changed from em execução to teste

Diretiva finalizada:

.directive('hoverEclique', function ($timeout) {
    return {
        restrict: 'A',
        link: function (scope, element, attrs) {
            element.addClass('hoverEclique');
            
            // Função para verificar se o input está selecionado
            function verificarSelecao() {
                const input = element.find('input')[0];
                if (!input) return;
                
                let estaSelecionado = false;
                
                if (input.type === 'checkbox') {
                    estaSelecionado = input.checked;
                } else if (input.type === 'radio') {
                    // Para radio, verifica se o ng-model tem o mesmo valor do input
                    const ngModel = angular.element(input).attr('ng-model');
                    if (ngModel) {
                        const modelValue = scope.$eval(ngModel);
                        estaSelecionado = (modelValue === input.value);
                    } else {
                        estaSelecionado = input.checked;
                    }
                }
                
                if (estaSelecionado) {
                    element.addClass('selecionado');
                    element[0].style.backgroundColor = '#F0F0F0';
                } else {
                    element.removeClass('selecionado');
                    element[0].style.backgroundColor = '';
                }
            }
            
            // Função para limpar seleção dos siblings (para radio buttons)
            function limparSiblings() {
                const input = element.find('input')[0];
                if (input && input.type === 'radio') {
                    const name = input.name;
                    if (name) {
                        // Busca todos os inputs radio com o mesmo name
                        const radios = document.querySelectorAll(`input[name="${name}"]`);
                        radios.forEach(radio => {
                            const container = radio.closest('.hoverEclique');
                            if (container && container !== element[0]) {
                                container.classList.remove('selecionado');
                                container.style.backgroundColor = '';
                            }
                        });
                    }
                }
            }
            
         
            function isInteractiveElement(clickedElement) {
                if (!clickedElement) return false;
                
                // Lista de elementos e classes que devem ser ignorados
                const interactiveSelectors = [
                    // Custom-select
                    '.custom-select-wrapper',
                    '.select-header',
                    '.select-options-body',
                    '.option',
                    '.arrow-icon',
                    'custom-select',
                    
                    // Popover
                    '.popover-edit-icon',
                    '.popover-container',
                    '.popover-content',
                    '.popover-body',
                    
                    // Elementos HTML interativos
                    'button',
                    'select',
                    'textarea',
                    'input[type="text"]',
                    'input[type="number"]',
                    'input[type="email"]',
                    'input[type="password"]',
                    'input[type="search"]',
                    'input[type="url"]',
                    'input[type="tel"]',
                    'input[type="date"]',
                    'input[type="datetime-local"]',
                    'input[type="month"]',
                    'input[type="time"]',
                    'input[type="week"]',
                    'input[type="color"]',
                    'input[type="range"]',
                    'input[type="file"]',
                    
                    // Elementos com atributos específicos
                    '[contenteditable]',
                    '[data-interactive]',
                    '[data-no-hover-click]'
                ];
                
                // Verifica se o elemento clicado ou algum de seus pais corresponde aos seletores
                let currentElement = clickedElement;
                while (currentElement && currentElement !== element[0]) {
                    // Verifica tag name
                    const tagName = currentElement.tagName ? currentElement.tagName.toLowerCase() : '';
                    
                    // Verifica se é um elemento interativo por tag
                    if (['button', 'select', 'textarea', 'input'].includes(tagName)) {
                        // Para input, verifica se não é o input principal (checkbox/radio)
                        if (tagName === 'input') {
                            const inputPrincipal = element.find('input')[0];
                            if (currentElement !== inputPrincipal) {
                                return true;
                            }
                        } else {
                            return true;
                        }
                    }
                    
                    // Verifica se é um custom-select por tag
                    if (tagName === 'custom-select') {
                        return true;
                    }
                    
                    // Verifica classes CSS
                    if (currentElement.classList) {
                        for (let selector of interactiveSelectors) {
                            if (selector.startsWith('.')) {
                                const className = selector.substring(1);
                                if (currentElement.classList.contains(className)) {
                                    return true;
                                }
                            }
                        }
                    }
                    
                    // Verifica atributos específicos
                    if (currentElement.hasAttribute && (
                        currentElement.hasAttribute('contenteditable') ||
                        currentElement.hasAttribute('data-interactive') ||
                        currentElement.hasAttribute('data-no-hover-click')
                    )) {
                        return true;
                    }
                    
                    // Verifica se tem data-custom-select (para elementos do custom-select)
                    if (currentElement.hasAttribute && (
                        currentElement.hasAttribute('data-custom-select') ||
                        currentElement.hasAttribute('data-custom-select-header') ||
                        currentElement.hasAttribute('data-custom-select-dropdown') ||
                        currentElement.hasAttribute('data-custom-select-option')
                    )) {
                        return true;
                    }
                    
                    currentElement = currentElement.parentElement;
                }
                
                return false;
            }
            
            // Verifica estado inicial
            $timeout(function() {
                verificarSelecao();
            }, 300);
            
            // Verificação adicional para mudanças programáticas
            let intervalId = setInterval(function() {
                const input = element.find('input')[0];
                if (input) {
                    const estaChecked = input.checked;
                    const temClasse = element.hasClass('selecionado');
                    
                    // Se há divergência entre o estado do input e a classe visual
                    if (estaChecked !== temClasse) {
                        scope.$apply(function() {
                            if (input.type === 'radio') {
                                limparSiblings();
                            }
                            verificarSelecao();
                        });
                    }
                }
            }, 200);
            
            // Limpa o interval quando o elemento é destruído
            scope.$on('$destroy', function() {
                if (intervalId) {
                    clearInterval(intervalId);
                }
            });
            
            // Monitora mudanças no input
            const input = element.find('input')[0];
            if (input) {
                // Escuta mudanças no input
                angular.element(input).on('change', function() {
                    $timeout(function() {
                        if (input.type === 'radio') {
                            limparSiblings();
                        }
                        verificarSelecao();
                    }, 10);
                });
                
                // Escuta cliques diretos no input
                angular.element(input).on('click', function() {
                    $timeout(function() {
                        if (input.type === 'radio') {
                            limparSiblings();
                        }
                        verificarSelecao();
                    }, 10);
                });
                
                // Monitora mudanças no ng-model - versão mais robusta
                const ngModel = angular.element(input).attr('ng-model');
                if (ngModel) {
                    scope.$watch(ngModel, function(newVal, oldVal) {
                        // Força a verificação sempre que o modelo muda
                        $timeout(function() {
                            // Para radio buttons, limpa todos os siblings primeiro
                            if (input.type === 'radio') {
                                const name = input.name;
                                if (name) {
                                    const radios = document.querySelectorAll(`input[name="${name}"]`);
                                    radios.forEach(radio => {
                                        const container = radio.closest('.hoverEclique');
                                        if (container) {
                                            container.classList.remove('selecionado');
                                            container.style.backgroundColor = '';
                                        }
                                    });
                                }
                            }
                            
                            // Depois verifica e aplica no correto
                            verificarSelecao();
                        }, 50);
                    });
                    
                    // Watch adicional para mudanças profundas no scope
                    scope.$watch(function() {
                        return scope.$eval(ngModel);
                    }, function(newVal, oldVal) {
                        if (newVal !== oldVal) {
                            $timeout(function() {
                                if (input.type === 'radio') {
                                    const name = input.name;
                                    if (name) {
                                        const radios = document.querySelectorAll(`input[name="${name}"]`);
                                        radios.forEach(radio => {
                                            const container = radio.closest('.hoverEclique');
                                            if (container) {
                                                container.classList.remove('selecionado');
                                                container.style.backgroundColor = '';
                                            }
                                        });
                                    }
                                }
                                verificarSelecao();
                            }, 50);
                        }
                    }, true);
                }
            }
            
 
            element.on('click', function(event) {
                const input = element.find('input')[0];
                if (!input) return;
                
                const clickedElement = event.target;
                
                if (isInteractiveElement(clickedElement)) {
                    
                    return;
                }
                
                // Verifica se o clique foi diretamente no input ou em um label associado
                const isInputClick = clickedElement === input;
                const isLabelClick = clickedElement.tagName === 'LABEL' && 
                                   (clickedElement.getAttribute('for') === input.id || 
                                    clickedElement.contains(input) || 
                                    input.closest('label') === clickedElement);
                
                // Se o clique foi no input ou label, deixa o comportamento nativo acontecer
                // e apenas sincroniza o visual depois
                if (isInputClick || isLabelClick) {
                    $timeout(function() {
                        if (input.type === 'radio') {
                            limparSiblings();
                        }
                        verificarSelecao();
                    }, 10);
                    return;
                }
                
            
                if (clickedElement.closest('custom-select') || 
                    clickedElement.closest('.custom-select-wrapper') ||
                    clickedElement.closest('.popover-edit-icon') ||
                    clickedElement.closest('button') ||
                    clickedElement.closest('select') ||
                    clickedElement.closest('textarea') ||
                    clickedElement.closest('input:not([type="checkbox"]):not([type="radio"])')) {
                   
                    return;
                }
                
        
                
                event.preventDefault();
                event.stopPropagation();
                
                scope.$apply(function() {
                    if (input.type === 'checkbox') {
                        // Para checkbox, inverte o estado
                        input.checked = !input.checked;
                        
                        // Sincroniza o ng-model se existir
                        const ngModel = angular.element(input).attr('ng-model');
                        if (ngModel) {
                            scope.$eval(ngModel + ' = ' + input.checked);
                        }
                    } else if (input.type === 'radio') {
                        // Para radio, sempre marca como true
                        input.checked = true;
                        
                      
                        const ngModel = angular.element(input).attr('ng-model');
                        if (ngModel && input.value) {
                            scope.$eval(ngModel + ' = "' + input.value + '"');
                        }
                        
                        limparSiblings();
                    }
                    
                   
                    const changeEvent = new Event('change', { bubbles: true });
                    input.dispatchEvent(changeEvent);
                    
                    // Atualiza o visual
                    verificarSelecao();
                });
            });
            
            // Cleanup
            scope.$on('$destroy', function() {
                element.off('click');
                const input = element.find('input')[0];
                if (input) {
                    angular.element(input).off('change');
                    angular.element(input).off('click');
                }
            });
        }
    };
})
Actions #4

Updated by Henrique Novaes 6 months ago

  • Status changed from teste to concluído
Actions

Also available in: Atom PDF