bootstrap_five_layouts-1.0.x-dev/js/behaviour.bsflPillBoxCounter.js

js/behaviour.bsflPillBoxCounter.js
/**
* @file
* Character counter behavior for inputs.
*/

(function (Drupal) {
  'use strict';

  class CharacterCounter {
    constructor(inputElement) {
      this.input = inputElement;
      this.maxLength = this.parseMaxLength(inputElement.getAttribute('maxlength'));
      this.counterEl = this.createCounterElement();
      this.insertAfterInput();
      this.update();
      this.attachEvents();
    }

    parseMaxLength(attrValue) {
      if (!attrValue) {
        return null;
      }
      const parsed = parseInt(attrValue, 10);
      return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
    }

    createCounterElement() {
      const el = document.createElement('div');
      el.className = 'bsfl-pillbox-counter';
      el.id = 'bsfl-counter-' + Math.random().toString(36).substr(2, 9);
      el.setAttribute('aria-live', 'polite');
      el.setAttribute('aria-atomic', 'true');
      el.setAttribute('role', 'status');
      el.setAttribute('aria-label', Drupal.t('Character count'));
      return el;
    }

    insertAfterInput() {
      let wrapper = null;
      if (this.input && typeof this.input.closest === 'function') {
        wrapper = this.input.closest('.bsfl-pillbox-wrapper, .form-item, .js-form-item, .form-group, .fieldset-wrapper');
      }
      if (!wrapper && this.input) {
        wrapper = this.input.parentNode || null;
      }
      if (wrapper) {
        wrapper.appendChild(this.counterEl);

        // Connect the input to the counter for better screen reader navigation
        const describedBy = this.input.getAttribute('aria-describedby') || '';
        this.input.setAttribute('aria-describedby', describedBy + ' ' + this.counterEl.id);
      }
    }

    formatMessage(current, max) {
      if (max !== null) {
        const message = Drupal.t('@current of @max characters', { '@current': current, '@max': max });
        // Add context for screen readers
        return Drupal.t('@current/@max', { '@current': current, '@max': max });
      }
      return Drupal.t('@current characters', { '@current': current });
    }

    update() {
      const currentLength = (this.input && typeof this.input.value === 'string') ? this.input.value.length : 0;
      const message = this.formatMessage(currentLength, this.maxLength);
      this.counterEl.textContent = message;

      // Hide counter when empty, show when has content
      if (currentLength === 0) {
        this.counterEl.style.display = 'none';
        this.counterEl.setAttribute('aria-hidden', 'true');
        // Remove counter from aria-describedby when hidden
        this.updateAriaDescribedBy('');
      } else {
        this.counterEl.style.display = '';
        this.counterEl.removeAttribute('aria-hidden');
        // Restore counter in aria-describedby when visible
        this.updateAriaDescribedBy(this.counterEl.id);
      }

      // Announce when approaching limit for better accessibility
      if (this.maxLength && currentLength > this.maxLength * 0.8) {
        this.counterEl.setAttribute('aria-live', 'assertive');
      } else {
        this.counterEl.setAttribute('aria-live', 'polite');
      }
    }

    updateAriaDescribedBy(counterId) {
      const describedBy = this.input.getAttribute('aria-describedby') || '';
      let ids = describedBy.split(' ').filter(id => id.trim() !== '' && id !== this.counterEl.id);

      if (counterId) {
        ids.push(counterId);
      }

      if (ids.length > 0) {
        this.input.setAttribute('aria-describedby', ids.join(' '));
      } else {
        this.input.removeAttribute('aria-describedby');
      }
    }

    attachEvents() {
      this.input.addEventListener('input', () => this.update());
      this.input.addEventListener('change', () => this.update());
    }
  }

  function findInputs(context) {
    const results = [];
    if (context && typeof context.matches === 'function' && context.matches('input[data-pillbox-counter]')) {
      results.push(context);
    }
    const descendants = context.querySelectorAll ? context.querySelectorAll('input[data-pillbox-counter]') : [];
    descendants.forEach(el => results.push(el));
    return results;
  }

  Drupal.behaviors.bsflPillBoxCounter = {
    attach: function (context) {
      const inputs = findInputs(context);
      inputs.forEach(input => {
        if (input.disabled) {
          return;
        }
        if (input.hasAttribute('data-bsfl-pillbox-counter-processed')) {
          return;
        }
        input.setAttribute('data-bsfl-pillbox-counter-processed', 'true');
        input.bsflCharacterCounter = new CharacterCounter(input);
      });
    }
  };

  Drupal.bsflPillBoxCounter = {
    enhance: function(selector) {
      const elements = document.querySelectorAll(selector || 'input[data-pillbox-counter]');
      elements.forEach(element => {
        if (!element.hasAttribute('data-bsfl-pillbox-counter-processed')) {
          element.setAttribute('data-pillbox-counter', 'true');
          Drupal.behaviors.bsflPillBoxCounter.attach(document);
        }
      });
    }
  };

})(Drupal);

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

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