vgapp 1.3.0 → 1.3.2

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 (89) hide show
  1. package/README.md +133 -133
  2. package/app/langs/en/buttons.json +17 -17
  3. package/app/langs/en/messages.json +36 -36
  4. package/app/langs/ru/buttons.json +17 -17
  5. package/app/langs/ru/messages.json +36 -36
  6. package/app/modules/base-module.js +111 -111
  7. package/app/modules/vgalert/js/vgalert.js +52 -29
  8. package/app/modules/vgalert/readme.md +105 -105
  9. package/app/modules/vgdynamictable/index.js +5 -5
  10. package/app/modules/vgdynamictable/js/editable.js +19 -19
  11. package/app/modules/vgdynamictable/js/filters.js +21 -21
  12. package/app/modules/vgdynamictable/js/fixed.js +179 -179
  13. package/app/modules/vgdynamictable/js/pagination.js +1 -1
  14. package/app/modules/vgdynamictable/js/search.js +12 -12
  15. package/app/modules/vgdynamictable/js/sortable.js +71 -71
  16. package/app/modules/vgdynamictable/js/summary-footer.js +28 -28
  17. package/app/modules/vgdynamictable/js/table-remote.js +79 -79
  18. package/app/modules/vgdynamictable/js/table-url-state.js +31 -31
  19. package/app/modules/vgdynamictable/js/utils/common.js +48 -48
  20. package/app/modules/vgdynamictable/js/vgdynamictable.js +268 -268
  21. package/app/modules/vgdynamictable/readme.md +250 -250
  22. package/app/modules/vgdynamictable/scss/_skeleton.scss +6 -6
  23. package/app/modules/vgdynamictable/scss/_sortable.scss +24 -24
  24. package/app/modules/vgdynamictable/scss/_table.scss +4 -4
  25. package/app/modules/vgdynamictable/scss/_variables.scss +79 -79
  26. package/app/modules/vgdynamictable/scss/vgdynamictable.scss +27 -27
  27. package/app/modules/vgfilepreview/js/i18n.js +56 -56
  28. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -145
  29. package/app/modules/vgfilepreview/js/renderers/image.js +92 -92
  30. package/app/modules/vgfilepreview/js/renderers/index.js +19 -19
  31. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -168
  32. package/app/modules/vgfilepreview/js/renderers/office.js +79 -79
  33. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -260
  34. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -76
  35. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -71
  36. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -343
  37. package/app/modules/vgfilepreview/js/renderers/text.js +83 -83
  38. package/app/modules/vgfilepreview/js/renderers/video-modal.js +272 -272
  39. package/app/modules/vgfilepreview/js/renderers/video.js +80 -80
  40. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -522
  41. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -89
  42. package/app/modules/vgfilepreview/js/vgfilepreview.js +7 -7
  43. package/app/modules/vgfilepreview/readme.md +68 -68
  44. package/app/modules/vgfilepreview/scss/_variables.scss +113 -113
  45. package/app/modules/vgfilepreview/scss/vgfilepreview.scss +464 -464
  46. package/app/modules/vgfiles/js/base.js +26 -26
  47. package/app/modules/vgfiles/js/droppable.js +260 -260
  48. package/app/modules/vgfiles/js/render.js +153 -153
  49. package/app/modules/vgfiles/js/vgfiles.js +104 -104
  50. package/app/modules/vgfiles/readme.md +123 -123
  51. package/app/modules/vgfiles/scss/_variables.scss +18 -18
  52. package/app/modules/vgfiles/scss/vgfiles.scss +148 -148
  53. package/app/modules/vgformsender/js/hideshowpass.js +16 -16
  54. package/app/modules/vgformsender/scss/vgformsender.scss +34 -34
  55. package/app/modules/vgmodal/js/vgmodal.drag.js +332 -332
  56. package/app/modules/vgmodal/js/vgmodal.js +188 -188
  57. package/app/modules/vgmodal/js/vgmodal.resize.js +27 -27
  58. package/app/modules/vgmodal/scss/vgmodal.scss +14 -14
  59. package/app/modules/vgnav/js/vgnav.js +135 -135
  60. package/app/modules/vgnav/readme.md +67 -67
  61. package/app/modules/vgnestable/README.md +307 -307
  62. package/app/modules/vgnestable/scss/_variables.scss +60 -60
  63. package/app/modules/vgnestable/scss/vgnestable.scss +163 -163
  64. package/app/modules/vgrangeslider/js/skins.js +222 -222
  65. package/app/modules/vgrangeslider/js/vgrangeslider.js +704 -704
  66. package/app/modules/vgrangeslider/readme.md +523 -523
  67. package/app/modules/vgrangeslider/scss/_variables.scss +53 -53
  68. package/app/modules/vgrangeslider/scss/vgrangeslider.scss +240 -240
  69. package/app/modules/vgselect/js/vgselect.js +39 -39
  70. package/app/modules/vgselect/scss/vgselect.scss +22 -22
  71. package/app/modules/vgspy/readme.md +28 -28
  72. package/app/modules/vgtoast/readme.md +18 -18
  73. package/app/modules/vgtoast/scss/vgtoast.scss +48 -48
  74. package/app/modules/vgtooltip/js/vgtooltip.js +89 -89
  75. package/app/modules/vgtooltip/readme.md +181 -181
  76. package/app/utils/js/components/ajax.js +117 -117
  77. package/app/utils/js/components/audio-metadata.js +240 -240
  78. package/app/utils/js/components/backdrop.js +17 -17
  79. package/app/utils/js/components/file-icon.js +109 -109
  80. package/app/utils/js/components/file-preview.js +304 -304
  81. package/app/utils/js/components/sanitize.js +150 -150
  82. package/app/utils/js/components/video-metadata.js +140 -140
  83. package/app/vgapp.js +130 -107
  84. package/build/vgapp.css +1 -1
  85. package/build/vgapp.css.map +1 -1
  86. package/build/vgapp.js +1 -1
  87. package/build/vgapp.js.map +1 -1
  88. package/index.js +14 -14
  89. package/package.json +1 -1
@@ -1,181 +1,181 @@
1
- # VGTooltip — модуль подсказок и popover
2
-
3
- `VGTooltip` — модуль всплывающих подсказок для Vegas App. Поддерживает tooltip и popover режимы, Data API и JS API, автопозиционирование, анимации и управление через события.
4
-
5
- ---
6
-
7
- ## ✅ Возможности
8
-
9
- - 📦 **Легковесный и автономный** — работает как модуль без внешнего UI-фреймворка.
10
- - 🎯 **Два режима работы** — обычный tooltip и `popover` с заголовком и контентом.
11
- - 🧭 **Умное позиционирование** — поддерживает `top`, `bottom`, `left`, `right` и авто-переворот.
12
- - 🖱 **Гибкие триггеры** — `hover`, `click` или их комбинация.
13
- - 🔁 **Автозакрытие других подсказок** — при `closeOther: true`.
14
- - ⌨ **Поддержка `Esc`** и закрытия по клику вне элемента.
15
- - 🎬 **Анимации показа и скрытия** через общий animation API.
16
- - 📱 **Адаптивное поведение** — перерасчёт позиции при `resize` и `scroll`.
17
-
18
- ---
19
-
20
- ## 📦 Установка
21
-
22
- Модуль подключается как часть системы модулей. Для работы должны быть доступны:
23
- - `BaseModule`
24
- - `EventHandler`
25
- - `Selectors`
26
- - `Placement`
27
-
28
- ---
29
-
30
- ## 🧰 Быстрое использование
31
-
32
- ### Через JavaScript
33
- ```js
34
- const button = document.querySelector('#tooltip-demo');
35
-
36
- const tooltip = new VGTooltip(button, {
37
- title: 'Подсказка',
38
- placement: 'right'
39
- });
40
-
41
- tooltip.show();
42
- ```
43
-
44
- ### Через Data API
45
- ```html
46
- <button
47
- data-vg-toggle="tooltip"
48
- data-vg-title="Текст подсказки">
49
- Наведи на меня
50
- </button>
51
- ```
52
-
53
- ### Popover
54
- ```html
55
- <button
56
- data-vg-toggle="popover"
57
- data-vg-title="Заголовок"
58
- data-vg-content="Подробный текст">
59
- Открыть popover
60
- </button>
61
- ```
62
-
63
- ---
64
-
65
- ## ⚙️ Параметры
66
-
67
- | Параметр | Тип | По умолчанию | Описание |
68
- |--------|------|-------------|--------|
69
- | `title` | `string` | `''` | Заголовок tooltip/popover |
70
- | `content` | `string` | `''` | Контент для режима `popover` |
71
- | `trigger` | `string` | `'hover'` | Триггер показа: `hover`, `click` или комбинация |
72
- | `placement` | `string` | `'top'` | Позиция: `top`, `bottom`, `left`, `right` и варианты |
73
- | `container` | `string` | `'body'` | Контейнер, в который рендерится tooltip |
74
- | `popover` | `boolean` | `false` | Режим popover |
75
- | `html` | `boolean` | `false` | Разрешить HTML в `title` и `content` |
76
- | `offset` | `array` | `[8, 8]` | Смещение относительно элемента |
77
- | `autoFlip` | `boolean` | `true` | Автоматически менять сторону при нехватке места |
78
- | `overflowProtection` | `boolean` | `true` | Защита от выхода за границы окна |
79
- | `fallbackPlacements` | `array` | `['bottom', 'right', 'left']` | Порядок запасных позиций |
80
- | `closeOnOutsideClick` | `boolean` | `true` | Закрывать по клику вне tooltip |
81
- | `keyboard` | `boolean` | `true` | Закрывать по `Esc` |
82
- | `closeOther` | `boolean` | `true` | Скрывать другие открытые tooltip |
83
- | `delay.show` | `number` | `100` | Задержка перед показом, мс |
84
- | `delay.hide` | `number` | `100` | Задержка перед скрытием, мс |
85
- | `arrow.padding` | `number` | `8` | Отступ стрелки от краёв |
86
- | `custom.class` | `string` | `''` | Дополнительный CSS-класс |
87
- | `animation.enable` | `boolean` | `true` | Включить анимации |
88
- | `animation.in` | `string` | `'animate__backInUp'` | Анимация появления |
89
- | `animation.out` | `string` | `'animate__backOutDown'` | Анимация скрытия |
90
- | `animation.delay` | `number` | `300` | Длительность анимации, мс |
91
- | `animation.effect` | `string` | `'none'` | Дополнительный CSS-эффект |
92
-
93
- ---
94
-
95
- ## 💡 Расширенные примеры
96
-
97
- ### 1. Tooltip по клику
98
- ```js
99
- new VGTooltip(document.querySelector('#help'), {
100
- title: 'Подсказка по клику',
101
- trigger: 'click',
102
- placement: 'bottom'
103
- });
104
- ```
105
-
106
- ### 2. HTML-контент
107
- ```js
108
- new VGTooltip(document.querySelector('#html-tooltip'), {
109
- title: '<strong>Важно</strong>',
110
- content: 'Можно передавать <em>HTML</em>',
111
- html: true,
112
- popover: true,
113
- trigger: 'click'
114
- });
115
- ```
116
-
117
- ### 3. Кастомный контейнер и класс
118
- ```js
119
- new VGTooltip(document.querySelector('#custom-tooltip'), {
120
- title: 'Подсказка внутри контейнера',
121
- container: '.demo-area',
122
- custom: {
123
- class: 'tooltip-accent'
124
- }
125
- });
126
- ```
127
-
128
- ---
129
-
130
- ## 🧩 Data API
131
-
132
- - `data-vg-toggle="tooltip"` — обычная подсказка.
133
- - `data-vg-toggle="popover"` — popover-режим.
134
- - `data-vg-title` — текст заголовка.
135
- - `data-vg-content` — текст контента.
136
- - Если у элемента есть `title`, модуль переносит его в `data-vg-original-title` и использует как источник текста.
137
-
138
- ---
139
-
140
- ## 🧪 События
141
-
142
- | Событие | Описание |
143
- |-------|--------|
144
- | `vg.tooltip.show` | Перед показом |
145
- | `vg.tooltip.shown` | После показа |
146
- | `vg.tooltip.hide` | Перед скрытием |
147
- | `vg.tooltip.hidden` | После скрытия |
148
-
149
- ---
150
-
151
- ## 🔄 Методы
152
-
153
- | Метод | Описание |
154
- |------|--------|
155
- | `VGTooltip.getInstance(element)` | Получить существующий экземпляр |
156
- | `VGTooltip.getOrCreateInstance(element, params)` | Получить или создать экземпляр |
157
- | `tooltip.show(relatedTarget)` | Показать tooltip |
158
- | `tooltip.hide()` | Скрыть tooltip |
159
- | `tooltip.toggle(relatedTarget)` | Переключить состояние |
160
- | `tooltip.dispose()` | Удалить tooltip и очистить обработчики |
161
-
162
- ---
163
-
164
- ## 🌐 Глобальные классы
165
-
166
- - `.vg-tooltip-open` — добавляется к `<body>`, пока открыт хотя бы один tooltip.
167
- - `.vg-tooltip.show` — tooltip находится в DOM и видим.
168
- - `.vg-tooltip.shown` — tooltip завершил фазу показа.
169
- - `.vg-tooltip-popover` — оформление для режима popover.
170
-
171
- ---
172
-
173
- ## 📝 Лицензия
174
-
175
- MIT. Свободно использовать и модифицировать.
176
-
177
- ---
178
-
179
- 📌 *Разработано в рамках фронтенд-системы VG Modules.*
180
- > 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
181
- > 📍 Поддерживается в проектах VEGAS
1
+ # VGTooltip — модуль подсказок и popover
2
+
3
+ `VGTooltip` — модуль всплывающих подсказок для Vegas App. Поддерживает tooltip и popover режимы, Data API и JS API, автопозиционирование, анимации и управление через события.
4
+
5
+ ---
6
+
7
+ ## ✅ Возможности
8
+
9
+ - 📦 **Легковесный и автономный** — работает как модуль без внешнего UI-фреймворка.
10
+ - 🎯 **Два режима работы** — обычный tooltip и `popover` с заголовком и контентом.
11
+ - 🧭 **Умное позиционирование** — поддерживает `top`, `bottom`, `left`, `right` и авто-переворот.
12
+ - 🖱 **Гибкие триггеры** — `hover`, `click` или их комбинация.
13
+ - 🔁 **Автозакрытие других подсказок** — при `closeOther: true`.
14
+ - ⌨ **Поддержка `Esc`** и закрытия по клику вне элемента.
15
+ - 🎬 **Анимации показа и скрытия** через общий animation API.
16
+ - 📱 **Адаптивное поведение** — перерасчёт позиции при `resize` и `scroll`.
17
+
18
+ ---
19
+
20
+ ## 📦 Установка
21
+
22
+ Модуль подключается как часть системы модулей. Для работы должны быть доступны:
23
+ - `BaseModule`
24
+ - `EventHandler`
25
+ - `Selectors`
26
+ - `Placement`
27
+
28
+ ---
29
+
30
+ ## 🧰 Быстрое использование
31
+
32
+ ### Через JavaScript
33
+ ```js
34
+ const button = document.querySelector('#tooltip-demo');
35
+
36
+ const tooltip = new VGTooltip(button, {
37
+ title: 'Подсказка',
38
+ placement: 'right'
39
+ });
40
+
41
+ tooltip.show();
42
+ ```
43
+
44
+ ### Через Data API
45
+ ```html
46
+ <button
47
+ data-vg-toggle="tooltip"
48
+ data-vg-title="Текст подсказки">
49
+ Наведи на меня
50
+ </button>
51
+ ```
52
+
53
+ ### Popover
54
+ ```html
55
+ <button
56
+ data-vg-toggle="popover"
57
+ data-vg-title="Заголовок"
58
+ data-vg-content="Подробный текст">
59
+ Открыть popover
60
+ </button>
61
+ ```
62
+
63
+ ---
64
+
65
+ ## ⚙️ Параметры
66
+
67
+ | Параметр | Тип | По умолчанию | Описание |
68
+ |--------|------|-------------|--------|
69
+ | `title` | `string` | `''` | Заголовок tooltip/popover |
70
+ | `content` | `string` | `''` | Контент для режима `popover` |
71
+ | `trigger` | `string` | `'hover'` | Триггер показа: `hover`, `click` или комбинация |
72
+ | `placement` | `string` | `'top'` | Позиция: `top`, `bottom`, `left`, `right` и варианты |
73
+ | `container` | `string` | `'body'` | Контейнер, в который рендерится tooltip |
74
+ | `popover` | `boolean` | `false` | Режим popover |
75
+ | `html` | `boolean` | `false` | Разрешить HTML в `title` и `content` |
76
+ | `offset` | `array` | `[8, 8]` | Смещение относительно элемента |
77
+ | `autoFlip` | `boolean` | `true` | Автоматически менять сторону при нехватке места |
78
+ | `overflowProtection` | `boolean` | `true` | Защита от выхода за границы окна |
79
+ | `fallbackPlacements` | `array` | `['bottom', 'right', 'left']` | Порядок запасных позиций |
80
+ | `closeOnOutsideClick` | `boolean` | `true` | Закрывать по клику вне tooltip |
81
+ | `keyboard` | `boolean` | `true` | Закрывать по `Esc` |
82
+ | `closeOther` | `boolean` | `true` | Скрывать другие открытые tooltip |
83
+ | `delay.show` | `number` | `100` | Задержка перед показом, мс |
84
+ | `delay.hide` | `number` | `100` | Задержка перед скрытием, мс |
85
+ | `arrow.padding` | `number` | `8` | Отступ стрелки от краёв |
86
+ | `custom.class` | `string` | `''` | Дополнительный CSS-класс |
87
+ | `animation.enable` | `boolean` | `true` | Включить анимации |
88
+ | `animation.in` | `string` | `'animate__backInUp'` | Анимация появления |
89
+ | `animation.out` | `string` | `'animate__backOutDown'` | Анимация скрытия |
90
+ | `animation.delay` | `number` | `300` | Длительность анимации, мс |
91
+ | `animation.effect` | `string` | `'none'` | Дополнительный CSS-эффект |
92
+
93
+ ---
94
+
95
+ ## 💡 Расширенные примеры
96
+
97
+ ### 1. Tooltip по клику
98
+ ```js
99
+ new VGTooltip(document.querySelector('#help'), {
100
+ title: 'Подсказка по клику',
101
+ trigger: 'click',
102
+ placement: 'bottom'
103
+ });
104
+ ```
105
+
106
+ ### 2. HTML-контент
107
+ ```js
108
+ new VGTooltip(document.querySelector('#html-tooltip'), {
109
+ title: '<strong>Важно</strong>',
110
+ content: 'Можно передавать <em>HTML</em>',
111
+ html: true,
112
+ popover: true,
113
+ trigger: 'click'
114
+ });
115
+ ```
116
+
117
+ ### 3. Кастомный контейнер и класс
118
+ ```js
119
+ new VGTooltip(document.querySelector('#custom-tooltip'), {
120
+ title: 'Подсказка внутри контейнера',
121
+ container: '.demo-area',
122
+ custom: {
123
+ class: 'tooltip-accent'
124
+ }
125
+ });
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 🧩 Data API
131
+
132
+ - `data-vg-toggle="tooltip"` — обычная подсказка.
133
+ - `data-vg-toggle="popover"` — popover-режим.
134
+ - `data-vg-title` — текст заголовка.
135
+ - `data-vg-content` — текст контента.
136
+ - Если у элемента есть `title`, модуль переносит его в `data-vg-original-title` и использует как источник текста.
137
+
138
+ ---
139
+
140
+ ## 🧪 События
141
+
142
+ | Событие | Описание |
143
+ |-------|--------|
144
+ | `vg.tooltip.show` | Перед показом |
145
+ | `vg.tooltip.shown` | После показа |
146
+ | `vg.tooltip.hide` | Перед скрытием |
147
+ | `vg.tooltip.hidden` | После скрытия |
148
+
149
+ ---
150
+
151
+ ## 🔄 Методы
152
+
153
+ | Метод | Описание |
154
+ |------|--------|
155
+ | `VGTooltip.getInstance(element)` | Получить существующий экземпляр |
156
+ | `VGTooltip.getOrCreateInstance(element, params)` | Получить или создать экземпляр |
157
+ | `tooltip.show(relatedTarget)` | Показать tooltip |
158
+ | `tooltip.hide()` | Скрыть tooltip |
159
+ | `tooltip.toggle(relatedTarget)` | Переключить состояние |
160
+ | `tooltip.dispose()` | Удалить tooltip и очистить обработчики |
161
+
162
+ ---
163
+
164
+ ## 🌐 Глобальные классы
165
+
166
+ - `.vg-tooltip-open` — добавляется к `<body>`, пока открыт хотя бы один tooltip.
167
+ - `.vg-tooltip.show` — tooltip находится в DOM и видим.
168
+ - `.vg-tooltip.shown` — tooltip завершил фазу показа.
169
+ - `.vg-tooltip-popover` — оформление для режима popover.
170
+
171
+ ---
172
+
173
+ ## 📝 Лицензия
174
+
175
+ MIT. Свободно использовать и модифицировать.
176
+
177
+ ---
178
+
179
+ 📌 *Разработано в рамках фронтенд-системы VG Modules.*
180
+ > 🚀 Автор: VEGAS STUDIO (vegas-dev.com)
181
+ > 📍 Поддерживается в проектах VEGAS
@@ -1,117 +1,117 @@
1
- import { isObject, noop, normalizeData } from "../functions";
2
-
3
- class Ajax {
4
- static _toRouteQueryParamValue(value) {
5
- if (!Array.isArray(value)) {
6
- return value;
7
- }
8
-
9
- return value
10
- .map((item) => String(item || '').trim())
11
- .filter((item) => item !== '')
12
- .join(',');
13
- }
14
-
15
- static buildRouteUrl(route, options = {}) {
16
- const target = String(route || '').trim();
17
- if (!target) {
18
- throw new Error('Request endpoint is empty');
19
- }
20
-
21
- const baseParams = isObject(options.baseParams) ? options.baseParams : {};
22
- const params = isObject(options.params) ? options.params : {};
23
- const url = new URL(target, window.location.origin);
24
- const merged = { ...baseParams, ...params };
25
-
26
- Object.keys(merged).forEach((key) => {
27
- const value = this._toRouteQueryParamValue(merged[key]);
28
- if (value === undefined || value === null || value === '') {
29
- return;
30
- }
31
-
32
- url.searchParams.set(key, String(value));
33
- });
34
-
35
- return url;
36
- }
37
-
38
- static async requestRoute(options = {}) {
39
- const method = String(options.method || 'GET').toUpperCase().trim() || 'GET';
40
- const headers = isObject(options.headers) ? { ...options.headers } : {};
41
- const credentials = String(options.credentials || 'same-origin').trim() || 'same-origin';
42
- const baseParams = isObject(options.baseParams) ? options.baseParams : {};
43
- const params = isObject(options.params) ? options.params : {};
44
- const hasBody = options.body !== undefined && options.body !== null;
45
- const isGetLike = method === 'GET' || method === 'HEAD';
46
- const route = String(options.route || options.endpoint || '').trim();
47
- const url = this.buildRouteUrl(route, {
48
- baseParams,
49
- params: isGetLike ? params : {},
50
- });
51
- const requestOptions = {
52
- method,
53
- credentials,
54
- headers,
55
- signal: options.signal || undefined,
56
- };
57
-
58
- if (!isGetLike && hasBody) {
59
- const body = options.body;
60
- const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;
61
-
62
- if (isFormData) {
63
- delete requestOptions.headers['Content-Type'];
64
- requestOptions.body = body;
65
- } else if (typeof body === 'string') {
66
- if (!requestOptions.headers['Content-Type']) {
67
- requestOptions.headers['Content-Type'] = 'application/json';
68
- }
69
- requestOptions.body = body;
70
- } else {
71
- if (!requestOptions.headers['Content-Type']) {
72
- requestOptions.headers['Content-Type'] = 'application/json';
73
- }
74
- requestOptions.body = JSON.stringify(body);
75
- }
76
- }
77
-
78
- const response = await fetch(url.toString(), requestOptions);
79
- const contentType = String(response.headers.get('content-type') || '').toLowerCase();
80
- const expectsJson = options.responseType === 'json'
81
- || (options.responseType !== 'text' && contentType.includes('application/json'));
82
- const payload = await (async () => {
83
- if (expectsJson) {
84
- try {
85
- return await response.json();
86
- } catch (error) {
87
- return await response.text();
88
- }
89
- }
90
-
91
- return response.text();
92
- })();
93
-
94
- if (!response.ok) {
95
- const apiError = payload && typeof payload === 'object' && payload.error && typeof payload.error === 'object'
96
- ? payload.error
97
- : {};
98
- const fallbackMessage = typeof payload === 'string' && payload.trim()
99
- ? payload
100
- : response.statusText;
101
- const message = String(apiError.message || fallbackMessage || 'Request failed');
102
- const error = new Error(message);
103
- error.status = response.status;
104
- error.code = apiError.code || 'REQUEST_FAILED';
105
- error.details = Array.isArray(apiError.details) ? apiError.details : [];
106
- error.requestId = apiError.request_id || response.headers.get('X-Request-Id') || '';
107
- error.response = payload;
108
- throw error;
109
- }
110
-
111
- return payload;
112
- }
113
-
114
- /**
1
+ import { isObject, noop, normalizeData } from "../functions";
2
+
3
+ class Ajax {
4
+ static _toRouteQueryParamValue(value) {
5
+ if (!Array.isArray(value)) {
6
+ return value;
7
+ }
8
+
9
+ return value
10
+ .map((item) => String(item || '').trim())
11
+ .filter((item) => item !== '')
12
+ .join(',');
13
+ }
14
+
15
+ static buildRouteUrl(route, options = {}) {
16
+ const target = String(route || '').trim();
17
+ if (!target) {
18
+ throw new Error('Request endpoint is empty');
19
+ }
20
+
21
+ const baseParams = isObject(options.baseParams) ? options.baseParams : {};
22
+ const params = isObject(options.params) ? options.params : {};
23
+ const url = new URL(target, window.location.origin);
24
+ const merged = { ...baseParams, ...params };
25
+
26
+ Object.keys(merged).forEach((key) => {
27
+ const value = this._toRouteQueryParamValue(merged[key]);
28
+ if (value === undefined || value === null || value === '') {
29
+ return;
30
+ }
31
+
32
+ url.searchParams.set(key, String(value));
33
+ });
34
+
35
+ return url;
36
+ }
37
+
38
+ static async requestRoute(options = {}) {
39
+ const method = String(options.method || 'GET').toUpperCase().trim() || 'GET';
40
+ const headers = isObject(options.headers) ? { ...options.headers } : {};
41
+ const credentials = String(options.credentials || 'same-origin').trim() || 'same-origin';
42
+ const baseParams = isObject(options.baseParams) ? options.baseParams : {};
43
+ const params = isObject(options.params) ? options.params : {};
44
+ const hasBody = options.body !== undefined && options.body !== null;
45
+ const isGetLike = method === 'GET' || method === 'HEAD';
46
+ const route = String(options.route || options.endpoint || '').trim();
47
+ const url = this.buildRouteUrl(route, {
48
+ baseParams,
49
+ params: isGetLike ? params : {},
50
+ });
51
+ const requestOptions = {
52
+ method,
53
+ credentials,
54
+ headers,
55
+ signal: options.signal || undefined,
56
+ };
57
+
58
+ if (!isGetLike && hasBody) {
59
+ const body = options.body;
60
+ const isFormData = typeof FormData !== 'undefined' && body instanceof FormData;
61
+
62
+ if (isFormData) {
63
+ delete requestOptions.headers['Content-Type'];
64
+ requestOptions.body = body;
65
+ } else if (typeof body === 'string') {
66
+ if (!requestOptions.headers['Content-Type']) {
67
+ requestOptions.headers['Content-Type'] = 'application/json';
68
+ }
69
+ requestOptions.body = body;
70
+ } else {
71
+ if (!requestOptions.headers['Content-Type']) {
72
+ requestOptions.headers['Content-Type'] = 'application/json';
73
+ }
74
+ requestOptions.body = JSON.stringify(body);
75
+ }
76
+ }
77
+
78
+ const response = await fetch(url.toString(), requestOptions);
79
+ const contentType = String(response.headers.get('content-type') || '').toLowerCase();
80
+ const expectsJson = options.responseType === 'json'
81
+ || (options.responseType !== 'text' && contentType.includes('application/json'));
82
+ const payload = await (async () => {
83
+ if (expectsJson) {
84
+ try {
85
+ return await response.json();
86
+ } catch (error) {
87
+ return await response.text();
88
+ }
89
+ }
90
+
91
+ return response.text();
92
+ })();
93
+
94
+ if (!response.ok) {
95
+ const apiError = payload && typeof payload === 'object' && payload.error && typeof payload.error === 'object'
96
+ ? payload.error
97
+ : {};
98
+ const fallbackMessage = typeof payload === 'string' && payload.trim()
99
+ ? payload
100
+ : response.statusText;
101
+ const message = String(apiError.message || fallbackMessage || 'Request failed');
102
+ const error = new Error(message);
103
+ error.status = response.status;
104
+ error.code = apiError.code || 'REQUEST_FAILED';
105
+ error.details = Array.isArray(apiError.details) ? apiError.details : [];
106
+ error.requestId = apiError.request_id || response.headers.get('X-Request-Id') || '';
107
+ error.response = payload;
108
+ throw error;
109
+ }
110
+
111
+ return payload;
112
+ }
113
+
114
+ /**
115
115
  * Конфигурация запроса
116
116
  * @param {Object} options
117
117
  * @param {string} options.baseUrl - Базовый URL API (опционально)
@@ -231,7 +231,7 @@ class Ajax {
231
231
  method,
232
232
  headers,
233
233
  signal,
234
- credentials: withCredentials ? 'include' : 'same-origin',
234
+ credentials: withCredentials ? 'include' : 'same-origin',
235
235
  ...(body !== undefined && { body }),
236
236
  };
237
237
 
@@ -267,7 +267,7 @@ class Ajax {
267
267
  () => {}
268
268
  );
269
269
  } else {
270
- onSuccess(data);
270
+ onSuccess(data);
271
271
  }
272
272
  })
273
273
  .catch((error) => {
@@ -393,4 +393,4 @@ class Ajax {
393
393
  }
394
394
  }
395
395
 
396
- export default Ajax;
396
+ export default Ajax;