Поддержка скрин-ридеров

Поддержка вспомогательных технологий в числовых инпутах с автоматическим форматированием требует аккуратного баланса между визуальным представлением данных и их семантической доступностью для скрин-ридеров. Библиотека AutoNumeric, работающая с форматированием валют, процентов и чисел, вносит дополнительный слой представления, который может конфликтовать с тем, как экранные читалки интерпретируют значение поля ввода.

Ключевая проблема заключается в том, что визуально корректное число, содержащее разделители тысяч, символы валют и локализованные форматы, не всегда соответствует удобному для озвучивания представлению. Например, строка 1,234,567.89 € может быть прочитана неоднозначно или фрагментировано, в зависимости от движка скрин-ридера.

Семантическая модель числового поля

Базовый HTML-элемент input type="text" или input type="tel" не несёт числовой семантики. При подключении AutoNumeric он остаётся текстовым, но его содержимое динамически форматируется.

Для корректного взаимодействия со скрин-ридерами используется комбинация следующих подходов:

  • aria-valuenow — числовое значение без форматирования
  • aria-valuetext — человеко-читаемое представление числа
  • role="spinbutton" — указание на изменяемое числовое поле
  • aria-live — уведомления об изменении значения
  • скрытое или синхронизируемое «сырое» значение

AutoNumeric в стандартной конфигурации не навязывает ARIA-атрибуты, поэтому их настройка выполняется на уровне интеграционного слоя.

Разделение визуального и семантического представления

Основная стратегия поддержки скрин-ридеров заключается в разделении двух представлений одного значения:

  1. Визуальное значение — форматированное число, отображаемое пользователю
  2. Семантическое значение — чистое число без форматирования

В DOM это достигается синхронизацией AutoNumeric-инстанса с атрибутами элемента:

const anElement = new AutoNumeric(inputElement, {
  digitGroupSeparator: ',',
  decimalCharacter: '.',
  currencySymbol: '€ ',
  modifyValueOnWheel: false,
});

После каждой модификации значения производится обновление ARIA-атрибутов:

function syncAriaState(el, numericValue) {
  el.setAttribute('aria-valuenow', numericValue);

  const formattedText = new Intl.NumberFormat('de-DE', {
    style: 'currency',
    currency: 'EUR',
  }).format(numericValue);

  el.setAttribute('aria-valuetext', formattedText);
}

Проблемы озвучивания форматированных чисел

Экранные ридеры интерпретируют содержимое input-элемента буквально. При наличии разделителей тысяч и символов валют возникают следующие эффекты:

  • дробление числа на отдельные токены
  • неправильная пауза при чтении
  • произвольная интерпретация символов
  • игнорирование локали форматирования

Особенно заметны проблемы при использовании нестандартных разделителей, например пробелов как группировочного символа.

Для устранения этих проблем применяется подход с явным указанием aria-valuetext, который переопределяет текстовое представление:

inputElement.setAttribute('role', 'spinbutton');
inputElement.setAttribute('aria-valuenow', '1234567.89');
inputElement.setAttribute(
  'aria-valuetext',
  'один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь евро и восемьдесят девять центов'
);

В реальных системах текстовая конвертация может выполняться через локализованный числительный модуль.

Использование aria-live для динамических изменений

AutoNumeric изменяет значение поля в ответ на пользовательский ввод, вставку, удаление и программные обновления. Скрин-ридеры не всегда отслеживают такие изменения без дополнительной сигнализации.

Использование aria-live позволяет объявлять изменения:

  • aria-live="polite" — ненавязчивое объявление
  • aria-live="assertive" — немедленное прерывание текущего чтения

Пример интеграции:

inputElement.setAttribute('aria-live', 'polite');

function announceChange(value) {
  liveRegion.textContent = `Значение обновлено: ${value}`;
}

Часто используется отдельный скрытый live-region контейнер:

<div id="an-live-region" aria-live="polite" class="sr-only"></div>
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

Роль spinbutton и поведение клавиатуры

При использовании AutoNumeric поле может быть семантически приближено к spinbutton. Это важно для корректной работы клавиш:

  • ArrowUp / ArrowDown — изменение значения
  • PageUp / PageDown — крупный шаг
  • Home / End — минимальное и максимальное значение

ARIA-атрибуты должны отражать диапазон:

inputElement.setAttribute('role', 'spinbutton');
inputElement.setAttribute('aria-valuemin', '0');
inputElement.setAttribute('aria-valuemax', '1000000');
inputElement.setAttribute('aria-valuenow', '2500');

AutoNumeric может управлять диапазонами через конфигурацию, но ARIA слой остаётся отдельной ответственностью.

Конфликты форматирования и доступности

Основная сложность возникает при синхронизации трёх представлений:

  • DOM value (визуальный формат)
  • internal numeric value (число)
  • ARIA state (семантика)

Несогласованность приводит к следующим ошибкам:

  • скрин-ридер озвучивает устаревшее значение
  • aria-valuenow не соответствует отображаемому числу
  • aria-valuetext не обновляется после программного изменения
  • форматирование ломает восприятие диапазона

Решение заключается в централизованной функции синхронизации:

function updateAllStates(an, el) {
  const numericValue = an.getNumber();

  el.setAttribute('aria-valuenow', numericValue);

  el.setAttribute(
    'aria-valuetext',
    new Intl.NumberFormat('ru-RU', {
      style: 'decimal',
    }).format(numericValue)
  );
}

Эта функция вызывается:

  • после input-событий
  • после программного set
  • после reset
  • после изменения конфигурации AutoNumeric

Работа с локализацией озвучивания

Разные локали требуют различной стратегии формирования aria-valuetext. В европейских локалях акцент делается на группировке тысяч, в восточноазиатских — на позиционной структуре числа.

Пример локализованного преобразования:

function formatForScreenReader(value, locale) {
  return new Intl.NumberFormat(locale, {
    notation: 'standard',
  }).format(value);
}

При этом aria-valuenow всегда остаётся в нейтральном числовом формате (без разделителей и локализации).

Скрытие форматированного значения от скрин-ридера

Иногда требуется полностью исключить влияние визуального форматирования на озвучивание. В таких случаях применяется стратегия:

  • input остаётся визуальным
  • скрин-ридер получает только aria-valuetext
  • либо используется aria-hidden на вспомогательных визуальных элементах

Пример:

<input id="amount" type="text" />
<span aria-hidden="true" class="visual-format"></span>

Такой подход уменьшает расхождение между восприятием и озвучиванием.

Двухполевая модель (hidden numeric field)

Распространённая архитектура включает два поля:

  • видимое поле с AutoNumeric
  • скрытое поле с чистым значением
<input id="amount-visible" type="text" />
<input id="amount-hidden" type="hidden" />

Синхронизация:

anElement.addEventListener('autoNumeric:rawValueModified', () => {
  hiddenInput.value = anElement.getNumber();
  syncAriaState(inputElement, anElement.getNumber());
});

Эта модель упрощает интеграцию с формами и серверной валидацией, одновременно повышая предсказуемость для вспомогательных технологий.

Обработка ошибок и доступность сообщений

Ошибки валидации числового ввода должны быть доступны скрин-ридерам через aria-describedby и live regions.

inputElement.setAttribute('aria-invalid', 'true');
inputElement.setAttribute('aria-describedby', 'amount-error');
<div id="amount-error" role="alert">
  Значение выходит за допустимый диапазон
</div>

AutoNumeric может предотвращать некорректный ввод, но семантика ошибки остаётся задачей интеграционного слоя.

Стабилизация поведения при быстром вводе

При высокой скорости ввода возможны рассинхронизации между:

  • визуальным обновлением
  • внутренним значением AutoNumeric
  • ARIA-атрибутами

Для устранения используется debounce синхронизации:

let t;

inputElement.addEventListener('input', () => {
  clearTimeout(t);
  t = setTimeout(() => {
    updateAllStates(anElement, inputElement);
  }, 50);
});

Это снижает вероятность гонок состояния и некорректных объявлений.

Итоговая архитектурная модель доступного числового поля

Корректная интеграция AutoNumeric с экранными ридерами опирается на разделение слоёв:

  • AutoNumeric — форматирование и пользовательский ввод
  • ARIA слой — семантика и доступность
  • Live region — динамические уведомления
  • Синхронизационный слой — согласование состояний

Такая модель обеспечивает стабильное поведение для вспомогательных технологий при сохранении богатого форматирования и локализации числовых значений.