vgapp 0.7.9 → 0.8.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/app/langs/en/buttons.json +10 -0
  3. package/app/langs/en/messages.json +32 -0
  4. package/app/langs/en/titles.json +6 -0
  5. package/app/langs/ru/buttons.json +10 -0
  6. package/app/langs/ru/messages.json +32 -0
  7. package/app/langs/ru/titles.json +6 -0
  8. package/app/modules/base-module.js +12 -1
  9. package/app/modules/module-fn.js +20 -9
  10. package/app/modules/vgalert/js/vgalert.js +12 -6
  11. package/app/modules/vgalert/readme.md +1 -1
  12. package/app/modules/vgcollapse/readme.md +1 -1
  13. package/app/modules/vgdropdown/js/vgdropdown.js +140 -38
  14. package/app/modules/vgdropdown/readme.md +225 -0
  15. package/app/modules/vgfiles/js/base.js +499 -0
  16. package/app/modules/vgfiles/js/droppable.js +159 -0
  17. package/app/modules/vgfiles/js/loader.js +389 -0
  18. package/app/modules/vgfiles/js/render.js +83 -0
  19. package/app/modules/vgfiles/js/sortable.js +155 -0
  20. package/app/modules/vgfiles/js/vgfiles.js +796 -280
  21. package/app/modules/vgfiles/readme.md +193 -0
  22. package/app/modules/vgfiles/scss/_animations.scss +18 -0
  23. package/app/modules/vgfiles/scss/_mixins.scss +73 -0
  24. package/app/modules/vgfiles/scss/_variables.scss +103 -26
  25. package/app/modules/vgfiles/scss/vgfiles.scss +573 -60
  26. package/app/modules/vgformsender/js/vgformsender.js +5 -1
  27. package/app/modules/vgformsender/readme.md +1 -1
  28. package/app/modules/vglawcookie/js/vglawcookie.js +96 -62
  29. package/app/modules/vglawcookie/readme.md +102 -0
  30. package/app/modules/vgsidebar/js/vgsidebar.js +6 -4
  31. package/app/utils/js/components/ajax.js +172 -122
  32. package/app/utils/js/components/animation.js +124 -39
  33. package/app/utils/js/components/backdrop.js +54 -31
  34. package/app/utils/js/components/lang.js +69 -88
  35. package/app/utils/js/components/params.js +34 -31
  36. package/app/utils/js/components/scrollbar.js +118 -67
  37. package/app/utils/js/components/templater.js +14 -4
  38. package/app/utils/js/dom/cookie.js +107 -64
  39. package/app/utils/js/dom/data.js +68 -20
  40. package/app/utils/js/dom/event.js +272 -239
  41. package/app/utils/js/dom/manipulator.js +135 -62
  42. package/app/utils/js/dom/selectors.js +134 -59
  43. package/app/utils/js/functions.js +183 -349
  44. package/build/vgapp.css +1 -1
  45. package/build/vgapp.css.map +1 -1
  46. package/package.json +1 -1
  47. package/app/utils/js/components/overflow.js +0 -28
@@ -1,7 +1,8 @@
1
- import {execute} from "../functions";
2
- import Selectors from "../dom/selectors";
1
+ import { execute } from "../functions";
3
2
  import EventHandler from "../dom/event";
4
- import Overflow from "./overflow";
3
+ import Html from "../components/templater";
4
+ import { Classes } from "../dom/manipulator";
5
+ import ScrollBarHelper from "./scrollbar";
5
6
 
6
7
  const NAME = 'backdrop';
7
8
  const CLASS_NAME = 'vg-backdrop';
@@ -9,50 +10,72 @@ const CLASS_NAME_FADE = 'fade';
9
10
  const CLASS_NAME_SHOW = 'show';
10
11
  const EVENT_MOUSEDOWN = `mousedown.vg.${NAME}`;
11
12
 
12
- let backdrop_delay = 500;
13
+ const backdropDelay = 150; // Уменьшено для более плавного UX
13
14
 
14
15
  class Backdrop {
16
+ static _rootEl = document.body;
17
+ static _scrollbar = new ScrollBarHelper();
18
+ static _backdrop = null;
19
+
20
+ /**
21
+ * Показывает бэкдроп
22
+ * @param {Function} callback - вызывается после отображения
23
+ */
15
24
  static show(callback) {
16
- Backdrop._append()
25
+ if (!this._backdrop) {
26
+ this._append();
27
+ }
28
+
17
29
  execute(callback);
18
30
  }
19
31
 
32
+ /**
33
+ * Скрывает бэкдроп
34
+ * @param {Function} callback - вызывается после скрытия
35
+ */
20
36
  static hide(callback) {
21
- Backdrop._destroy();
22
- execute(callback);
37
+ if (!this._backdrop) return;
38
+
39
+ this._destroy().then(execute.bind(null, callback));
23
40
  }
24
41
 
42
+ /**
43
+ * Создаёт и добавляет элемент бэкдропа
44
+ * @private
45
+ */
25
46
  static _append() {
26
- if (Selectors.find('.' + CLASS_NAME)) {
27
- return false;
28
- }
47
+ const html = Html('dom');
48
+ this._backdrop = html.div({ class: CLASS_NAME });
29
49
 
30
- let backdrop = document.createElement('div');
31
- backdrop.classList.add(CLASS_NAME);
32
-
33
- document.body.append(backdrop);
34
- backdrop.classList.add(CLASS_NAME_SHOW)
35
-
36
- setTimeout(() => {
37
- backdrop.classList.add(CLASS_NAME_FADE)
38
- }, 50);
50
+ this._rootEl.appendChild(this._backdrop);
51
+ requestAnimationFrame(() => {
52
+ Classes.add(this._backdrop, CLASS_NAME_SHOW);
53
+ setTimeout(() => {
54
+ Classes.add(this._backdrop, CLASS_NAME_FADE);
55
+ }, backdropDelay);
56
+ });
39
57
 
40
- EventHandler.on(backdrop, EVENT_MOUSEDOWN, () => {
41
- Backdrop.hide()
42
- Overflow.destroy();
58
+ EventHandler.on(this._backdrop, EVENT_MOUSEDOWN, () => {
59
+ this.hide();
43
60
  });
44
61
  }
45
62
 
63
+ /**
64
+ * Удаляет бэкдроп с анимацией
65
+ * @returns {Promise}
66
+ * @private
67
+ */
46
68
  static _destroy() {
47
- let element = Selectors.find('.' + CLASS_NAME);
48
- if (!element) return;
49
-
50
- element.classList.remove(CLASS_NAME_FADE);
51
-
52
- setTimeout(() => {
53
- element.classList.remove(CLASS_NAME_SHOW);
54
- element.remove();
55
- }, backdrop_delay);
69
+ return new Promise((resolve) => {
70
+ Classes.remove(this._backdrop, CLASS_NAME_FADE);
71
+ setTimeout(() => {
72
+ Classes.remove(this._backdrop, CLASS_NAME_SHOW);
73
+ this._backdrop.remove();
74
+ this._backdrop = null;
75
+ this._scrollbar.reset();
76
+ resolve();
77
+ }, backdropDelay);
78
+ });
56
79
  }
57
80
  }
58
81
 
@@ -1,108 +1,89 @@
1
- import {normalizeData} from "../functions";
1
+ import { normalizeData } from "../functions";
2
2
 
3
- const langs = {
4
- ru: {
5
- messages: {
6
- errors: {
7
- went_wrong: 'Что-то пошло не так, повторите позже',
8
- "400": 'Неверный запрос',
9
- "401": 'Не авторизован',
10
- "403": 'Запрещено',
11
- "404": 'Не найдено',
12
- "413": 'Слишком большой запрос',
13
- "419": 'Проблемы с токеном CSRF',
14
- "422": 'Неверный запрос',
15
- "500": 'Внутренняя ошибка сервера',
16
- "504": 'Превышено время ожидания'
17
- },
18
- 'form-sender': {
19
- 'bootstrap_not_found': 'VGApp не удалось найти bootstrap, модалки не будут закрыты, попробуйте сделать это через коллбек afterSend.'
20
- },
21
- alert: {
22
- title: 'Заголовок по умолчанию',
23
- description: 'Описание текущего действия',
24
- reason: 'Алерт уже открыт'
25
- }
26
- },
27
- titles: {
28
- errors: {
29
- title: 'Ошибка',
30
- titles: 'Ошибки'
31
- }
32
- },
33
- buttons: {
34
- alert: {
35
- agree: 'Да, согласен',
36
- cancel: 'Отмена'
37
- }
38
- }
39
- },
40
- en: {
41
- messages: {
42
- errors: {
43
- went_wrong: 'Something went wrong, please repeat later',
44
- "400": 'Bad Request',
45
- "401": 'Unauthorized',
46
- "403": 'Forbidden',
47
- "404": 'Not Found',
48
- "413": 'Payload Too Large',
49
- "419": 'Problems with the CSRF token',
50
- "422": 'Unprocessable Entity',
51
- "500": 'Internal Server Error',
52
- "504": 'Gateway Timeout'
53
- },
54
- 'form-sender': {
55
- 'bootstrap_not_found': 'VGApp could not find bootstrap, the modals will not be closed, try to do this through the afterSend callback.'
56
- },
57
- alert: {
58
- title: 'Default header',
59
- description: 'Description of the current action',
60
- reason: 'Alert already open'
61
- }
62
- },
63
- titles: {
64
- errors: {
65
- title: 'Error',
66
- titles: 'Errors'
67
- }
68
- },
69
- buttons: {
70
- alert: {
71
- agree: 'Yeah, I agree',
72
- cancel: 'Cancel'
73
- }
74
- }
75
- },
3
+ // Синхронный импорт JSON-файлов (включаются в бандл Webpack/Vite)
4
+ import messagesRu from '../../../langs/ru/messages.json';
5
+ import titlesRu from '../../../langs/ru/titles.json';
6
+ import buttonsRu from '../../../langs/ru/buttons.json';
7
+
8
+ import messagesEn from '../../../langs/en/messages.json';
9
+ import titlesEn from '../../../langs/en/titles.json';
10
+ import buttonsEn from '../../../langs/en/buttons.json';
11
+
12
+ // Синхронные данные, загруженные при сборке
13
+ const langData = {
14
+ ru: normalizeData({
15
+ messages: messagesRu,
16
+ titles: titlesRu,
17
+ buttons: buttonsRu
18
+ }),
19
+ en: normalizeData({
20
+ messages: messagesEn,
21
+ titles: titlesEn,
22
+ buttons: buttonsEn
23
+ })
76
24
  };
77
25
 
26
+ /**
27
+ * Класс для управления языком (синхронный)
28
+ */
78
29
  class Lang {
30
+ /**
31
+ * @param {string} lang - Язык (ru, en и т.д.)
32
+ */
79
33
  constructor(lang = 'en') {
80
34
  this.lang = lang;
81
35
  }
82
36
 
37
+ /**
38
+ * Получение языкового пакета
39
+ * @returns {Object}
40
+ */
83
41
  get() {
84
- let data = langs[this.lang];
85
-
86
- if (!data) data = langs['en'];
87
-
88
- return normalizeData(data);
42
+ return langData[this.lang] || langData['en'];
89
43
  }
90
44
  }
91
45
 
92
- function lang(lg, mode, module) {
93
- return new Lang(lg).get()[mode][module];
46
+ /**
47
+ * Получение языкового раздела
48
+ * @param {string} language - Язык
49
+ * @param {string} mode - messages, titles, buttons
50
+ * @param {string} module - Модуль (files, alert и т.д.)
51
+ * @returns {Object}
52
+ */
53
+ function getLangData(language = 'en', mode, module) {
54
+ const langInstance = new Lang(language);
55
+ const data = langInstance.get();
56
+ return data[mode]?.[module] || {};
94
57
  }
95
58
 
96
- function lang_titles(lg, module) {
97
- return lang(lg, 'titles', module) || {};
59
+ /**
60
+ * Получение заголовков
61
+ * @param {string} language
62
+ * @param {string} module
63
+ * @returns {Object}
64
+ */
65
+ function lang_titles(language, module) {
66
+ return getLangData(language, 'titles', module);
98
67
  }
99
68
 
100
- function lang_messages(lg, module) {
101
- return lang(lg, 'messages', module) || {};
69
+ /**
70
+ * Получение сообщений
71
+ * @param {string} language
72
+ * @param {string} module
73
+ * @returns {Object}
74
+ */
75
+ function lang_messages(language, module) {
76
+ return getLangData(language, 'messages', module);
102
77
  }
103
78
 
104
- function lang_buttons(lg, module) {
105
- return lang(lg, 'buttons', module) || {};
79
+ /**
80
+ * Получение текстов кнопок
81
+ * @param {string} language
82
+ * @param {string} module
83
+ * @returns {Object}
84
+ */
85
+ function lang_buttons(language, module) {
86
+ return getLangData(language, 'buttons', module);
106
87
  }
107
88
 
108
- export {lang, lang_messages, lang_titles, lang_buttons};
89
+ export { lang_messages, lang_titles, lang_buttons };
@@ -1,54 +1,57 @@
1
- import {isElement, mergeDeepObject, normalizeData} from "../functions";
2
- import {Manipulator} from "../dom/manipulator";
1
+ import { isElement, mergeDeepObject } from "../functions";
2
+ import { Manipulator } from "../dom/manipulator";
3
3
 
4
+ /**
5
+ * Класс Params
6
+ * Объединяет параметры из объекта и атрибутов элемента, поддерживает вложенные параметры через дефисы
7
+ */
4
8
  class Params {
5
9
  constructor(params, element = null) {
6
10
  this._params = this.merge(params, element);
7
11
  }
8
12
 
13
+ /**
14
+ * Возвращает итоговые параметры
15
+ */
9
16
  get() {
10
17
  return this._params;
11
18
  }
12
19
 
20
+ /**
21
+ * Извлекает параметры из data-атрибутов элемента
22
+ */
13
23
  fromElement(element) {
14
24
  return isElement(element) ? Manipulator.get(element) : {};
15
25
  }
16
26
 
17
- merge(params, element) {
18
- let mParams = mergeDeepObject(params, this.fromElement(element));
19
-
20
- function stringToNestedObjectWithValue(str, value, params) {
21
- const keys = str.split('-');
22
- let result = {};
23
- let currentLevel = result;
24
-
25
- for (let i = 0; i < keys.length; i++) {
26
- const key = keys[i];
27
-
28
- if (i < keys.length - 1) {
29
- currentLevel[key] = {};
30
- currentLevel = currentLevel[key];
31
- } else {
32
- currentLevel[key] = value;
33
- }
34
- }
27
+ /**
28
+ * Преобразует строку вида 'foo-bar-baz' в вложенный объект { foo: { bar: { baz: value } } }
29
+ */
30
+ static stringToNestedObject(str, value) {
31
+ return str.split('-').reduceRight((acc, key) => ({ [key]: acc }), value);
32
+ }
35
33
 
36
- return mergeDeepObject(params, result);
37
- }
34
+ /**
35
+ * Слияние параметров: объект + data-атрибуты + обработка вложенности и ключа 'params'
36
+ */
37
+ merge(params, element) {
38
+ let merged = mergeDeepObject(params, this.fromElement(element));
38
39
 
39
- for (let key in mParams) {
40
- if ('params' in mParams) {
41
- mParams = mergeDeepObject(mParams, mParams.params);
42
- delete mParams.params;
40
+ // Обработка вложенных ключей через дефисы и ключа 'params'
41
+ Object.keys(merged).forEach(key => {
42
+ if (key === 'params') {
43
+ merged = mergeDeepObject(merged, merged.params);
44
+ delete merged.params;
43
45
  }
44
46
 
45
- if (key.indexOf('-') !== -1) {
46
- mParams = stringToNestedObjectWithValue(key, mParams[key], mParams);
47
- delete mParams[key];
47
+ if (key.includes('-')) {
48
+ const nestedObj = Params.stringToNestedObject(key, merged[key]);
49
+ merged = mergeDeepObject(merged, nestedObj);
50
+ delete merged[key];
48
51
  }
49
- }
52
+ });
50
53
 
51
- return mParams;
54
+ return merged;
52
55
  }
53
56
  }
54
57
 
@@ -1,114 +1,165 @@
1
1
  /**
2
2
  * --------------------------------------------------------------------------
3
- * Bootstrap util/scrollBar.js
3
+ * Bootstrap util/scrollBar.js (рефакторинг)
4
4
  * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
5
5
  * --------------------------------------------------------------------------
6
+ *
7
+ * Улучшена читаемость, вынесены дублирующиеся операции,
8
+ * добавлены JSDoc-комментарии, упрощена логика.
6
9
  */
7
10
 
8
- import {Manipulator} from "../dom/manipulator";
9
- import {isElement} from "../functions";
11
+ import { Manipulator } from "../dom/manipulator";
10
12
  import Selectors from "../dom/selectors";
13
+ import { isElement } from "../functions";
11
14
 
12
15
  /**
13
- * Constants
16
+ * Константы
14
17
  */
15
-
16
- const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'
17
- const SELECTOR_STICKY_CONTENT = '.sticky-top'
18
- const PROPERTY_PADDING = 'padding-right'
19
- const PROPERTY_MARGIN = 'margin-right'
18
+ const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top';
19
+ const SELECTOR_STICKY_CONTENT = '.sticky-top';
20
+ const PROPERTY_PADDING = 'padding-right';
21
+ const PROPERTY_MARGIN = 'margin-right';
20
22
 
21
23
  /**
22
- * Class definition
24
+ * Вспомогательный класс для управления скроллбаром
25
+ * Корректирует отступы при скрытии скролла, сохраняет исходные стили
23
26
  */
24
-
25
27
  class ScrollBarHelper {
26
28
  constructor() {
27
- this._element = document.body
29
+ this._element = document.body;
28
30
  }
29
31
 
30
- // Public
32
+ /**
33
+ * Возвращает ширину вертикального скроллбара
34
+ * @returns {number}
35
+ */
31
36
  getWidth() {
32
- // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes
33
- const documentWidth = document.documentElement.clientWidth
34
- return Math.abs(window.innerWidth - documentWidth)
37
+ const documentWidth = document.documentElement.clientWidth;
38
+ return Math.abs(window.innerWidth - documentWidth);
39
+ }
40
+
41
+ /**
42
+ * Проверяет, есть ли скроллбар (ширина > 0)
43
+ * @returns {boolean}
44
+ */
45
+ isOverflowing() {
46
+ return this.getWidth() > 0;
35
47
  }
36
48
 
49
+ /**
50
+ * Скрывает скроллбар и корректирует макет
51
+ */
37
52
  hide() {
38
- const width = this.getWidth()
39
- this._disableOverFlow()
40
- // give padding to element to balance the hidden scrollbar width
41
- this._setElementAttributes(this._element, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
42
- // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth
43
- this._setElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, calculatedValue => calculatedValue + width)
44
- this._setElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, calculatedValue => calculatedValue - width)
53
+ const width = this.getWidth();
54
+ if (width === 0) return;
55
+
56
+ this._disableOverflow();
57
+
58
+ this._setStyle(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING, width, (value) => value + width);
59
+ this._setStyle(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN, width, (value) => value - width);
60
+ this._setStyle(this._element, PROPERTY_PADDING, width, (value) => value + width);
45
61
  }
46
62
 
63
+ /**
64
+ * Сбрасывает стили до исходных значений
65
+ */
47
66
  reset() {
48
- this._resetElementAttributes(this._element, 'overflow')
49
- this._resetElementAttributes(this._element, PROPERTY_PADDING)
50
- this._resetElementAttributes(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING)
51
- this._resetElementAttributes(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN)
67
+ this._resetStyle(this._element, "overflow");
68
+ this._resetStyle(this._element, PROPERTY_PADDING);
69
+ this._resetStyle(SELECTOR_FIXED_CONTENT, PROPERTY_PADDING);
70
+ this._resetStyle(SELECTOR_STICKY_CONTENT, PROPERTY_MARGIN);
52
71
  }
53
72
 
54
- isOverflowing() {
55
- return this.getWidth() > 0
56
- }
73
+ // === Приватные методы ===
57
74
 
58
- // Private
59
- _disableOverFlow() {
60
- this._saveInitialAttribute(this._element, 'overflow')
61
- this._element.style.overflow = 'hidden'
75
+ /**
76
+ * Блокирует overflow у body
77
+ * @private
78
+ */
79
+ _disableOverflow() {
80
+ this._saveInitialStyle(this._element, "overflow");
81
+ this._element.style.overflow = "hidden";
62
82
  }
63
83
 
64
- _setElementAttributes(selector, styleProperty, callback) {
65
- const scrollbarWidth = this.getWidth()
66
- const manipulationCallBack = element => {
67
- if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) {
68
- return
84
+ /**
85
+ * Устанавливает стили с сохранением начальных значений
86
+ * @param {string|HTMLElement} selector
87
+ * @param {string} property
88
+ * @param {number} width
89
+ * @param {(value: number) => number} callback
90
+ * @private
91
+ */
92
+ _setStyle(selector, property, width, callback) {
93
+ const apply = (element) => {
94
+ // Исключаем элементы, у которых и так нет места под скролл
95
+ if (element !== this._element && window.innerWidth > element.clientWidth + width) {
96
+ return;
69
97
  }
70
98
 
71
- this._saveInitialAttribute(element, styleProperty)
72
- const calculatedValue = window.getComputedStyle(element).getPropertyValue(styleProperty)
73
- element.style.setProperty(styleProperty, `${callback(Number.parseFloat(calculatedValue))}px`)
74
- }
99
+ this._saveInitialStyle(element, property);
75
100
 
76
- this._applyManipulationCallback(selector, manipulationCallBack)
77
- }
101
+ const current = getComputedStyle(element).getPropertyValue(property);
102
+ const value = Number.parseFloat(current) || 0;
78
103
 
79
- _saveInitialAttribute(element, styleProperty) {
80
- const actualValue = element.style.getPropertyValue(styleProperty)
81
- if (actualValue) {
82
- Manipulator.get(element, styleProperty, actualValue)
83
- }
104
+ element.style.setProperty(property, `${callback(value)}px`);
105
+ };
106
+
107
+ this._applyToElements(selector, apply);
84
108
  }
85
109
 
86
- _resetElementAttributes(selector, styleProperty) {
87
- const manipulationCallBack = element => {
88
- const value = Manipulator.get(element, styleProperty)
89
- // We only want to remove the property if the value is `null`; the value can also be zero
110
+ /**
111
+ * Сбрасывает стили до сохранённых значений
112
+ * @param {string|HTMLElement} selector
113
+ * @param {string} property
114
+ * @private
115
+ */
116
+ _resetStyle(selector, property) {
117
+ const apply = (element) => {
118
+ const value = Manipulator.get(element, property);
119
+
90
120
  if (value === null) {
91
- element.style.removeProperty(styleProperty)
92
- return
121
+ element.style.removeProperty(property);
122
+ return;
93
123
  }
94
124
 
95
- Manipulator.remove(element, styleProperty)
96
- element.style.setProperty(styleProperty, value)
97
- }
125
+ Manipulator.remove(element, property);
126
+ element.style.setProperty(property, value);
127
+ };
98
128
 
99
- this._applyManipulationCallback(selector, manipulationCallBack)
129
+ this._applyToElements(selector, apply);
100
130
  }
101
131
 
102
- _applyManipulationCallback(selector, callBack) {
103
- if (isElement(selector)) {
104
- callBack(selector)
105
- return
132
+ /**
133
+ * Сохраняет текущее значение inline-стиля через Manipulator
134
+ * (включая пустое значение, чтобы reset() работал стабильно).
135
+ * @param {HTMLElement} element
136
+ * @param {string} property
137
+ * @private
138
+ */
139
+ _saveInitialStyle(element, property) {
140
+ const value = element.style.getPropertyValue(property);
141
+
142
+ // Важно: сохраняем даже пустую строку (если inline-стиля не было)
143
+ // чтобы reset() мог корректно восстановить "как было".
144
+ if (value !== null) {
145
+ Manipulator.set(element, property, value);
106
146
  }
147
+ }
107
148
 
108
- for (const sel of Selectors.findAll(selector, this._element)) {
109
- callBack(sel)
149
+ /**
150
+ * Применяет callback к элементу или списку элементов
151
+ * @param {string|HTMLElement} selector
152
+ * @param {(element: HTMLElement) => void} callback
153
+ * @private
154
+ */
155
+ _applyToElements(selector, callback) {
156
+ if (isElement(selector)) {
157
+ callback(selector);
158
+ return;
110
159
  }
160
+
161
+ Selectors.findAll(selector, this._element).forEach(callback);
111
162
  }
112
163
  }
113
164
 
114
- export default ScrollBarHelper
165
+ export default ScrollBarHelper;
@@ -213,13 +213,23 @@ class LaravelHtmlBuilder {
213
213
  return this.element('textarea', { name, ...attributes }, content);
214
214
  }
215
215
 
216
- ul(items = [], attributes = {}) {
217
- const listItems = items.map(item =>
218
- this.element('li', {}, item)
219
- );
216
+ ul(items = [], attributes = {}, isNative = false) {
217
+ let listItems = [];
218
+
219
+ if (!isNative) {
220
+ listItems = items.map(item =>
221
+ this.element('li', {}, item)
222
+ );
223
+ } else {
224
+ listItems = items
225
+ }
220
226
  return this.element('ul', attributes, listItems);
221
227
  }
222
228
 
229
+ li(attributes = {}, content, options) {
230
+ return this.element('li', attributes, content, options);
231
+ }
232
+
223
233
  ol(items = [], attributes = {}) {
224
234
  const listItems = items.map(item =>
225
235
  this.element('li', {}, item)