vgapp 1.1.2 → 1.1.4

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 (55) hide show
  1. package/CHANGELOG.md +23 -5
  2. package/README.md +19 -16
  3. package/agents.md +6 -0
  4. package/app/langs/en/buttons.json +17 -2
  5. package/app/langs/en/messages.json +36 -1
  6. package/app/langs/ru/buttons.json +17 -2
  7. package/app/langs/ru/messages.json +69 -34
  8. package/app/modules/module-fn.js +15 -9
  9. package/app/modules/vgfilepreview/index.js +3 -0
  10. package/app/modules/vgfilepreview/js/i18n.js +56 -0
  11. package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -0
  12. package/app/modules/vgfilepreview/js/renderers/image.js +92 -0
  13. package/app/modules/vgfilepreview/js/renderers/index.js +19 -0
  14. package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -0
  15. package/app/modules/vgfilepreview/js/renderers/office.js +79 -0
  16. package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -0
  17. package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -0
  18. package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -0
  19. package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -0
  20. package/app/modules/vgfilepreview/js/renderers/text.js +83 -0
  21. package/app/modules/vgfilepreview/js/renderers/video-modal.js +272 -0
  22. package/app/modules/vgfilepreview/js/renderers/video.js +80 -0
  23. package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -0
  24. package/app/modules/vgfilepreview/js/renderers/zip.js +89 -0
  25. package/app/modules/vgfilepreview/js/vgfilepreview.js +594 -0
  26. package/app/modules/vgfilepreview/readme.md +68 -0
  27. package/app/modules/vgfilepreview/scss/_variables.scss +113 -0
  28. package/app/modules/vgfilepreview/scss/vgfilepreview.scss +460 -0
  29. package/app/modules/vgfiles/js/base.js +463 -175
  30. package/app/modules/vgfiles/js/droppable.js +260 -260
  31. package/app/modules/vgfiles/js/render.js +153 -153
  32. package/app/modules/vgfiles/js/vgfiles.js +41 -29
  33. package/app/modules/vgfiles/readme.md +116 -217
  34. package/app/modules/vgfiles/scss/_variables.scss +18 -10
  35. package/app/modules/vgfiles/scss/vgfiles.scss +153 -59
  36. package/app/modules/vgformsender/js/vgformsender.js +13 -13
  37. package/app/modules/vgmodal/js/vgmodal.js +12 -0
  38. package/app/modules/vgnav/js/vgnav.js +135 -135
  39. package/app/modules/vgnav/readme.md +67 -67
  40. package/app/modules/vgnestable/README.md +307 -307
  41. package/app/modules/vgnestable/scss/_variables.scss +60 -60
  42. package/app/modules/vgnestable/scss/vgnestable.scss +163 -163
  43. package/app/modules/vgselect/js/vgselect.js +39 -39
  44. package/app/modules/vgselect/scss/vgselect.scss +22 -22
  45. package/app/modules/vgspy/readme.md +28 -28
  46. package/app/utils/js/components/audio-metadata.js +240 -0
  47. package/app/utils/js/components/file-icon.js +109 -0
  48. package/app/utils/js/components/file-preview.js +304 -0
  49. package/app/utils/js/components/sanitize.js +150 -150
  50. package/build/vgapp.css +1 -1
  51. package/build/vgapp.css.map +1 -1
  52. package/build/vgapp.js.map +1 -1
  53. package/index.js +1 -0
  54. package/index.scss +9 -6
  55. package/package.json +1 -1
@@ -1,307 +1,307 @@
1
- # VGNestable
2
-
3
- `VGNestable` - модуль для вложенной сортировки списка (drag-and-drop + клавиатура) с ограничением глубины, сворачиванием веток и опциональным сохранением структуры на сервер.
4
-
5
- ## Возможности
6
-
7
- - Сортировка элементов внутри списка мышью, touch и Pointer Events.
8
- - Перенос элементов между несколькими списками (по `group` + `connect`).
9
- - Ограничение максимальной глубины вложенности (`maxdepth`).
10
- - Управление разрешением дропа через функцию `accept`.
11
- - Поддержка клавиатуры и live-region для доступности (ARIA).
12
- - Автоматическое создание внутренней структуры (`.vg-nestable-inner`, drag-handle, кнопка collapse).
13
- - Сериализация структуры в дерево (`serialize()`).
14
- - Автосохранение после изменения через `ajax.route` или ручной вызов `save()`.
15
- - События жизненного цикла через DOM events и `callbacks`.
16
-
17
- ## Подключение
18
-
19
- ```js
20
- import VGNestable from "./app/modules/vgnestable";
21
- ```
22
-
23
- Инициализация через JS:
24
-
25
- ```js
26
- const nestable = VGNestable.getOrCreateInstance("#myNestable", {
27
- maxdepth: 4
28
- });
29
- ```
30
-
31
- Или через data-api (автоинициализация на `DOMContentLoaded`):
32
-
33
- ```html
34
- <div data-vg-toggle="nestable" class="vg-nestable">
35
- <ol class="vg-nestable-list">
36
- <li class="vg-nestable-item" data-id="1">...</li>
37
- <li class="vg-nestable-item" data-id="2">...</li>
38
- </ol>
39
- </div>
40
- ```
41
-
42
- ## Рекомендуемая разметка
43
-
44
- ```html
45
- <div id="myNestable" class="vg-nestable">
46
- <ol class="vg-nestable-list">
47
- <li class="vg-nestable-item" data-id="1">
48
- <div class="vg-nestable-inner">
49
- <div class="vg-nestable-handle"></div>
50
- <div class="vg-nestable-content">Item 1</div>
51
- </div>
52
- <ol class="vg-nestable-list">
53
- <li class="vg-nestable-item" data-id="11">
54
- <div class="vg-nestable-inner">
55
- <div class="vg-nestable-handle"></div>
56
- <div class="vg-nestable-content">Item 1.1</div>
57
- </div>
58
- </li>
59
- </ol>
60
- </li>
61
- </ol>
62
- </div>
63
- ```
64
-
65
- Примечание: если `inner`/`handle` отсутствуют, модуль может достроить их автоматически при `refresh()`.
66
-
67
- ## Все настройки
68
-
69
- Значения по умолчанию:
70
-
71
- ```js
72
- {
73
- listselector: ".vg-nestable-list",
74
- itemselector: ".vg-nestable-item",
75
- handleselector: ".vg-nestable-handle",
76
- idattribute: "data-id",
77
- childlistclass: "vg-nestable-list",
78
- handleicon: "",
79
- indent: 28,
80
- maxdepth: 6,
81
- hoverthreshold: 0.18,
82
- neighborchangethreshold: 0,
83
- showplaceholder: true,
84
- group: "",
85
- connect: false,
86
- accept: null,
87
- collapse: {
88
- enabled: true,
89
- open: true,
90
- showtext: getSVG("chevron"),
91
- hidetext: getSVG("chevron")
92
- },
93
- callbacks: {
94
- init: null,
95
- refresh: null,
96
- pointerdown: null,
97
- start: null,
98
- move: null,
99
- placeholdermove: null,
100
- drop: null,
101
- transfer: null,
102
- change: null,
103
- end: null,
104
- save: null,
105
- destroy: null
106
- },
107
- ajax: {
108
- route: "",
109
- method: "post",
110
- field: "items",
111
- data: {},
112
- loader: false,
113
- once: false,
114
- output: false,
115
- timeout: 0
116
- }
117
- }
118
- ```
119
-
120
- ### Пояснение параметров
121
-
122
- - `listselector`: селектор корневого/вложенных списков.
123
- - `itemselector`: селектор sortable-элемента.
124
- - `handleselector`: селектор зоны, за которую можно начинать drag.
125
- - `idattribute`: атрибут, из которого берется `id` в `serialize()`.
126
- - `childlistclass`: классы для автосозданного дочернего списка.
127
- - `handleicon`: HTML/SVG иконки хэндла (проходит SVG-санитизацию).
128
- - `indent`: смещение по X (px), после которого режим дропа переключается в вложение (`child`).
129
- - `maxdepth`: максимальная глубина дерева.
130
- - `hoverthreshold`: вертикальный порог (0.05..0.45 фактически), определяет зоны `before/after/keep`.
131
- - `neighborchangethreshold`: порог в процентах (0..49), альтернативная логика смены позиции по краям элемента.
132
- - `showplaceholder`: показывать/скрывать placeholder во время drag.
133
- - `group`: имя группы списков для межспискового dnd.
134
- - `connect`: включить связь списков внутри группы.
135
- - `accept(item, sourceInstance, targetInstance)`: функция-фильтр разрешения дропа в target.
136
-
137
- #### `collapse`
138
-
139
- - `enabled`: добавить collapse-toggle у элементов с дочерним списком.
140
- - `open`: начальное состояние дочерних списков (`show`/скрыт).
141
- - `showtext`: контент кнопки в закрытом состоянии.
142
- - `hidetext`: контент кнопки в открытом состоянии.
143
-
144
- #### `callbacks`
145
-
146
- Ключи совпадают с именами событий. Если передана функция, модуль вызывает ее в `_emit(action, payload)`.
147
-
148
- #### `ajax`
149
-
150
- - `route`: URL сохранения (если пустой, `save()` вернет payload без запроса).
151
- - `method`: `post | get | delete`.
152
- - `field`: имя поля для payload при `post` (по умолчанию `items`).
153
- - `data`: дополнительные данные для `post`.
154
- - `timeout`: задержка перед отправкой (мс).
155
- - `loader`, `once`, `output`: есть в конфиге, но в текущей реализации `save()` не используются.
156
-
157
- ## Публичное API
158
-
159
- - `VGNestable.getOrCreateInstance(element, params?)`
160
- - `VGNestable.getInstance(element)`
161
- - `instance.refresh()` - пересобирает layout элементов, handle, collapse.
162
- - `instance.serialize()` - возвращает дерево вида:
163
-
164
- ```js
165
- [
166
- { id: 1, children: [{ id: 11 }] },
167
- { id: 2 }
168
- ]
169
- ```
170
-
171
- - `instance.save()` - отправляет сериализованные данные на сервер (если указан `ajax.route`), возвращает `Promise`.
172
- - `instance.dispose()` - снимает listeners, удаляет служебные узлы, unregister из группы.
173
-
174
- ## События
175
-
176
- DOM-события триггерятся на корневом элементе модуля:
177
-
178
- - `vg.nestable.init`
179
- - `vg.nestable.refresh`
180
- - `vg.nestable.pointerdown`
181
- - `vg.nestable.start`
182
- - `vg.nestable.move`
183
- - `vg.nestable.placeholdermove`
184
- - `vg.nestable.drop`
185
- - `vg.nestable.transfer`
186
- - `vg.nestable.change`
187
- - `vg.nestable.end`
188
- - `vg.nestable.save`
189
- - `vg.nestable.destroy`
190
-
191
- Подписка:
192
-
193
- ```js
194
- const el = document.querySelector("#myNestable");
195
- el.addEventListener("vg.nestable.change", (event) => {
196
- console.log(event.detail.payload);
197
- });
198
- ```
199
-
200
- Полезные поля `event.detail` (зависят от события):
201
-
202
- - `action`
203
- - `instance`
204
- - `item`
205
- - `payload`
206
- - `previousPayload`
207
- - `changed`
208
- - `targetInstance`
209
- - `sourcePayload` / `targetPayload` (для transfer)
210
- - `status`, `response`, `error` (для save)
211
- - `keyboard`, `cancelled`
212
-
213
- ## Клавиатурное управление
214
-
215
- Фокус на handle (или `handleselector`), затем:
216
-
217
- - `Enter`/`Space`: поднять элемент или завершить drop.
218
- - `ArrowUp` / `ArrowDown`: перемещение вверх/вниз в текущем списке.
219
- - `ArrowRight`: сделать вложенным в предыдущий элемент.
220
- - `ArrowLeft`: уменьшить уровень вложенности.
221
- - `Escape`: отмена текущей keyboard-сессии.
222
-
223
- ## Межсписковый перенос
224
-
225
- Чтобы переносить элементы между списками:
226
-
227
- 1. У обоих инстансов должен быть одинаковый `group`.
228
- 2. У обоих `connect: true`.
229
- 3. (Опционально) `accept` у target должен вернуть `true`.
230
-
231
- При переносе:
232
-
233
- - исходный инстанс и целевой получают `transfer`;
234
- - исходный получает `change`;
235
- - целевой получает `change` с новым payload;
236
- - если настроен `ajax.route`, `save()` вызывается для каждого изменившегося инстанса.
237
-
238
- ## Автосохранение и ручное сохранение
239
-
240
- Автосохранение происходит после `change`, если указан `ajax.route`.
241
- Ручной вызов:
242
-
243
- ```js
244
- nestable.save()
245
- .then((response) => console.log("Saved", response))
246
- .catch((error) => console.error("Save error", error));
247
- ```
248
-
249
- При `method: "post"` уходит:
250
-
251
- ```js
252
- {
253
- ...ajax.data,
254
- [ajax.field]: nestable.serialize()
255
- }
256
- ```
257
-
258
- ## Стили и CSS-переменные
259
-
260
- Основные классы:
261
-
262
- - `.vg-nestable-list`
263
- - `.vg-nestable-item`
264
- - `.vg-nestable-inner`
265
- - `.vg-nestable-handle`
266
- - `.vg-nestable-handle-icon`
267
- - `.vg-nestable-collapse-toggle`
268
- - `.vg-nestable-placeholder`
269
- - `.vg-nestable-drag-element`
270
- - `.is-drop-target`
271
- - `.is-drop-denied`
272
- - `.is-dragging`
273
- - `.is-drag-ghost`
274
-
275
- SCSS-переменные заданы в `scss/_variables.scss` через map `nestable` (`--vg-nestable-*`), включая:
276
-
277
- - размеры handle/toggle;
278
- - цвета и границы placeholder/drop-state;
279
- - отступы вложенных списков;
280
- - стиль ghost-элемента при drag.
281
-
282
- ## Короткий пример полной инициализации
283
-
284
- ```js
285
- VGNestable.getOrCreateInstance("#myNestable", {
286
- maxdepth: 5,
287
- indent: 24,
288
- connect: true,
289
- group: "menu-builder",
290
- accept: (item, from, to) => from !== to || item.dataset.locked !== "true",
291
- collapse: {
292
- enabled: true,
293
- open: false
294
- },
295
- ajax: {
296
- route: "/admin/menu/reorder",
297
- method: "post",
298
- field: "items",
299
- data: { menu_id: 15 },
300
- timeout: 100
301
- },
302
- callbacks: {
303
- change: ({ payload }) => console.log("Tree changed:", payload),
304
- save: ({ status, response, error }) => console.log("Save:", status, response || error)
305
- }
306
- });
307
- ```
1
+ # VGNestable
2
+
3
+ `VGNestable` - модуль для вложенной сортировки списка (drag-and-drop + клавиатура) с ограничением глубины, сворачиванием веток и опциональным сохранением структуры на сервер.
4
+
5
+ ## Возможности
6
+
7
+ - Сортировка элементов внутри списка мышью, touch и Pointer Events.
8
+ - Перенос элементов между несколькими списками (по `group` + `connect`).
9
+ - Ограничение максимальной глубины вложенности (`maxdepth`).
10
+ - Управление разрешением дропа через функцию `accept`.
11
+ - Поддержка клавиатуры и live-region для доступности (ARIA).
12
+ - Автоматическое создание внутренней структуры (`.vg-nestable-inner`, drag-handle, кнопка collapse).
13
+ - Сериализация структуры в дерево (`serialize()`).
14
+ - Автосохранение после изменения через `ajax.route` или ручной вызов `save()`.
15
+ - События жизненного цикла через DOM events и `callbacks`.
16
+
17
+ ## Подключение
18
+
19
+ ```js
20
+ import VGNestable from "./app/modules/vgnestable";
21
+ ```
22
+
23
+ Инициализация через JS:
24
+
25
+ ```js
26
+ const nestable = VGNestable.getOrCreateInstance("#myNestable", {
27
+ maxdepth: 4
28
+ });
29
+ ```
30
+
31
+ Или через data-api (автоинициализация на `DOMContentLoaded`):
32
+
33
+ ```html
34
+ <div data-vg-toggle="nestable" class="vg-nestable">
35
+ <ol class="vg-nestable-list">
36
+ <li class="vg-nestable-item" data-id="1">...</li>
37
+ <li class="vg-nestable-item" data-id="2">...</li>
38
+ </ol>
39
+ </div>
40
+ ```
41
+
42
+ ## Рекомендуемая разметка
43
+
44
+ ```html
45
+ <div id="myNestable" class="vg-nestable">
46
+ <ol class="vg-nestable-list">
47
+ <li class="vg-nestable-item" data-id="1">
48
+ <div class="vg-nestable-inner">
49
+ <div class="vg-nestable-handle"></div>
50
+ <div class="vg-nestable-content">Item 1</div>
51
+ </div>
52
+ <ol class="vg-nestable-list">
53
+ <li class="vg-nestable-item" data-id="11">
54
+ <div class="vg-nestable-inner">
55
+ <div class="vg-nestable-handle"></div>
56
+ <div class="vg-nestable-content">Item 1.1</div>
57
+ </div>
58
+ </li>
59
+ </ol>
60
+ </li>
61
+ </ol>
62
+ </div>
63
+ ```
64
+
65
+ Примечание: если `inner`/`handle` отсутствуют, модуль может достроить их автоматически при `refresh()`.
66
+
67
+ ## Все настройки
68
+
69
+ Значения по умолчанию:
70
+
71
+ ```js
72
+ {
73
+ listselector: ".vg-nestable-list",
74
+ itemselector: ".vg-nestable-item",
75
+ handleselector: ".vg-nestable-handle",
76
+ idattribute: "data-id",
77
+ childlistclass: "vg-nestable-list",
78
+ handleicon: "",
79
+ indent: 28,
80
+ maxdepth: 6,
81
+ hoverthreshold: 0.18,
82
+ neighborchangethreshold: 0,
83
+ showplaceholder: true,
84
+ group: "",
85
+ connect: false,
86
+ accept: null,
87
+ collapse: {
88
+ enabled: true,
89
+ open: true,
90
+ showtext: getSVG("chevron"),
91
+ hidetext: getSVG("chevron")
92
+ },
93
+ callbacks: {
94
+ init: null,
95
+ refresh: null,
96
+ pointerdown: null,
97
+ start: null,
98
+ move: null,
99
+ placeholdermove: null,
100
+ drop: null,
101
+ transfer: null,
102
+ change: null,
103
+ end: null,
104
+ save: null,
105
+ destroy: null
106
+ },
107
+ ajax: {
108
+ route: "",
109
+ method: "post",
110
+ field: "items",
111
+ data: {},
112
+ loader: false,
113
+ once: false,
114
+ output: false,
115
+ timeout: 0
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### Пояснение параметров
121
+
122
+ - `listselector`: селектор корневого/вложенных списков.
123
+ - `itemselector`: селектор sortable-элемента.
124
+ - `handleselector`: селектор зоны, за которую можно начинать drag.
125
+ - `idattribute`: атрибут, из которого берется `id` в `serialize()`.
126
+ - `childlistclass`: классы для автосозданного дочернего списка.
127
+ - `handleicon`: HTML/SVG иконки хэндла (проходит SVG-санитизацию).
128
+ - `indent`: смещение по X (px), после которого режим дропа переключается в вложение (`child`).
129
+ - `maxdepth`: максимальная глубина дерева.
130
+ - `hoverthreshold`: вертикальный порог (0.05..0.45 фактически), определяет зоны `before/after/keep`.
131
+ - `neighborchangethreshold`: порог в процентах (0..49), альтернативная логика смены позиции по краям элемента.
132
+ - `showplaceholder`: показывать/скрывать placeholder во время drag.
133
+ - `group`: имя группы списков для межспискового dnd.
134
+ - `connect`: включить связь списков внутри группы.
135
+ - `accept(item, sourceInstance, targetInstance)`: функция-фильтр разрешения дропа в target.
136
+
137
+ #### `collapse`
138
+
139
+ - `enabled`: добавить collapse-toggle у элементов с дочерним списком.
140
+ - `open`: начальное состояние дочерних списков (`show`/скрыт).
141
+ - `showtext`: контент кнопки в закрытом состоянии.
142
+ - `hidetext`: контент кнопки в открытом состоянии.
143
+
144
+ #### `callbacks`
145
+
146
+ Ключи совпадают с именами событий. Если передана функция, модуль вызывает ее в `_emit(action, payload)`.
147
+
148
+ #### `ajax`
149
+
150
+ - `route`: URL сохранения (если пустой, `save()` вернет payload без запроса).
151
+ - `method`: `post | get | delete`.
152
+ - `field`: имя поля для payload при `post` (по умолчанию `items`).
153
+ - `data`: дополнительные данные для `post`.
154
+ - `timeout`: задержка перед отправкой (мс).
155
+ - `loader`, `once`, `output`: есть в конфиге, но в текущей реализации `save()` не используются.
156
+
157
+ ## Публичное API
158
+
159
+ - `VGNestable.getOrCreateInstance(element, params?)`
160
+ - `VGNestable.getInstance(element)`
161
+ - `instance.refresh()` - пересобирает layout элементов, handle, collapse.
162
+ - `instance.serialize()` - возвращает дерево вида:
163
+
164
+ ```js
165
+ [
166
+ { id: 1, children: [{ id: 11 }] },
167
+ { id: 2 }
168
+ ]
169
+ ```
170
+
171
+ - `instance.save()` - отправляет сериализованные данные на сервер (если указан `ajax.route`), возвращает `Promise`.
172
+ - `instance.dispose()` - снимает listeners, удаляет служебные узлы, unregister из группы.
173
+
174
+ ## События
175
+
176
+ DOM-события триггерятся на корневом элементе модуля:
177
+
178
+ - `vg.nestable.init`
179
+ - `vg.nestable.refresh`
180
+ - `vg.nestable.pointerdown`
181
+ - `vg.nestable.start`
182
+ - `vg.nestable.move`
183
+ - `vg.nestable.placeholdermove`
184
+ - `vg.nestable.drop`
185
+ - `vg.nestable.transfer`
186
+ - `vg.nestable.change`
187
+ - `vg.nestable.end`
188
+ - `vg.nestable.save`
189
+ - `vg.nestable.destroy`
190
+
191
+ Подписка:
192
+
193
+ ```js
194
+ const el = document.querySelector("#myNestable");
195
+ el.addEventListener("vg.nestable.change", (event) => {
196
+ console.log(event.detail.payload);
197
+ });
198
+ ```
199
+
200
+ Полезные поля `event.detail` (зависят от события):
201
+
202
+ - `action`
203
+ - `instance`
204
+ - `item`
205
+ - `payload`
206
+ - `previousPayload`
207
+ - `changed`
208
+ - `targetInstance`
209
+ - `sourcePayload` / `targetPayload` (для transfer)
210
+ - `status`, `response`, `error` (для save)
211
+ - `keyboard`, `cancelled`
212
+
213
+ ## Клавиатурное управление
214
+
215
+ Фокус на handle (или `handleselector`), затем:
216
+
217
+ - `Enter`/`Space`: поднять элемент или завершить drop.
218
+ - `ArrowUp` / `ArrowDown`: перемещение вверх/вниз в текущем списке.
219
+ - `ArrowRight`: сделать вложенным в предыдущий элемент.
220
+ - `ArrowLeft`: уменьшить уровень вложенности.
221
+ - `Escape`: отмена текущей keyboard-сессии.
222
+
223
+ ## Межсписковый перенос
224
+
225
+ Чтобы переносить элементы между списками:
226
+
227
+ 1. У обоих инстансов должен быть одинаковый `group`.
228
+ 2. У обоих `connect: true`.
229
+ 3. (Опционально) `accept` у target должен вернуть `true`.
230
+
231
+ При переносе:
232
+
233
+ - исходный инстанс и целевой получают `transfer`;
234
+ - исходный получает `change`;
235
+ - целевой получает `change` с новым payload;
236
+ - если настроен `ajax.route`, `save()` вызывается для каждого изменившегося инстанса.
237
+
238
+ ## Автосохранение и ручное сохранение
239
+
240
+ Автосохранение происходит после `change`, если указан `ajax.route`.
241
+ Ручной вызов:
242
+
243
+ ```js
244
+ nestable.save()
245
+ .then((response) => console.log("Saved", response))
246
+ .catch((error) => console.error("Save error", error));
247
+ ```
248
+
249
+ При `method: "post"` уходит:
250
+
251
+ ```js
252
+ {
253
+ ...ajax.data,
254
+ [ajax.field]: nestable.serialize()
255
+ }
256
+ ```
257
+
258
+ ## Стили и CSS-переменные
259
+
260
+ Основные классы:
261
+
262
+ - `.vg-nestable-list`
263
+ - `.vg-nestable-item`
264
+ - `.vg-nestable-inner`
265
+ - `.vg-nestable-handle`
266
+ - `.vg-nestable-handle-icon`
267
+ - `.vg-nestable-collapse-toggle`
268
+ - `.vg-nestable-placeholder`
269
+ - `.vg-nestable-drag-element`
270
+ - `.is-drop-target`
271
+ - `.is-drop-denied`
272
+ - `.is-dragging`
273
+ - `.is-drag-ghost`
274
+
275
+ SCSS-переменные заданы в `scss/_variables.scss` через map `nestable` (`--vg-nestable-*`), включая:
276
+
277
+ - размеры handle/toggle;
278
+ - цвета и границы placeholder/drop-state;
279
+ - отступы вложенных списков;
280
+ - стиль ghost-элемента при drag.
281
+
282
+ ## Короткий пример полной инициализации
283
+
284
+ ```js
285
+ VGNestable.getOrCreateInstance("#myNestable", {
286
+ maxdepth: 5,
287
+ indent: 24,
288
+ connect: true,
289
+ group: "menu-builder",
290
+ accept: (item, from, to) => from !== to || item.dataset.locked !== "true",
291
+ collapse: {
292
+ enabled: true,
293
+ open: false
294
+ },
295
+ ajax: {
296
+ route: "/admin/menu/reorder",
297
+ method: "post",
298
+ field: "items",
299
+ data: { menu_id: 15 },
300
+ timeout: 100
301
+ },
302
+ callbacks: {
303
+ change: ({ payload }) => console.log("Tree changed:", payload),
304
+ save: ({ status, response, error }) => console.log("Save:", status, response || error)
305
+ }
306
+ });
307
+ ```