# Button visually impaired

[English](README.md) | Русский

Button visually impaired — плагин, который добавляет на сайт версию для слабовидящих. Панель на сайте меняет цветовую
схему, размер шрифта и интервалы, скрывает изображения или делает их чёрно-белыми, а синтезатор речи озвучивает каждое
изменение и любой размеченный вами текст.

* Размер шрифта (до 39 px), шрифт (с засечками или без), межбуквенный и межстрочный интервал;
* Пять контрастных цветовых схем; панель и окно настроек следуют выбранной схеме;
* Изображения: оттенки серого или скрытие, а вместо скрытой картинки — подпись из текста `alt`;
* Отключение встроенных элементов (видео, карт и т. д.);
* Синтезатор речи: озвучивает изменения и любой текст в блоке `.bvi-speech`, подсвечивает слова и позволяет выбрать голос;
* Интерфейс и озвучка на 11 языках;
* Работает на любом экране: на телефонах и планшетах панель сворачивается в меню;
* Доступность: управление с клавиатуры, заметный фокус, кнопки не меньше 44×44 px, разметка для скринридеров;
* Настройки запоминаются на сутки (cookie);
* Без зависимостей; ES-модули, CommonJS и `<script>`, типы TypeScript, удобно с React, Vue и Next.js (`destroy()`, безопасный для SSR импорт);
* Современные браузеры: Chrome, Edge, Opera, Firefox, Safari (Internet Explorer не поддерживается).

### Демо

Демо-версия доступна [здесь](https://bvi.isvek.ru/demo/).

Тестовые страницы с готовыми сценариями и со всеми видами элементов форм доступны на GitHub Pages:
[Русский](https://veks.github.io/button-visually-impaired-javascript/test/) и
[English](https://veks.github.io/button-visually-impaired-javascript/test/en.html).

### NPM

```
$ npm install bvi
```

``` javascript
import Bvi from "bvi"
import "bvi/style" // стили (bvi/dist/css/bvi.min.css)

new Bvi({ target: ".bvi-open" })
```

Пакет содержит ES-модули (`import`), CommonJS (`require`) и типы TypeScript. Запись `import * as isvek from "bvi"` и
`isvek.Bvi` тоже работает.

Плагин работает с DOM, поэтому создавайте его только в браузере. Серверный рендеринг безопасен: импорт пакета не
обращается к `window` и `document`.

Глобальная переменная `isvek` есть только в сборке для `<script>` (`bvi.min.js`, см. [Использование в браузере](#использование-в-браузере)).
При `import` и `require` (React, Vue, Next.js, Nuxt, Node) глобальной переменной нет: пользуйтесь импортами из примеров.
Не импортируйте файлы из `dist/js/` напрямую, `import "bvi"` сам выберет нужную сборку.

#### React

``` jsx
import { useEffect } from "react"
import Bvi from "bvi"
import "bvi/style"

export function AccessibilityButton() {
  useEffect(() => {
    const bvi = new Bvi({ target: ".bvi-open", lang: "ru-RU" })

    return () => bvi.destroy() // убирает панель и все обработчики, сохранённые настройки остаются
  }, [])

  return <button type="button" className="bvi-open">Версия для слабовидящих</button>
}
```

Создавайте экземпляр в `useEffect`, когда кнопка уже есть в DOM, и вызывайте `destroy()` при очистке. Это безопасно и в
`StrictMode` React, где эффекты в режиме разработки выполняются дважды: сохранённые настройки остаются, и второй
экземпляр восстанавливает состояние. В Next.js помещайте компонент в клиентский (`"use client"`).

Переиспользуемый хук с типами TypeScript:

``` tsx
import { useEffect } from "react"
import Bvi, { type BviOptions } from "bvi"
import "bvi/style"

export function useBvi(options: BviOptions = { target: ".bvi-open" }) {
  const key = JSON.stringify(options) // плагин пересоздаётся, только когда меняются параметры

  useEffect(() => {
    const bvi = new Bvi(JSON.parse(key))

    return () => bvi.destroy()
  }, [key])
}
```

#### Vue

Vue 3 (`<script setup>`):

``` vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted } from "vue"
import Bvi from "bvi"
import "bvi/style"

let bvi: Bvi | undefined

onMounted(() => {
  bvi = new Bvi({ target: ".bvi-open", lang: "ru-RU" })
})

onBeforeUnmount(() => bvi?.destroy()) // убирает панель и все обработчики, сохранённые настройки остаются
</script>

<template>
  <button type="button" class="bvi-open">Версия для слабовидящих</button>
</template>
```

`onMounted` не выполняется на сервере, поэтому такой код безопасен при серверном рендеринге. В Nuxt 3 подключите стили
один раз в `nuxt.config.ts` (`css: ["bvi/style"]`), а экземпляр создавайте в `onMounted` компонента (или оберните
компонент в `<ClientOnly>`).

Vue 2 (Options API):

``` javascript
import Bvi from "bvi"
import "bvi/style"

export default {
  mounted() {
    this.bvi = new Bvi({ target: ".bvi-open" })
  },
  beforeDestroy() {
    this.bvi.destroy()
  },
}
```

Примеры выше проверены с React 19 и Vue 3.5 в Chromium, Firefox и WebKit: клиентский рендеринг, `StrictMode`,
размонтирование и повторное монтирование, серверный рендеринг.

Пока плагин включён, он оборачивает содержимое страницы в элемент `.bvi-body`; `destroy()` возвращает всё обратно.
Селектор `target` должен указывать на элемент, который уже есть в DOM к моменту создания экземпляра.

#### Sass

Соберите стили сами и измените цвета (Sass с `--load-path=node_modules` или `sass-loader` в webpack):

``` scss
@use "bvi/src/scss/variables" with ($theme-bg-blue: #cfe8ff, $link-border-color: #444);
@use "bvi/src/scss/bvi";
```

Переменные лежат в `src/scss/variables/` (`_panel`, `_themes`, `_buttons`). По умолчанию цвета панели следуют выбранной
цветовой схеме; значение, которое вы передадите, заменяет их для каждой схемы.

#### CSS-переменные во время работы

Таблица стилей также определяет свойства `--bvi-*` на `:root`. Выбор темы в JavaScript уже задаёт `data-bvi-theme` на
`.bvi-body`, поэтому настройка в JavaScript не нужна: `--bvi-site-bg` и `--bvi-site-color` сами принимают значения
активной темы. Переопределить тему можно до или после подключения стилей:

```css
:root {
  --bvi-theme-blue-bg: #d7ecff;
  --bvi-theme-blue-color: #073763;
}
```

Токены панели, кнопок и окна настроек доступны с префиксами `--bvi-panel-*`, `--bvi-link-*` и `--bvi-modal-*`. По умолчанию
они принимают цвета активной схемы (`--bvi-site-bg`, `--bvi-site-color`), поэтому панель и окно настроек меняются вместе
с сайтом. Задайте токен, например `--bvi-link-border-color`, чтобы закрепить его цвет для каждой схемы.

### Использование в браузере

Скачайте [последний пакет](https://github.com/veks/button-visually-impaired-javascript/archive/master.zip), распакуйте
его и посмотрите содержимое. Скопируйте `bvi.min.js` и `bvi.min.css` (или их минифицированные варианты) в папки `dist`
вашего приложения, как показано ниже. Подключите нужный CSS в теге `<head>` документа

```html

<link href="dist/css/bvi.min.css" rel="stylesheet">
```

Подключите нужный JS в конце страницы, прямо перед закрывающим тегом `</body>`

```html

<script src="dist/js/bvi.min.js"></script>
```

Запуск с настройками по умолчанию

```html

<script>
  new isvek.Bvi();
</script>
```

Запуск со своими настройками

```html

<script>
  new isvek.Bvi({
    target: '.className',
    fontSize: 24,
    theme: 'black'
    //...и т. д.
  });
</script>
```

### HTML-классы

Произвольные ссылки

```html
<a href="#" class="className">версия для слабовидящих</a>
```

Синтез речи

```html

<div class="bvi-speech">
  Lorem Ipsum — это текст-«рыба», часто используемый в печати и вэб-дизайне. Lorem Ipsum является стандартной «рыбой»
  для текстов на латинице с начала XVI века. В то время некий безымянный печатник создал большую коллекцию размеров и
  форм шрифтов, используя Lorem Ipsum для распечатки образцов. Lorem Ipsum не только успешно пережил без заметных
  изменений пять веков, но и перешагнул в электронный дизайн.
</div>
```

Скрыть элемент

```html

<div class="bvi-hide">Текст будет скрыт, когда плагин включён.</div>
```

Показать элемент

```html

<div class="bvi-show">Текст будет показан, когда плагин включён.</div>
```

Отключить стили плагина в блоке

```html

<div class="bvi-no-styles">Стили плагина не применяются в этом блоке.</div>
```

### Настройки

Опция | Тип | Значение по умолчанию | Допустимые значения | Описание
------ | ---- | ------- | -------------- | -----------
target | string |  '.bvi-open' | '.className' | Класс элементов, запускающих плагин |
fontSize | number |  16 | 1-39 | Размер шрифта  |
theme | string |  'white' |  (`white`&#124;`black`&#124;`blue`&#124;`brown`&#124;`green`) | Цветовая схема |
images |(string&#124;boolean) | 'grayscale' |  (`true`&#124;`false`&#124;`grayscale`) | Режим изображений |
letterSpacing | string | 'normal' | (`normal`&#124;`average`&#124;`big`) | Межбуквенный интервал |
lineHeight | string | 'normal' | (`normal`&#124;`average`&#124;`big`) | Межстрочный интервал |
speech | boolean | true | (`true`&#124;`false`) | Синтез речи |
fontFamily | string | 'arial' |  (`arial`&#124;`times`) | Шрифт |
builtElements | boolean | false | (`true`&#124;`false`) | Встроенные элементы: части HTML, которые позволяют встраивать в страницу документы, видео, карты и интерактивные материалы.|
panelFixed | boolean | true | (`true`&#124;`false`) | Закрепление панели для слабовидящих вверху страницы. |
panelHide | boolean | false | (`true`&#124;`false`) | Скрывает панель для слабовидящих и показывает значок панели. |
reload | boolean | false | (`true`&#124;`false`) | Включает и отключает перезагрузку страницы при переходе к обычной версии сайта. |
lang | string | 'ru-RU' | (`ru-RU`&#124;`en-US`&#124;`es-ES`&#124;`de-DE`&#124;`fr-FR`&#124;`pt-BR`&#124;`it-IT`&#124;`tr-TR`&#124;`pl-PL`&#124;`zh-CN`&#124;`ja-JP`) | Язык интерфейса и озвучки: русский, английский, испанский, немецкий, французский, португальский (Бразилия), итальянский, турецкий, польский, китайский (упрощённый), японский. |
copyright | boolean | true | (`true`&#124;`false`) | Показывает ссылку на bvi.isvek.ru в окне настроек. Значение `false` её скрывает. |

### Методы

Метод | Описание
------ | -----------
`destroy()` | Убирает со страницы панель, обёртку и все обработчики. Сохранённые настройки (cookie) остаются, поэтому новый экземпляр восстанавливает прежнее состояние. Вызывайте при размонтировании компонента (React, Vue и др.).

### Клавиатура

* `Tab` / `Shift+Tab` — переход между элементами управления (все они — обычные кнопки `<button>`).
* `←` `→` `↑` `↓` — переход между кнопками панели, окна настроек или элементов озвучки (по кругу). `Home` / `End` — первая / последняя кнопка.
* `Space` / `Enter` — нажатие кнопки. `Esc` — закрыть окно настроек (фокус возвращается на кнопку, которая его открыла).

### История изменений

#### 2.0.0

**Несовместимые изменения**

* Internet Explorer и другие устаревшие браузеры больше не поддерживаются. Babel нацелен на `defaults and supports es6-module, not dead`; полифилы, `core-js` и `regenerator-runtime` удалены. `bvi.min.js` весит около 62 КБ (16,6 КБ в gzip) вместе с 11 языками.
* Элементы управления панели — обычные кнопки `<button type="button">` вместо `<a href="#" role="button">`. Свои стили для `a.bvi-link` нужно перенести на `.bvi-link`.
* Иконки — Font Awesome Free 7.3.1 (Solid), встроены в CSS (data URI) и рисуются через CSS `mask`, поэтому в каждой цветовой схеме принимают цвет текста кнопки. Папка `dist/img` больше не публикуется.
* Кнопки размера шрифта переименованы под остальные классы: `.bvi-fontSize-minus` / `.bvi-fontSize-plus` теперь `.bvi-font-size-minus` / `.bvi-font-size-plus`. Обновите свои стили и скрипты, которые к ним обращаются.
* У панели белые кнопки с рамкой цвета текста и залитая активная кнопка вместо нейтральных серых заливок. Текст кнопок 16px вместо 14px, зазор между кнопками не меньше 8px. Панель и окно настроек следуют выбранной цветовой схеме («Цвета сайта»), поэтому тёмная схема больше не оставляет на странице яркую белую панель. Их цвета по умолчанию берутся из `--bvi-site-bg` и `--bvi-site-color`; Sass-переменные (`$panel-*`, `$link-*`, новые `$panel-accent`, `$panel-accent-strong` и `$link-active-border-color`) по-прежнему их переопределяют.
* Sass-переменные `$breakpoint-*` теперь означают минимальную ширину viewport (mobile-first), а не максимальную. Ниже `$breakpoint-desktop` (78rem) группы настроек свёрнуты за кнопкой «Меню».
* Все размеры заданы в `rem`, а не в `px`. Размер шрифта страницы отсчитывается от корневого размера (`html.bvi-active { font-size: 100% }`), поэтому учитывает настройку размера шрифта в браузере.

**Добавлено**

* Метод `destroy()`, сборки ES-модуля (`import`) и CommonJS (`require`), карта `exports`, типы TypeScript, `import "bvi/style"`, безопасный для SSR импорт. Документация по использованию с React, Next.js, Vue и Sass.
* Подписи вместо скрытых изображений: текст `alt` (`Изображение: ...`) или «Изображение без описания». Декоративные изображения (`alt=""`) остаются без подписи.
* Доступность: разметка `role="region"`, `role="group"`, `aria-label`, `aria-pressed` и `role="dialog"`; заметное кольцо фокуса; элементы управления не меньше 44×44 px; контраст рамок 3:1; читаемые недоступные кнопки озвучки; хорошо заметное выбранное состояние (заливка цветом текста, светлое внутреннее кольцо, инвертированная иконка), отличающееся от наведения; закреплённая панель не закрывает цель перехода по якорю (`scroll-padding-top`); ловушка фокуса, `Esc` и возврат фокуса в окне настроек; навигация стрелками, `Home` и `End`.
* Языки интерфейса и озвучки: русский, английский, испанский, немецкий, французский, португальский (Бразилия), итальянский, турецкий, польский, китайский (упрощённый) и японский (опция `lang`). Переводы — JSON-файлы в `src/js/i18n/locales/`.
* Опция `copyright`: значение `false` скрывает ссылку на bvi.isvek.ru в окне настроек. Ссылка теперь сообщает, что открывается в новой вкладке.
* На телефонах и планшетах (ниже `$breakpoint-desktop`) группы настроек свёрнуты за кнопкой «Меню», которая плавно раскрывается и учитывает `prefers-reduced-motion`.
* Sass-переменные разделены на `variables/`, `mixins/` и `functions/` и переопределяются через `@use ... with (...)`.
* Тестовая страница с готовыми сценариями и со всеми видами элементов форм и страницы (`test/index.html`), а также браузерные E2E-тесты в Chromium, Firefox и WebKit (`e2e/`); плоская конфигурация ESLint.

**Исправлено**

* Озвучка: цепочка «Воспроизвести → Пауза → Продолжить» больше не сбрасывает состояние и не перестаёт работать (таймер статуса, `cancel()` после `pause()` и события отменённых фраз).
* CSS плагина менял `html { font-size }` и `box-sizing` на каждой странице, даже когда плагин был выключен.
* Межстрочный и межбуквенный интервал и шрифт теперь доходят до элементов, у которых заданы собственные значения (заголовки и другие).
* Элементы с фоновым изображением скрывались целиком; теперь убирается только изображение.
* Панель «прыгала», когда закреплялась при прокрутке (содержимое под ней сдвигалось вверх); теперь она сохраняет своё место и плавно выезжает (без анимации при `prefers-reduced-motion`).
* Панель использовала шрифт с засечками; заголовок окна настроек был слишком мелким; цвета выделения текста не применялись в цветовых схемах; удалены сломанные и мёртвые CSS-правила.
* `bvi.min.css` собирался из предыдущего `bvi.css`; в `package.json` `files` пропускал вложенные папки.
* Панель не открывалась в браузерах, где у `speechSynthesis` нет `addEventListener` (старый Safari); повторная инициализация больше не дублирует обработчики.
* Опечатки и непереведённые строки в английских текстах.

#### 1.0.0

* создана новая версия на JavaScript

### Лицензия

[MIT License](https://github.com/veks/button-visually-impaired-javascript/blob/master/LICENSE.md)
