vvjr-1.0.3/js/vvjr.js

js/vvjr.js
/**
 * @file
 * VVJ Reveal.
 *
 * Filename:     vvjr.js
 * Website:      https://www.flashwebcenter.com
 * Developer:    Alaa Haddad https://www.alaahaddad.com.
 */

(function (Drupal, drupalSettings, once) {
  Drupal.behaviors.VVJReveal = {
    attach: function (context, settings) {
      // Scoped selection for '.vvjr' elements within the context
      const reveals = once('vvjReveal', '.vvjr', context);

      reveals.forEach(reveal => {
        const breakpoints = reveal.getAttribute('data-breakpoints');
        const trigger = reveal.getAttribute('data-reveal-method');
        const parsedBreakpoint = parseInt(breakpoints, 10);
        const isBreakpointValid = !isNaN(parsedBreakpoint);

        function updateAccessibility(frontSide, backSide, isRevealed) {
          if (frontSide && backSide) {
            const frontAriaHidden = isRevealed ? 'true' : 'false';
            const backAriaHidden = isRevealed ? 'false' : 'true';

            // Set aria-hidden and tabindex based on the revealed state
            frontSide.setAttribute('aria-hidden', frontAriaHidden);
            backSide.setAttribute('aria-hidden', backAriaHidden);

            frontSide.setAttribute('tabindex', frontAriaHidden === 'true' ? '-1' : '0');
            backSide.setAttribute('tabindex', backAriaHidden === 'true' ? '-1' : '0');

            // Adjust tabbable elements based on trigger and revealed state
            frontSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
              el.setAttribute('tabindex', isRevealed ? '-1' : '0');
            });

            backSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
              if (trigger === 'click') {
                el.setAttribute('tabindex', isRevealed ? '-1' : '0');
              } else if (trigger === 'hover') {
                el.setAttribute('tabindex', isRevealed ? '0' : '-1');
              }
            });
          }
        }

        function disableLinksInsideReveal(reveal, disable) {
          const links = reveal.querySelectorAll('.front-box a, .back-box a');
          links.forEach(link => {
            if (disable) {
              link.setAttribute('tabindex', '-1');
              link.addEventListener('click', preventDefaultHandler);
            } else {
              link.removeAttribute('tabindex');
              link.removeEventListener('click', preventDefaultHandler);
            }
          });
        }

        function preventDefaultHandler(event) {
          event.preventDefault();
        }

        function applyRevealBehavior() {
          const currentWidth = window.innerWidth;

          if (breakpoints === 'all' || (isBreakpointValid && currentWidth >= parsedBreakpoint)) {
            activateReveal(reveal);
            disableLinksInsideReveal(reveal, trigger === 'click'); // Disable links when active and triggered by click
          } else {
            deactivateReveal(reveal);
            disableLinksInsideReveal(reveal, false); // Re-enable links when inactive
          }
        }

        function activateReveal(reveal) {
          const revealItems = reveal.querySelectorAll('.reveal-item');
          if (!revealItems.length) return;

          revealItems.forEach(inner => {
            const frontSide = inner.querySelector('.front-box');
            const backSide = inner.querySelector('.back-box');
            if (!frontSide || !backSide) return;

            function handleMouseEnter() {
              inner.classList.add('revealed');
              updateAccessibility(frontSide, backSide, true);
            }

            function handleMouseLeave() {
              inner.classList.remove('revealed');
              updateAccessibility(frontSide, backSide, false);
            }

            function handleClick() {
              const isRevealed = inner.classList.toggle('revealed');
              updateAccessibility(frontSide, backSide, isRevealed);

              if (isRevealed) {
                applyRevealEffect(inner, backSide);
              } else {
                resetRevealEffect(inner, backSide);
              }
            }

            function handleKeyDown(event) {
              if (event.key === 'Enter' || event.key === ' ') {
                event.preventDefault();
                const isRevealed = inner.classList.toggle('revealed');
                updateAccessibility(frontSide, backSide, isRevealed);

                if (isRevealed) {
                  applyRevealEffect(inner, backSide);
                } else {
                  resetRevealEffect(inner, backSide);
                }
              }
            }

            // Remove existing event listeners to prevent duplication
            inner.removeEventListener('mouseenter', inner.revealHandlers?.handleMouseEnter);
            inner.removeEventListener('mouseleave', inner.revealHandlers?.handleMouseLeave);
            inner.removeEventListener('click', inner.revealHandlers?.handleClick);
            inner.removeEventListener('keydown', inner.revealHandlers?.handleKeyDown);

            // Attach event listeners based on the trigger type
            if (trigger === 'hover') {
              inner.addEventListener('mouseenter', handleMouseEnter);
              inner.addEventListener('mouseleave', handleMouseLeave);
            }

            if (trigger === 'click') {
              inner.addEventListener('click', handleClick);
            }

            inner.addEventListener('keydown', handleKeyDown);

            inner.revealHandlers = { handleMouseEnter, handleMouseLeave, handleClick, handleKeyDown };
          });
        }

        function applyRevealEffect(inner, backSide) {
          backSide.style.opacity = '1';
          if (inner.classList.contains('a-fade')) {
            backSide.style.opacity = '1';
          } else if (inner.classList.contains('a-zoom')) {
            backSide.style.transform = 'scale(1)';
          } else if (inner.classList.contains('a-top')) {
            backSide.style.transform = 'translateY(0)';
          } else if (inner.classList.contains('a-right')) {
            backSide.style.transform = 'translateX(0)';
          } else if (inner.classList.contains('a-bottom')) {
            backSide.style.transform = 'translateY(0)';
          } else if (inner.classList.contains('a-left')) {
            backSide.style.transform = 'translateX(0)';
          }
        }

        function resetRevealEffect(inner, backSide) {
          backSide.style.opacity = '0';
          if (inner.classList.contains('a-fade')) {
            backSide.style.opacity = '0';
          } else if (inner.classList.contains('a-zoom')) {
            backSide.style.transform = 'scale(0)';
          } else if (inner.classList.contains('a-top')) {
            backSide.style.transform = 'translateY(-100%)';
          } else if (inner.classList.contains('a-right')) {
            backSide.style.transform = 'translateX(100%)';
          } else if (inner.classList.contains('a-bottom')) {
            backSide.style.transform = 'translateY(100%)';
          } else if (inner.classList.contains('a-left')) {
            backSide.style.transform = 'translateX(-100%)';
          }
        }

        function deactivateReveal(reveal) {
          const revealItems = reveal.querySelectorAll('.reveal-item');
          if (!revealItems.length) return;

          revealItems.forEach(inner => {
            inner.classList.remove('revealed');
            if (inner.revealHandlers) {
              const { handleMouseEnter, handleMouseLeave, handleClick, handleKeyDown } = inner.revealHandlers;
              inner.removeEventListener('mouseenter', handleMouseEnter);
              inner.removeEventListener('mouseleave', handleMouseLeave);
              inner.removeEventListener('click', handleClick);
              inner.removeEventListener('keydown', handleKeyDown);
            }
          });
        }

        function resetRevealedOnResize() {
          const revealItems = reveal.querySelectorAll('.reveal-item');
          if (!revealItems.length) return;

          revealItems.forEach(inner => {
            if (inner.classList.contains('revealed')) {
              inner.classList.remove('revealed');
              const frontSide = inner.querySelector('.front-box');
              const backSide = inner.querySelector('.back-box');
              updateAccessibility(frontSide, backSide, false);
              resetRevealEffect(inner, backSide);
            }
          });
        }

        function updateAriaHiddenBasedOnWidth() {
          const currentWidth = window.innerWidth;
          const revealItems = reveal.querySelectorAll('.reveal-item');

          revealItems.forEach(inner => {
            const frontSide = inner.querySelector('.front-box');
            const backSide = inner.querySelector('.back-box');

            if (currentWidth < parsedBreakpoint && breakpoints !== 'all') {
              // Small screen: both sides visible, disable revealing
              frontSide.setAttribute('aria-hidden', 'false');
              backSide.setAttribute('aria-hidden', 'false');

              // Remove tabindex from both sides
              frontSide.removeAttribute('tabindex');
              backSide.removeAttribute('tabindex');

              // Ensure all elements are tabbable
              const allTabbables = reveal.querySelectorAll('a, button, input, select, textarea, [tabindex]');
              allTabbables.forEach(el => el.setAttribute('tabindex', '0'));

              // Remove reveal classes and event listeners
              if (inner.classList.contains('revealed')) {
                inner.classList.remove('revealed');
              }
              inner.removeEventListener('mouseenter', inner.revealHandlers?.handleMouseEnter);
              inner.removeEventListener('mouseleave', inner.revealHandlers?.handleMouseLeave);
              inner.removeEventListener('click', inner.revealHandlers?.handleClick);
              inner.removeEventListener('keydown', inner.revealHandlers?.handleKeyDown);

              disableLinksInsideReveal(reveal, false); // Re-enable links on small screens

            } else {
              // Large screen: standard behavior
              const isRevealed = inner.classList.contains('revealed');
              const frontAriaHidden = isRevealed ? 'true' : 'false';
              const backAriaHidden = isRevealed ? 'false' : 'true';

              frontSide.setAttribute('aria-hidden', frontAriaHidden);
              backSide.setAttribute('aria-hidden', backAriaHidden);

              frontSide.setAttribute('tabindex', frontAriaHidden === 'true' ? '-1' : '0');
              backSide.setAttribute('tabindex', backAriaHidden === 'true' ? '-1' : '0');

              // Adjust tabbable elements based on trigger and revealed state
              frontSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
                el.setAttribute('tabindex', isRevealed ? '-1' : '0');
              });

              backSide.querySelectorAll('a, button, input, select, textarea, [tabindex]').forEach(el => {
                if (trigger === 'click') {
                  el.setAttribute('tabindex', isRevealed ? '-1' : '0');
                } else if (trigger === 'hover') {
                  el.setAttribute('tabindex', isRevealed ? '0' : '-1');
                }
              });

              // Reattach event listeners
              activateReveal(reveal);
              disableLinksInsideReveal(reveal, trigger === 'click'); // Disable links on large screens when clicked
            }
          });
        }

        // Throttle the resize event handler
        function throttle(func, limit) {
          let lastFunc;
          let lastRan;
          return function() {
            const context = this;
            const args = arguments;
            if (!lastRan) {
              func.apply(context, args);
              lastRan = Date.now();
            } else {
              clearTimeout(lastFunc);
              lastFunc = setTimeout(function() {
                if (Date.now() - lastRan >= limit) {
                  func.apply(context, args);
                  lastRan = Date.now();
                }
              }, limit - (Date.now() - lastRan));
            }
          };
        }

        // Initial check on load
        applyRevealBehavior();
        updateAriaHiddenBasedOnWidth();

        // Throttled resize event listener
        const resizeHandler = throttle(() => {
          resetRevealedOnResize();
          applyRevealBehavior();
          updateAriaHiddenBasedOnWidth();
        }, 200);

        window.addEventListener('resize', resizeHandler);
      });
    }
  };
})(Drupal, drupalSettings, once);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc