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
package/src/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Экспорт компонентов панели доступности
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Основные типы
|
|
6
|
+
export type {
|
|
7
|
+
AccessibilitySettings,
|
|
8
|
+
ColorScheme,
|
|
9
|
+
FontSize,
|
|
10
|
+
AccessibilityPanelProps,
|
|
11
|
+
ControlComponentProps,
|
|
12
|
+
AccessibilityContextType
|
|
13
|
+
} from './types';
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
DEFAULT_ACCESSIBILITY_SETTINGS,
|
|
17
|
+
STORAGE_KEY
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
// Утилиты для работы с localStorage
|
|
21
|
+
export {
|
|
22
|
+
saveToStorage,
|
|
23
|
+
loadFromStorage,
|
|
24
|
+
clearStorage,
|
|
25
|
+
validateSettings,
|
|
26
|
+
safeLocalStorageOperation
|
|
27
|
+
} from './lib/accessibility-storage';
|
|
28
|
+
|
|
29
|
+
// Утилиты для работы с DOM
|
|
30
|
+
export {
|
|
31
|
+
getTextElements,
|
|
32
|
+
getImageElements,
|
|
33
|
+
applyClassToElements,
|
|
34
|
+
removeClassFromElements,
|
|
35
|
+
safeDOMOperation,
|
|
36
|
+
isDOMAvailable
|
|
37
|
+
} from './lib/dom-manipulator';
|
|
38
|
+
|
|
39
|
+
// Утилиты для применения CSS стилей
|
|
40
|
+
export {
|
|
41
|
+
applyColorScheme,
|
|
42
|
+
applyFontSize,
|
|
43
|
+
applyImageVisibility,
|
|
44
|
+
removeAllAccessibilityStyles,
|
|
45
|
+
applyAllSettings,
|
|
46
|
+
applySettingIfEnabled,
|
|
47
|
+
CSS_CLASSES
|
|
48
|
+
} from './lib/css-applier';
|
|
49
|
+
|
|
50
|
+
// Утилиты для динамического управления размером шрифта
|
|
51
|
+
export {
|
|
52
|
+
applyDynamicFontSize,
|
|
53
|
+
resetAllFontSizes,
|
|
54
|
+
updateNewElementsFontSize,
|
|
55
|
+
isFontSizeModified
|
|
56
|
+
} from './lib/font-size-manager';
|
|
57
|
+
|
|
58
|
+
// Хуки для управления настройками доступности
|
|
59
|
+
export {
|
|
60
|
+
useAccessibilitySettings,
|
|
61
|
+
useAccessibilitySetting,
|
|
62
|
+
useAccessibilityToggle
|
|
63
|
+
} from './hooks/useAccessibilitySettings';
|
|
64
|
+
|
|
65
|
+
// Контекст для управления состоянием
|
|
66
|
+
export {
|
|
67
|
+
AccessibilityProvider,
|
|
68
|
+
useAccessibilityContext
|
|
69
|
+
} from './context/AccessibilityContext';
|
|
70
|
+
|
|
71
|
+
// UI компоненты
|
|
72
|
+
export { AccessibilityPanel } from './components/AccessibilityPanel/AccessibilityPanel';
|
|
73
|
+
export { AccessibilityToggle } from './components/AccessibilityToggle/AccessibilityToggle';
|
|
74
|
+
export { ColorSchemeControl } from './components/ColorSchemeControl/ColorSchemeControl';
|
|
75
|
+
export { FontSizeControl } from './components/FontSizeControl/FontSizeControl';
|
|
76
|
+
export { ImageControl } from './components/ImageControl/ImageControl';
|
|
77
|
+
|
|
78
|
+
// Стили (для импорта в основное приложение)
|
|
79
|
+
// Пользователи должны импортировать стили напрямую:
|
|
80
|
+
// import 'vision-accessibility/dist/style.css';
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Модуль для работы с localStorage
|
|
3
|
+
* Обеспечивает сохранение, загрузку и валидацию настроек доступности
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AccessibilitySettings, DEFAULT_ACCESSIBILITY_SETTINGS, STORAGE_KEY } from '../types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Сохранение настроек в localStorage
|
|
10
|
+
*/
|
|
11
|
+
export const saveToStorage = (settings: AccessibilitySettings): void => {
|
|
12
|
+
try {
|
|
13
|
+
if (typeof window === 'undefined') return;
|
|
14
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.warn('Не удалось сохранить настройки доступности:', error);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Загрузка настроек из localStorage
|
|
22
|
+
*/
|
|
23
|
+
export const loadFromStorage = (): AccessibilitySettings | null => {
|
|
24
|
+
try {
|
|
25
|
+
if (typeof window === 'undefined') return null;
|
|
26
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
27
|
+
return saved ? validateSettings(JSON.parse(saved)) : null;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.warn('Не удалось загрузить настройки доступности:', error);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Очистка настроек из localStorage
|
|
36
|
+
*/
|
|
37
|
+
export const clearStorage = (): void => {
|
|
38
|
+
try {
|
|
39
|
+
if (typeof window === 'undefined') return;
|
|
40
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.warn('Не удалось очистить настройки доступности:', error);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Валидация настроек из localStorage
|
|
48
|
+
*/
|
|
49
|
+
export const validateSettings = (settings: unknown): AccessibilitySettings => {
|
|
50
|
+
if (!settings || typeof settings !== 'object') {
|
|
51
|
+
return DEFAULT_ACCESSIBILITY_SETTINGS;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const s = settings as Partial<AccessibilitySettings>;
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
isEnabled: typeof s.isEnabled === 'boolean' ? s.isEnabled : DEFAULT_ACCESSIBILITY_SETTINGS.isEnabled,
|
|
58
|
+
colorScheme: ['standard', 'high-contrast', 'grayscale'].includes(s.colorScheme as string)
|
|
59
|
+
? s.colorScheme as AccessibilitySettings['colorScheme']
|
|
60
|
+
: DEFAULT_ACCESSIBILITY_SETTINGS.colorScheme,
|
|
61
|
+
fontSize: ['small', 'standard', 'large'].includes(s.fontSize as string)
|
|
62
|
+
? s.fontSize as AccessibilitySettings['fontSize']
|
|
63
|
+
: DEFAULT_ACCESSIBILITY_SETTINGS.fontSize,
|
|
64
|
+
showImages: typeof s.showImages === 'boolean' ? s.showImages : DEFAULT_ACCESSIBILITY_SETTINGS.showImages
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Безопасное выполнение операций с localStorage
|
|
70
|
+
*/
|
|
71
|
+
export const safeLocalStorageOperation = <T>(
|
|
72
|
+
operation: () => T,
|
|
73
|
+
fallback: T,
|
|
74
|
+
errorMessage: string
|
|
75
|
+
): T => {
|
|
76
|
+
try {
|
|
77
|
+
return operation();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.warn(errorMessage, error);
|
|
80
|
+
return fallback;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Модуль для применения CSS стилей доступности
|
|
3
|
+
* Содержит функции для применения различных настроек доступности к DOM
|
|
4
|
+
*
|
|
5
|
+
* Реализует требования:
|
|
6
|
+
* - 2.1, 2.2, 2.3: Настройка цветовой схемы
|
|
7
|
+
* - 3.1, 3.2, 3.3: Настройка размера шрифта
|
|
8
|
+
* - 4.1, 4.2: Управление отображением изображений
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { ColorScheme, FontSize, AccessibilitySettings } from '../types';
|
|
12
|
+
import {
|
|
13
|
+
getImageElements,
|
|
14
|
+
applyClassToElements,
|
|
15
|
+
removeClassFromElements,
|
|
16
|
+
safeDOMOperation,
|
|
17
|
+
isDOMAvailable
|
|
18
|
+
} from './dom-manipulator';
|
|
19
|
+
import {
|
|
20
|
+
applyDynamicFontSize,
|
|
21
|
+
resetAllFontSizes
|
|
22
|
+
} from './font-size-manager';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* CSS классы для различных настроек доступности
|
|
26
|
+
*/
|
|
27
|
+
export const CSS_CLASSES = {
|
|
28
|
+
HIGH_CONTRAST: 'accessibility-high-contrast',
|
|
29
|
+
FONT_SMALL: 'accessibility-font-small',
|
|
30
|
+
FONT_LARGE: 'accessibility-font-large',
|
|
31
|
+
HIDE_IMAGES: 'accessibility-hide-images',
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Применение цветовой схемы
|
|
36
|
+
* Реализует требования 2.1, 2.2, 2.3:
|
|
37
|
+
* - high-contrast: Контрастный белый на черном
|
|
38
|
+
* - grayscale: Черно-белый режим
|
|
39
|
+
* - standard: Стандартное оформление
|
|
40
|
+
*/
|
|
41
|
+
export const applyColorScheme = (scheme: ColorScheme): void => {
|
|
42
|
+
safeDOMOperation(() => {
|
|
43
|
+
if (!isDOMAvailable()) return;
|
|
44
|
+
|
|
45
|
+
// Удаляем существующие классы и стили
|
|
46
|
+
document.body.classList.remove(CSS_CLASSES.HIGH_CONTRAST);
|
|
47
|
+
document.body.style.filter = '';
|
|
48
|
+
|
|
49
|
+
switch (scheme) {
|
|
50
|
+
case 'high-contrast':
|
|
51
|
+
// Требование 2.1: Контрастный белый на черном
|
|
52
|
+
document.body.classList.add(CSS_CLASSES.HIGH_CONTRAST);
|
|
53
|
+
break;
|
|
54
|
+
case 'grayscale':
|
|
55
|
+
// Требование 2.2: Черно-белый режим через CSS фильтр
|
|
56
|
+
document.body.style.filter = 'grayscale(100%)';
|
|
57
|
+
break;
|
|
58
|
+
case 'standard':
|
|
59
|
+
// Требование 2.3: Стандартное оформление (стили уже удалены выше)
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
console.warn(`Неизвестная цветовая схема: ${scheme}`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}, `Ошибка при применении цветовой схемы: ${scheme}`);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Применение настроек размера шрифта
|
|
70
|
+
* Реализует требования 3.1, 3.2, 3.3:
|
|
71
|
+
* - small: Уменьшение размера на 10% от текущего размера каждого элемента
|
|
72
|
+
* - large: Увеличение размера на 10% от текущего размера каждого элемента
|
|
73
|
+
* - standard: Восстановление оригинального размера шрифта
|
|
74
|
+
*/
|
|
75
|
+
export const applyFontSize = (size: FontSize): void => {
|
|
76
|
+
// Используем новый модуль для динамического управления размером шрифта
|
|
77
|
+
applyDynamicFontSize(size);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Управление отображением изображений
|
|
82
|
+
* Реализует требования 4.1, 4.2:
|
|
83
|
+
* - false: Скрывать изображения через CSS свойство hidden
|
|
84
|
+
* - true: Показывать изображения (восстановление видимости)
|
|
85
|
+
* Требование 4.3: Сохраняет возможность восстановления без перезагрузки
|
|
86
|
+
*/
|
|
87
|
+
export const applyImageVisibility = (showImages: boolean): void => {
|
|
88
|
+
safeDOMOperation(() => {
|
|
89
|
+
if (!isDOMAvailable()) return;
|
|
90
|
+
|
|
91
|
+
const elements = getImageElements();
|
|
92
|
+
|
|
93
|
+
if (showImages) {
|
|
94
|
+
// Требование 4.2: Показывать изображения (восстановление видимости)
|
|
95
|
+
removeClassFromElements(elements, CSS_CLASSES.HIDE_IMAGES);
|
|
96
|
+
} else {
|
|
97
|
+
// Требование 4.1: Скрывать изображения через CSS класс
|
|
98
|
+
applyClassToElements(elements, CSS_CLASSES.HIDE_IMAGES);
|
|
99
|
+
}
|
|
100
|
+
}, `Ошибка при управлении отображением изображений: ${showImages}`);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Удаление всех CSS классов и стилей доступности
|
|
105
|
+
*/
|
|
106
|
+
export const removeAllAccessibilityStyles = (): void => {
|
|
107
|
+
safeDOMOperation(() => {
|
|
108
|
+
if (!isDOMAvailable()) return;
|
|
109
|
+
|
|
110
|
+
// Удаляем классы с body
|
|
111
|
+
document.body.classList.remove(CSS_CLASSES.HIGH_CONTRAST);
|
|
112
|
+
document.body.classList.remove(CSS_CLASSES.FONT_SMALL);
|
|
113
|
+
document.body.classList.remove(CSS_CLASSES.FONT_LARGE);
|
|
114
|
+
document.body.style.filter = '';
|
|
115
|
+
|
|
116
|
+
// Сбрасываем все изменения размера шрифта
|
|
117
|
+
resetAllFontSizes();
|
|
118
|
+
|
|
119
|
+
// Удаляем классы с изображений
|
|
120
|
+
const imageElements = getImageElements();
|
|
121
|
+
removeClassFromElements(imageElements, CSS_CLASSES.HIDE_IMAGES);
|
|
122
|
+
}, 'Ошибка при удалении всех стилей доступности');
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Применение всех настроек доступности
|
|
127
|
+
* Применяет все переданные настройки одновременно
|
|
128
|
+
*/
|
|
129
|
+
export const applyAllSettings = (settings: AccessibilitySettings): void => {
|
|
130
|
+
if (!settings.isEnabled) {
|
|
131
|
+
// Если режим доступности выключен, удаляем все стили
|
|
132
|
+
removeAllAccessibilityStyles();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Применяем все настройки
|
|
137
|
+
applyColorScheme(settings.colorScheme);
|
|
138
|
+
applyFontSize(settings.fontSize);
|
|
139
|
+
applyImageVisibility(settings.showImages);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Применение конкретной настройки с проверкой общего режима
|
|
144
|
+
* Применяет настройку только если общий режим доступности включен
|
|
145
|
+
*/
|
|
146
|
+
export const applySettingIfEnabled = (
|
|
147
|
+
isEnabled: boolean,
|
|
148
|
+
settingType: 'colorScheme' | 'fontSize' | 'imageVisibility',
|
|
149
|
+
value: ColorScheme | FontSize | boolean
|
|
150
|
+
): void => {
|
|
151
|
+
if (!isEnabled) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
switch (settingType) {
|
|
156
|
+
case 'colorScheme':
|
|
157
|
+
applyColorScheme(value as ColorScheme);
|
|
158
|
+
break;
|
|
159
|
+
case 'fontSize':
|
|
160
|
+
applyFontSize(value as FontSize);
|
|
161
|
+
break;
|
|
162
|
+
case 'imageVisibility':
|
|
163
|
+
applyImageVisibility(value as boolean);
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
console.warn(`Неизвестный тип настройки: ${settingType}`);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Модуль для работы с DOM элементами
|
|
3
|
+
* Предоставляет функции для поиска и манипуляции элементами страницы
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Селектор для поиска всех текстовых элементов
|
|
8
|
+
*/
|
|
9
|
+
const TEXT_ELEMENTS_SELECTOR = 'h1, h2, h3, h4, h5, h6, p, span, a, button, label, li';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Селектор для поиска всех изображений
|
|
13
|
+
*/
|
|
14
|
+
const IMAGE_ELEMENTS_SELECTOR = 'img, picture, svg[role="img"]';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Поиск всех текстовых элементов на странице
|
|
18
|
+
*/
|
|
19
|
+
export const getTextElements = (): NodeListOf<HTMLElement> => {
|
|
20
|
+
if (typeof document === 'undefined') {
|
|
21
|
+
return [] as unknown as NodeListOf<HTMLElement>;
|
|
22
|
+
}
|
|
23
|
+
return document.querySelectorAll(TEXT_ELEMENTS_SELECTOR);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Поиск всех изображений на странице
|
|
28
|
+
*/
|
|
29
|
+
export const getImageElements = (): NodeListOf<HTMLElement> => {
|
|
30
|
+
if (typeof document === 'undefined') {
|
|
31
|
+
return [] as unknown as NodeListOf<HTMLElement>;
|
|
32
|
+
}
|
|
33
|
+
return document.querySelectorAll(IMAGE_ELEMENTS_SELECTOR);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Применение CSS класса к коллекции элементов
|
|
38
|
+
*/
|
|
39
|
+
export const applyClassToElements = (elements: NodeListOf<HTMLElement>, className: string): void => {
|
|
40
|
+
elements.forEach(element => {
|
|
41
|
+
if (element && element.classList) {
|
|
42
|
+
element.classList.add(className);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Удаление CSS класса с коллекции элементов
|
|
49
|
+
*/
|
|
50
|
+
export const removeClassFromElements = (elements: NodeListOf<HTMLElement>, className: string): void => {
|
|
51
|
+
elements.forEach(element => {
|
|
52
|
+
if (element && element.classList) {
|
|
53
|
+
element.classList.remove(className);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Безопасное выполнение операций с DOM
|
|
60
|
+
*/
|
|
61
|
+
export const safeDOMOperation = (operation: () => void, errorMessage: string): void => {
|
|
62
|
+
try {
|
|
63
|
+
if (typeof window === 'undefined') return;
|
|
64
|
+
operation();
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn(errorMessage, error);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Проверка доступности DOM
|
|
72
|
+
*/
|
|
73
|
+
export const isDOMAvailable = (): boolean => {
|
|
74
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
75
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Модуль для динамического управления размером шрифта
|
|
3
|
+
* Получает текущий размер каждого элемента и применяет процентное изменение
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FontSize } from '../types';
|
|
7
|
+
import { getTextElements, safeDOMOperation, isDOMAvailable } from './dom-manipulator';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Атрибут для хранения оригинального размера шрифта
|
|
11
|
+
*/
|
|
12
|
+
const ORIGINAL_FONT_SIZE_ATTR = 'data-original-font-size';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Коэффициенты изменения размера шрифта
|
|
16
|
+
*/
|
|
17
|
+
const FONT_SIZE_MULTIPLIERS = {
|
|
18
|
+
small: 0.8, // 80% от оригинального размера
|
|
19
|
+
standard: 1.0, // 100% - оригинальный размер
|
|
20
|
+
large: 1.3, // 130% от оригинального размера
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Получение вычисленного размера шрифта элемента в пикселях
|
|
25
|
+
*
|
|
26
|
+
* @param element - DOM элемент
|
|
27
|
+
* @returns Размер шрифта в пикселях
|
|
28
|
+
*/
|
|
29
|
+
const getComputedFontSize = (element: HTMLElement): number => {
|
|
30
|
+
const computedStyle = window.getComputedStyle(element);
|
|
31
|
+
const fontSize = computedStyle.fontSize;
|
|
32
|
+
return parseFloat(fontSize);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Сохранение оригинального размера шрифта элемента
|
|
37
|
+
*
|
|
38
|
+
* @param element - DOM элемент
|
|
39
|
+
*/
|
|
40
|
+
const saveOriginalFontSize = (element: HTMLElement): void => {
|
|
41
|
+
// Сохраняем оригинальный размер только если он еще не сохранен
|
|
42
|
+
if (!element.hasAttribute(ORIGINAL_FONT_SIZE_ATTR)) {
|
|
43
|
+
const originalSize = getComputedFontSize(element);
|
|
44
|
+
element.setAttribute(ORIGINAL_FONT_SIZE_ATTR, originalSize.toString());
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Получение оригинального размера шрифта элемента
|
|
50
|
+
*
|
|
51
|
+
* @param element - DOM элемент
|
|
52
|
+
* @returns Оригинальный размер шрифта в пикселях
|
|
53
|
+
*/
|
|
54
|
+
const getOriginalFontSize = (element: HTMLElement): number => {
|
|
55
|
+
const savedSize = element.getAttribute(ORIGINAL_FONT_SIZE_ATTR);
|
|
56
|
+
if (savedSize) {
|
|
57
|
+
return parseFloat(savedSize);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Если оригинальный размер не сохранен, используем текущий
|
|
61
|
+
const currentSize = getComputedFontSize(element);
|
|
62
|
+
element.setAttribute(ORIGINAL_FONT_SIZE_ATTR, currentSize.toString());
|
|
63
|
+
return currentSize;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Применение нового размера шрифта к элементу
|
|
68
|
+
*
|
|
69
|
+
* @param element - DOM элемент
|
|
70
|
+
* @param newSize - Новый размер шрифта в пикселях
|
|
71
|
+
*/
|
|
72
|
+
const applyFontSizeToElement = (element: HTMLElement, newSize: number): void => {
|
|
73
|
+
element.style.fontSize = `${newSize}px`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Сброс размера шрифта элемента к оригинальному
|
|
78
|
+
*
|
|
79
|
+
* @param element - DOM элемент
|
|
80
|
+
*/
|
|
81
|
+
const resetElementFontSize = (element: HTMLElement): void => {
|
|
82
|
+
element.style.fontSize = '';
|
|
83
|
+
element.removeAttribute(ORIGINAL_FONT_SIZE_ATTR);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Применение размера шрифта ко всем текстовым элементам
|
|
88
|
+
*
|
|
89
|
+
* @param size - Размер шрифта (small, standard, large)
|
|
90
|
+
*/
|
|
91
|
+
export const applyDynamicFontSize = (size: FontSize): void => {
|
|
92
|
+
safeDOMOperation(() => {
|
|
93
|
+
if (!isDOMAvailable()) return;
|
|
94
|
+
|
|
95
|
+
const elements = getTextElements();
|
|
96
|
+
const multiplier = FONT_SIZE_MULTIPLIERS[size];
|
|
97
|
+
|
|
98
|
+
elements.forEach((element) => {
|
|
99
|
+
try {
|
|
100
|
+
if (size === 'standard') {
|
|
101
|
+
// Для стандартного размера сбрасываем к оригинальному
|
|
102
|
+
resetElementFontSize(element);
|
|
103
|
+
} else {
|
|
104
|
+
// Сохраняем оригинальный размер, если еще не сохранен
|
|
105
|
+
saveOriginalFontSize(element);
|
|
106
|
+
|
|
107
|
+
// Получаем оригинальный размер и применяем множитель
|
|
108
|
+
const originalSize = getOriginalFontSize(element);
|
|
109
|
+
const newSize = originalSize * multiplier;
|
|
110
|
+
|
|
111
|
+
applyFontSizeToElement(element, newSize);
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.warn('Ошибка при изменении размера шрифта элемента:', element, error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}, `Ошибка при применении динамического размера шрифта: ${size}`);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Сброс всех изменений размера шрифта
|
|
122
|
+
*/
|
|
123
|
+
export const resetAllFontSizes = (): void => {
|
|
124
|
+
safeDOMOperation(() => {
|
|
125
|
+
if (!isDOMAvailable()) return;
|
|
126
|
+
|
|
127
|
+
const elements = getTextElements();
|
|
128
|
+
|
|
129
|
+
elements.forEach((element) => {
|
|
130
|
+
try {
|
|
131
|
+
resetElementFontSize(element);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.warn('Ошибка при сбросе размера шрифта элемента:', element, error);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}, 'Ошибка при сбросе всех размеров шрифта');
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Обновление размеров шрифта для новых элементов
|
|
141
|
+
* Используется когда на страницу добавляются новые элементы динамически
|
|
142
|
+
*
|
|
143
|
+
* @param size - Текущий размер шрифта
|
|
144
|
+
*/
|
|
145
|
+
export const updateNewElementsFontSize = (size: FontSize): void => {
|
|
146
|
+
if (size === 'standard') return;
|
|
147
|
+
|
|
148
|
+
safeDOMOperation(() => {
|
|
149
|
+
if (!isDOMAvailable()) return;
|
|
150
|
+
|
|
151
|
+
const elements = getTextElements();
|
|
152
|
+
const multiplier = FONT_SIZE_MULTIPLIERS[size];
|
|
153
|
+
|
|
154
|
+
elements.forEach((element) => {
|
|
155
|
+
// Применяем размер только к элементам, которые еще не обработаны
|
|
156
|
+
if (!element.hasAttribute(ORIGINAL_FONT_SIZE_ATTR) && !element.style.fontSize) {
|
|
157
|
+
try {
|
|
158
|
+
saveOriginalFontSize(element);
|
|
159
|
+
const originalSize = getOriginalFontSize(element);
|
|
160
|
+
const newSize = originalSize * multiplier;
|
|
161
|
+
applyFontSizeToElement(element, newSize);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn('Ошибка при обновлении размера шрифта нового элемента:', element, error);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}, `Ошибка при обновлении размера шрифта новых элементов: ${size}`);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Проверка, применены ли изменения размера шрифта
|
|
172
|
+
*
|
|
173
|
+
* @returns true, если размер шрифта изменен
|
|
174
|
+
*/
|
|
175
|
+
export const isFontSizeModified = (): boolean => {
|
|
176
|
+
if (!isDOMAvailable()) return false;
|
|
177
|
+
|
|
178
|
+
const elements = getTextElements();
|
|
179
|
+
for (let i = 0; i < Math.min(elements.length, 10); i++) {
|
|
180
|
+
if (elements[i].hasAttribute(ORIGINAL_FONT_SIZE_ATTR)) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return false;
|
|
185
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Глобальные стили доступности
|
|
3
|
+
* Эти стили должны быть импортированы в основной CSS файл приложения
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* ========================================
|
|
7
|
+
Глобальные стили доступности
|
|
8
|
+
======================================== */
|
|
9
|
+
|
|
10
|
+
/* Высококонтрастная тема */
|
|
11
|
+
.accessibility-high-contrast {
|
|
12
|
+
background: #000 !important;
|
|
13
|
+
color: #fff !important;
|
|
14
|
+
|
|
15
|
+
/* Применяем стили ко всем элементам, КРОМЕ панели доступности */
|
|
16
|
+
*:not(.accessibility-panel-container):not(.accessibility-panel-container *) {
|
|
17
|
+
background: #000 !important;
|
|
18
|
+
color: #fff !important;
|
|
19
|
+
border-color: #fff !important;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
a:not(.accessibility-panel-container a) {
|
|
23
|
+
color: #ffff00 !important;
|
|
24
|
+
|
|
25
|
+
&:visited {
|
|
26
|
+
color: #ff00ff !important;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&:hover {
|
|
30
|
+
color: #00ffff !important;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
button:not(.accessibility-panel-container button) {
|
|
35
|
+
background: #fff !important;
|
|
36
|
+
color: #000 !important;
|
|
37
|
+
border: 2px solid #fff !important;
|
|
38
|
+
|
|
39
|
+
&:hover {
|
|
40
|
+
background: #ffff00 !important;
|
|
41
|
+
color: #000 !important;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
input:not(.accessibility-panel-container input),
|
|
46
|
+
textarea:not(.accessibility-panel-container textarea),
|
|
47
|
+
select:not(.accessibility-panel-container select) {
|
|
48
|
+
background: #fff !important;
|
|
49
|
+
color: #000 !important;
|
|
50
|
+
border: 2px solid #fff !important;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* Размеры шрифтов */
|
|
55
|
+
/* Стили для размера шрифта применяются динамически через JavaScript */
|
|
56
|
+
|
|
57
|
+
/* Скрытие изображений */
|
|
58
|
+
.accessibility-hide-images {
|
|
59
|
+
display: none !important;
|
|
60
|
+
}
|