vision-accessibility 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +225 -0
- package/dist/components/AccessibilityPanel/AccessibilityPanel.d.ts +12 -0
- package/dist/components/AccessibilityPanel/AccessibilityPanel.d.ts.map +1 -0
- package/dist/components/AccessibilityToggle/AccessibilityToggle.d.ts +18 -0
- package/dist/components/AccessibilityToggle/AccessibilityToggle.d.ts.map +1 -0
- package/dist/components/ColorSchemeControl/ColorSchemeControl.d.ts +18 -0
- package/dist/components/ColorSchemeControl/ColorSchemeControl.d.ts.map +1 -0
- package/dist/components/FontSizeControl/FontSizeControl.d.ts +18 -0
- package/dist/components/FontSizeControl/FontSizeControl.d.ts.map +1 -0
- package/dist/components/ImageControl/ImageControl.d.ts +18 -0
- package/dist/components/ImageControl/ImageControl.d.ts.map +1 -0
- package/dist/context/AccessibilityContext.d.ts +25 -0
- package/dist/context/AccessibilityContext.d.ts.map +1 -0
- package/dist/hooks/useAccessibilitySettings.d.ts +25 -0
- package/dist/hooks/useAccessibilitySettings.d.ts.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +1151 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +11 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/lib/accessibility-storage.d.ts +23 -0
- package/dist/lib/accessibility-storage.d.ts.map +1 -0
- package/dist/lib/css-applier.d.ts +50 -0
- package/dist/lib/css-applier.d.ts.map +1 -0
- package/dist/lib/dom-manipulator.d.ts +29 -0
- package/dist/lib/dom-manipulator.d.ts.map +1 -0
- package/dist/lib/font-size-manager.d.ts +26 -0
- package/dist/lib/font-size-manager.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +58 -0
- package/src/components/AccessibilityPanel/AccessibilityPanel.module.scss +214 -0
- package/src/components/AccessibilityPanel/AccessibilityPanel.tsx +151 -0
- package/src/components/AccessibilityToggle/AccessibilityToggle.module.scss +158 -0
- package/src/components/AccessibilityToggle/AccessibilityToggle.tsx +85 -0
- package/src/components/ColorSchemeControl/ColorSchemeControl.module.scss +175 -0
- package/src/components/ColorSchemeControl/ColorSchemeControl.tsx +116 -0
- package/src/components/FontSizeControl/FontSizeControl.module.scss +175 -0
- package/src/components/FontSizeControl/FontSizeControl.tsx +116 -0
- package/src/components/ImageControl/ImageControl.module.scss +163 -0
- package/src/components/ImageControl/ImageControl.tsx +85 -0
- package/src/context/AccessibilityContext.tsx +54 -0
- package/src/hooks/useAccessibilitySettings.ts +228 -0
- package/src/index.ts +80 -0
- package/src/lib/accessibility-storage.ts +82 -0
- package/src/lib/css-applier.ts +168 -0
- package/src/lib/dom-manipulator.ts +75 -0
- package/src/lib/font-size-manager.ts +185 -0
- package/src/styles/global.scss +60 -0
- package/src/styles/variables.scss +80 -0
- package/src/types/index.ts +72 -0
- package/src/types/scss.d.ts +9 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Компонент управления размером шрифта
|
|
3
|
+
* Предоставляет радио-кнопки для выбора размера шрифта
|
|
4
|
+
*
|
|
5
|
+
* Реализует требования:
|
|
6
|
+
* - 3.1: Уменьшение размера шрифта на 10%
|
|
7
|
+
* - 3.2: Увеличение размера шрифта на 10%
|
|
8
|
+
* - 3.3: Возврат к стандартному размеру шрифта
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { FontSize } from '../../types';
|
|
13
|
+
import { useAccessibilityContext } from '../../context/AccessibilityContext';
|
|
14
|
+
import styles from './FontSizeControl.module.scss';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Свойства компонента FontSizeControl
|
|
18
|
+
*/
|
|
19
|
+
interface FontSizeControlProps {
|
|
20
|
+
/** CSS класс для дополнительной стилизации */
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Опции размеров шрифта с описаниями
|
|
26
|
+
*/
|
|
27
|
+
const FONT_SIZE_OPTIONS: Array<{
|
|
28
|
+
value: FontSize;
|
|
29
|
+
label: string;
|
|
30
|
+
description: string;
|
|
31
|
+
}> = [
|
|
32
|
+
{
|
|
33
|
+
value: 'small',
|
|
34
|
+
label: 'Мелкий',
|
|
35
|
+
description: 'Уменьшенный размер текста (80%)'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: 'standard',
|
|
39
|
+
label: 'Стандартный',
|
|
40
|
+
description: 'Обычный размер текста'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: 'large',
|
|
44
|
+
label: 'Крупный',
|
|
45
|
+
description: 'Увеличенный размер текста (130%)'
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Компонент управления размером шрифта
|
|
51
|
+
*
|
|
52
|
+
* @param props - Свойства компонента
|
|
53
|
+
* @returns JSX элемент с радио-кнопками для выбора размера шрифта
|
|
54
|
+
*/
|
|
55
|
+
export const FontSizeControl: React.FC<FontSizeControlProps> = ({
|
|
56
|
+
className
|
|
57
|
+
}) => {
|
|
58
|
+
// Используем контекст для получения состояния и методов управления
|
|
59
|
+
const { settings, updateSettings } = useAccessibilityContext();
|
|
60
|
+
const fontSize = settings.fontSize;
|
|
61
|
+
const isEnabled = settings.isEnabled;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Обработчик изменения размера шрифта
|
|
65
|
+
*
|
|
66
|
+
* @param event - Событие изменения радио-кнопки
|
|
67
|
+
*/
|
|
68
|
+
const handleFontSizeChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
69
|
+
const newFontSize = event.target.value as FontSize;
|
|
70
|
+
updateSettings({ fontSize: newFontSize });
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<div className={`${styles.fontSizeControl} ${className || ''} ${!isEnabled ? styles.disabled : ''}`}>
|
|
75
|
+
<h4 className={styles.title}>
|
|
76
|
+
Размер шрифта
|
|
77
|
+
</h4>
|
|
78
|
+
|
|
79
|
+
<div className={styles.optionsContainer} role="radiogroup" aria-labelledby="font-size-title">
|
|
80
|
+
{FONT_SIZE_OPTIONS.map((option) => (
|
|
81
|
+
<label
|
|
82
|
+
key={option.value}
|
|
83
|
+
className={`${styles.option} ${fontSize === option.value ? styles.selected : ''} ${!isEnabled ? styles.optionDisabled : ''}`}
|
|
84
|
+
>
|
|
85
|
+
<input
|
|
86
|
+
type="radio"
|
|
87
|
+
name="fontSize"
|
|
88
|
+
value={option.value}
|
|
89
|
+
checked={fontSize === option.value}
|
|
90
|
+
onChange={handleFontSizeChange}
|
|
91
|
+
className={styles.radioInput}
|
|
92
|
+
aria-describedby={`font-size-${option.value}-description`}
|
|
93
|
+
disabled={!isEnabled}
|
|
94
|
+
/>
|
|
95
|
+
|
|
96
|
+
<div className={styles.optionContent}>
|
|
97
|
+
<span className={styles.optionLabel}>
|
|
98
|
+
{option.label}
|
|
99
|
+
</span>
|
|
100
|
+
<span
|
|
101
|
+
id={`font-size-${option.value}-description`}
|
|
102
|
+
className={styles.optionDescription}
|
|
103
|
+
>
|
|
104
|
+
{option.description}
|
|
105
|
+
</span>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div className={styles.radioIndicator} aria-hidden="true" />
|
|
109
|
+
</label>
|
|
110
|
+
))}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default FontSizeControl;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Стили для компонента управления отображением изображений
|
|
3
|
+
* Реализует доступный переключатель для управления видимостью изображений
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.imageControl {
|
|
7
|
+
margin-bottom: $spacing-lg;
|
|
8
|
+
|
|
9
|
+
&.disabled {
|
|
10
|
+
.title {
|
|
11
|
+
opacity: 0.6;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.title {
|
|
17
|
+
font-size: 14px;
|
|
18
|
+
font-weight: 600;
|
|
19
|
+
margin: 0 0 $spacing-md 0;
|
|
20
|
+
color: $gray-800;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.switchContainer {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: $spacing-sm;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.switchLabel {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: $spacing-md;
|
|
33
|
+
cursor: pointer;
|
|
34
|
+
padding: $spacing-md;
|
|
35
|
+
border: 1px solid $gray-300;
|
|
36
|
+
border-radius: $border-radius;
|
|
37
|
+
transition: all $transition-fast;
|
|
38
|
+
background: $white;
|
|
39
|
+
|
|
40
|
+
&:hover {
|
|
41
|
+
background: $gray-100;
|
|
42
|
+
border-color: $primary;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&:focus-within {
|
|
46
|
+
background: $gray-100;
|
|
47
|
+
border-color: $primary;
|
|
48
|
+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&.switchDisabled {
|
|
52
|
+
opacity: 0.5;
|
|
53
|
+
cursor: not-allowed;
|
|
54
|
+
pointer-events: none;
|
|
55
|
+
|
|
56
|
+
&:hover {
|
|
57
|
+
background: $white;
|
|
58
|
+
border-color: $gray-300;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.switchInput {
|
|
64
|
+
position: absolute;
|
|
65
|
+
opacity: 0;
|
|
66
|
+
width: 0;
|
|
67
|
+
height: 0;
|
|
68
|
+
margin: 0;
|
|
69
|
+
|
|
70
|
+
&:focus + .switchSlider {
|
|
71
|
+
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
&:checked + .switchSlider {
|
|
75
|
+
background: $primary;
|
|
76
|
+
|
|
77
|
+
.switchThumb {
|
|
78
|
+
transform: translateX(20px);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.switchSlider {
|
|
84
|
+
position: relative;
|
|
85
|
+
width: 44px;
|
|
86
|
+
height: 24px;
|
|
87
|
+
background: $gray-400;
|
|
88
|
+
border-radius: 12px;
|
|
89
|
+
transition: all $transition-fast;
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.switchThumb {
|
|
94
|
+
position: absolute;
|
|
95
|
+
top: 2px;
|
|
96
|
+
left: 2px;
|
|
97
|
+
width: 20px;
|
|
98
|
+
height: 20px;
|
|
99
|
+
background: $white;
|
|
100
|
+
border-radius: 50%;
|
|
101
|
+
transition: transform $transition-fast;
|
|
102
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.switchText {
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
color: $gray-800;
|
|
109
|
+
line-height: 1.4;
|
|
110
|
+
flex: 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.description {
|
|
114
|
+
font-size: 12px;
|
|
115
|
+
color: $gray-600;
|
|
116
|
+
line-height: 1.3;
|
|
117
|
+
margin: 0;
|
|
118
|
+
padding-left: 56px; // Выравнивание с текстом переключателя
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Высококонтрастная тема - адаптация стилей
|
|
122
|
+
:global(.accessibility-high-contrast) .imageControl {
|
|
123
|
+
.title {
|
|
124
|
+
color: $white !important;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.switchLabel {
|
|
128
|
+
background: $black !important;
|
|
129
|
+
border-color: $white !important;
|
|
130
|
+
color: $white !important;
|
|
131
|
+
|
|
132
|
+
&:hover,
|
|
133
|
+
&:focus-within {
|
|
134
|
+
background: $gray-800 !important;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.switchText {
|
|
139
|
+
color: $white !important;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.description {
|
|
143
|
+
color: $gray-400 !important;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.switchSlider {
|
|
147
|
+
background: $gray-600 !important;
|
|
148
|
+
border: 1px solid $white !important;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.switchInput:checked + .switchSlider {
|
|
152
|
+
background: $white !important;
|
|
153
|
+
|
|
154
|
+
.switchThumb {
|
|
155
|
+
background: $black !important;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.switchThumb {
|
|
160
|
+
background: $white !important;
|
|
161
|
+
border: 1px solid $black !important;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Компонент управления отображением изображений
|
|
3
|
+
* Предоставляет переключатель для скрытия/показа изображений
|
|
4
|
+
*
|
|
5
|
+
* Реализует требования:
|
|
6
|
+
* - 4.1: Скрытие изображений через CSS свойство hidden
|
|
7
|
+
* - 4.2: Восстановление видимости изображений
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { useAccessibilityContext } from '../../context/AccessibilityContext';
|
|
12
|
+
import styles from './ImageControl.module.scss';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Свойства компонента ImageControl
|
|
16
|
+
*/
|
|
17
|
+
interface ImageControlProps {
|
|
18
|
+
/** CSS класс для дополнительной стилизации */
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Компонент управления отображением изображений
|
|
24
|
+
*
|
|
25
|
+
* @param props - Свойства компонента
|
|
26
|
+
* @returns JSX элемент с переключателем для управления изображениями
|
|
27
|
+
*/
|
|
28
|
+
export const ImageControl: React.FC<ImageControlProps> = ({
|
|
29
|
+
className
|
|
30
|
+
}) => {
|
|
31
|
+
// Используем контекст для получения состояния и методов управления
|
|
32
|
+
const { settings, updateSettings } = useAccessibilityContext();
|
|
33
|
+
const showImages = settings.showImages;
|
|
34
|
+
const isEnabled = settings.isEnabled;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Обработчик изменения настройки отображения изображений
|
|
38
|
+
*
|
|
39
|
+
* @param event - Событие изменения чекбокса
|
|
40
|
+
*/
|
|
41
|
+
const handleImageVisibilityChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
42
|
+
updateSettings({ showImages: event.target.checked });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className={`${styles.imageControl} ${className || ''} ${!isEnabled ? styles.disabled : ''}`}>
|
|
47
|
+
<h4 className={styles.title}>
|
|
48
|
+
Изображения
|
|
49
|
+
</h4>
|
|
50
|
+
|
|
51
|
+
<div className={styles.switchContainer}>
|
|
52
|
+
<label className={`${styles.switchLabel} ${!isEnabled ? styles.switchDisabled : ''}`}>
|
|
53
|
+
<input
|
|
54
|
+
type="checkbox"
|
|
55
|
+
checked={showImages}
|
|
56
|
+
onChange={handleImageVisibilityChange}
|
|
57
|
+
className={styles.switchInput}
|
|
58
|
+
aria-describedby="image-control-description"
|
|
59
|
+
disabled={!isEnabled}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<span className={styles.switchSlider} aria-hidden="true">
|
|
63
|
+
<span className={styles.switchThumb} />
|
|
64
|
+
</span>
|
|
65
|
+
|
|
66
|
+
<span className={styles.switchText}>
|
|
67
|
+
{showImages ? 'Показывать изображения' : 'Скрывать изображения'}
|
|
68
|
+
</span>
|
|
69
|
+
</label>
|
|
70
|
+
|
|
71
|
+
<p
|
|
72
|
+
id="image-control-description"
|
|
73
|
+
className={styles.description}
|
|
74
|
+
>
|
|
75
|
+
{showImages
|
|
76
|
+
? 'Изображения отображаются на странице'
|
|
77
|
+
: 'Изображения скрыты для лучшей концентрации на тексте'
|
|
78
|
+
}
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export default ImageControl;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Контекст для управления настройками доступности
|
|
3
|
+
* Обеспечивает единое состояние для всех компонентов панели доступности
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { createContext, useContext, ReactNode } from 'react';
|
|
7
|
+
import { AccessibilityContextType } from '../types';
|
|
8
|
+
import { useAccessibilitySettings } from '../hooks/useAccessibilitySettings';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Контекст настроек доступности
|
|
12
|
+
*/
|
|
13
|
+
const AccessibilityContext = createContext<AccessibilityContextType | null>(null);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Свойства провайдера контекста
|
|
17
|
+
*/
|
|
18
|
+
interface AccessibilityProviderProps {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Провайдер контекста настроек доступности
|
|
24
|
+
*
|
|
25
|
+
* @param props - Свойства провайдера
|
|
26
|
+
* @returns JSX элемент с провайдером контекста
|
|
27
|
+
*/
|
|
28
|
+
export const AccessibilityProvider: React.FC<AccessibilityProviderProps> = ({ children }) => {
|
|
29
|
+
const accessibilityState = useAccessibilitySettings();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<AccessibilityContext.Provider value={accessibilityState}>
|
|
33
|
+
{children}
|
|
34
|
+
</AccessibilityContext.Provider>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Хук для использования контекста настроек доступности
|
|
40
|
+
*
|
|
41
|
+
* @returns Контекст настроек доступности
|
|
42
|
+
* @throws Ошибка, если хук используется вне провайдера
|
|
43
|
+
*/
|
|
44
|
+
export const useAccessibilityContext = (): AccessibilityContextType => {
|
|
45
|
+
const context = useContext(AccessibilityContext);
|
|
46
|
+
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error('useAccessibilityContext должен использоваться внутри AccessibilityProvider');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return context;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default AccessibilityProvider;
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Хук для управления настройками доступности
|
|
3
|
+
* Обеспечивает централизованное управление состоянием настроек доступности
|
|
4
|
+
* с автоматическим сохранением в localStorage и применением к DOM
|
|
5
|
+
*
|
|
6
|
+
* Реализует требования:
|
|
7
|
+
* - 5.1: Включение режима доступности с активацией всех сохраненных настроек
|
|
8
|
+
* - 5.2: Выключение режима доступности с удалением всех CSS классов и стилей
|
|
9
|
+
* - 6.2: Загрузка настроек из localStorage при инициализации
|
|
10
|
+
* - 6.3: Автоматическое применение сохраненных настроек при загрузке страницы
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
14
|
+
import {
|
|
15
|
+
AccessibilitySettings,
|
|
16
|
+
DEFAULT_ACCESSIBILITY_SETTINGS,
|
|
17
|
+
AccessibilityContextType
|
|
18
|
+
} from '../types';
|
|
19
|
+
import {
|
|
20
|
+
saveToStorage,
|
|
21
|
+
loadFromStorage,
|
|
22
|
+
clearStorage
|
|
23
|
+
} from '../lib/accessibility-storage';
|
|
24
|
+
import {
|
|
25
|
+
applyAllSettings,
|
|
26
|
+
removeAllAccessibilityStyles
|
|
27
|
+
} from '../lib/css-applier';
|
|
28
|
+
import { updateNewElementsFontSize } from '../lib/font-size-manager';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Хук для управления настройками доступности
|
|
32
|
+
*
|
|
33
|
+
* @returns Объект с текущими настройками и методами для их управления
|
|
34
|
+
*/
|
|
35
|
+
export const useAccessibilitySettings = (): AccessibilityContextType => {
|
|
36
|
+
// Состояние настроек доступности
|
|
37
|
+
const [settings, setSettings] = useState<AccessibilitySettings>(DEFAULT_ACCESSIBILITY_SETTINGS);
|
|
38
|
+
|
|
39
|
+
// Флаг загрузки настроек из localStorage (для SSR совместимости)
|
|
40
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Наблюдатель за изменениями DOM для обработки динамически добавляемых элементов
|
|
44
|
+
*/
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (typeof window === 'undefined' || !isLoaded || !settings.isEnabled) return;
|
|
47
|
+
|
|
48
|
+
const observer = new MutationObserver((mutations) => {
|
|
49
|
+
let hasNewTextElements = false;
|
|
50
|
+
|
|
51
|
+
mutations.forEach((mutation) => {
|
|
52
|
+
if (mutation.type === 'childList') {
|
|
53
|
+
mutation.addedNodes.forEach((node) => {
|
|
54
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
55
|
+
const element = node as Element;
|
|
56
|
+
// Проверяем, есть ли текстовые элементы среди добавленных
|
|
57
|
+
if (element.matches('h1, h2, h3, h4, h5, h6, p, span, a, button, label, li, div') ||
|
|
58
|
+
element.querySelector('h1, h2, h3, h4, h5, h6, p, span, a, button, label, li, div')) {
|
|
59
|
+
hasNewTextElements = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Если добавлены новые текстовые элементы, применяем к ним текущие настройки размера шрифта
|
|
67
|
+
if (hasNewTextElements && settings.fontSize !== 'standard') {
|
|
68
|
+
setTimeout(() => {
|
|
69
|
+
updateNewElementsFontSize(settings.fontSize);
|
|
70
|
+
}, 100); // Небольшая задержка для завершения рендеринга
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
observer.observe(document.body, {
|
|
75
|
+
childList: true,
|
|
76
|
+
subtree: true
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
observer.disconnect();
|
|
81
|
+
};
|
|
82
|
+
}, [isLoaded, settings.isEnabled, settings.fontSize]);
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Загрузка настроек из localStorage при инициализации
|
|
86
|
+
* Реализует требования 6.2, 6.3: Восстановление сохраненных настроек
|
|
87
|
+
* Обеспечивает SSR совместимость с проверкой window
|
|
88
|
+
*/
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
// SSR совместимость: выполняем только на клиенте
|
|
91
|
+
if (typeof window === 'undefined') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
// Требование 6.2: Загрузка настроек из localStorage
|
|
97
|
+
const savedSettings = loadFromStorage();
|
|
98
|
+
|
|
99
|
+
if (savedSettings) {
|
|
100
|
+
setSettings(savedSettings);
|
|
101
|
+
|
|
102
|
+
// Требование 6.3: Автоматическое применение сохраненных настроек
|
|
103
|
+
// Применяем настройки только если режим доступности включен
|
|
104
|
+
if (savedSettings.isEnabled) {
|
|
105
|
+
applyAllSettings(savedSettings);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.warn('Ошибка при загрузке настроек доступности:', error);
|
|
110
|
+
// При ошибке используем настройки по умолчанию
|
|
111
|
+
setSettings(DEFAULT_ACCESSIBILITY_SETTINGS);
|
|
112
|
+
} finally {
|
|
113
|
+
// Отмечаем, что настройки загружены
|
|
114
|
+
setIsLoaded(true);
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Сохранение настроек в localStorage при изменении
|
|
120
|
+
* Реализует автоматическое сохранение всех изменений настроек
|
|
121
|
+
*/
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
// Сохраняем только после первоначальной загрузки
|
|
124
|
+
if (!isLoaded) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
// Сохраняем настройки в localStorage
|
|
130
|
+
saveToStorage(settings);
|
|
131
|
+
|
|
132
|
+
// Применяем настройки к DOM только если режим доступности включен
|
|
133
|
+
if (settings.isEnabled) {
|
|
134
|
+
applyAllSettings(settings);
|
|
135
|
+
} else {
|
|
136
|
+
// Если режим выключен, удаляем все стили доступности
|
|
137
|
+
removeAllAccessibilityStyles();
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.warn('Ошибка при сохранении настроек доступности:', error);
|
|
141
|
+
}
|
|
142
|
+
}, [settings, isLoaded]);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Обновление настроек доступности
|
|
146
|
+
* Позволяет частично обновить настройки, сохраняя остальные значения
|
|
147
|
+
*
|
|
148
|
+
* @param updates - Частичные обновления настроек
|
|
149
|
+
*/
|
|
150
|
+
const updateSettings = useCallback((updates: Partial<AccessibilitySettings>): void => {
|
|
151
|
+
setSettings(prevSettings => {
|
|
152
|
+
const newSettings = { ...prevSettings, ...updates };
|
|
153
|
+
|
|
154
|
+
// Если выключается общий режим доступности, удаляем все стили немедленно
|
|
155
|
+
if (prevSettings.isEnabled && !newSettings.isEnabled) {
|
|
156
|
+
// Требование 5.2: Выключение режима доступности с удалением всех стилей
|
|
157
|
+
removeAllAccessibilityStyles();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return newSettings;
|
|
161
|
+
});
|
|
162
|
+
}, []);
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Сброс настроек к значениям по умолчанию
|
|
166
|
+
* Удаляет все примененные стили и очищает localStorage
|
|
167
|
+
*/
|
|
168
|
+
const resetSettings = useCallback((): void => {
|
|
169
|
+
try {
|
|
170
|
+
// Удаляем все примененные стили доступности
|
|
171
|
+
removeAllAccessibilityStyles();
|
|
172
|
+
|
|
173
|
+
// Сбрасываем настройки к значениям по умолчанию
|
|
174
|
+
setSettings(DEFAULT_ACCESSIBILITY_SETTINGS);
|
|
175
|
+
|
|
176
|
+
// Очищаем localStorage
|
|
177
|
+
clearStorage();
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.warn('Ошибка при сбросе настроек доступности:', error);
|
|
180
|
+
}
|
|
181
|
+
}, []);
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
settings,
|
|
185
|
+
updateSettings,
|
|
186
|
+
resetSettings,
|
|
187
|
+
isLoaded,
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Хук для управления конкретной настройкой доступности
|
|
193
|
+
* Предоставляет удобный интерфейс для работы с отдельными настройками
|
|
194
|
+
*
|
|
195
|
+
* @param settingKey - Ключ настройки для управления
|
|
196
|
+
* @returns Значение настройки и функция для её обновления
|
|
197
|
+
*/
|
|
198
|
+
export const useAccessibilitySetting = <K extends keyof AccessibilitySettings>(
|
|
199
|
+
settingKey: K
|
|
200
|
+
): [AccessibilitySettings[K], (value: AccessibilitySettings[K]) => void] => {
|
|
201
|
+
const { settings, updateSettings } = useAccessibilitySettings();
|
|
202
|
+
|
|
203
|
+
const setValue = useCallback((value: AccessibilitySettings[K]) => {
|
|
204
|
+
updateSettings({ [settingKey]: value } as Partial<AccessibilitySettings>);
|
|
205
|
+
}, [settingKey, updateSettings]);
|
|
206
|
+
|
|
207
|
+
return [settings[settingKey], setValue];
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Хук для управления общим режимом доступности
|
|
212
|
+
* Предоставляет удобный интерфейс для включения/выключения всех настроек
|
|
213
|
+
*
|
|
214
|
+
* @returns Состояние режима доступности и функция для его переключения
|
|
215
|
+
*/
|
|
216
|
+
export const useAccessibilityToggle = (): [boolean, (enabled: boolean) => void] => {
|
|
217
|
+
const { settings, updateSettings } = useAccessibilitySettings();
|
|
218
|
+
|
|
219
|
+
const setEnabled = useCallback((enabled: boolean) => {
|
|
220
|
+
// Требование 5.1: Включение режима доступности с активацией всех настроек
|
|
221
|
+
// Требование 5.2: Выключение режима доступности с удалением всех стилей
|
|
222
|
+
updateSettings({ isEnabled: enabled });
|
|
223
|
+
}, [updateSettings]);
|
|
224
|
+
|
|
225
|
+
return [settings.isEnabled, setEnabled];
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
export default useAccessibilitySettings;
|