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.
- package/CHANGELOG.md +23 -5
- package/README.md +19 -16
- package/agents.md +6 -0
- package/app/langs/en/buttons.json +17 -2
- package/app/langs/en/messages.json +36 -1
- package/app/langs/ru/buttons.json +17 -2
- package/app/langs/ru/messages.json +69 -34
- package/app/modules/module-fn.js +15 -9
- package/app/modules/vgfilepreview/index.js +3 -0
- package/app/modules/vgfilepreview/js/i18n.js +56 -0
- package/app/modules/vgfilepreview/js/renderers/image-modal.js +145 -0
- package/app/modules/vgfilepreview/js/renderers/image.js +92 -0
- package/app/modules/vgfilepreview/js/renderers/index.js +19 -0
- package/app/modules/vgfilepreview/js/renderers/office-modal.js +168 -0
- package/app/modules/vgfilepreview/js/renderers/office.js +79 -0
- package/app/modules/vgfilepreview/js/renderers/pdf-modal.js +260 -0
- package/app/modules/vgfilepreview/js/renderers/pdf.js +76 -0
- package/app/modules/vgfilepreview/js/renderers/playlist.js +71 -0
- package/app/modules/vgfilepreview/js/renderers/text-modal.js +343 -0
- package/app/modules/vgfilepreview/js/renderers/text.js +83 -0
- package/app/modules/vgfilepreview/js/renderers/video-modal.js +272 -0
- package/app/modules/vgfilepreview/js/renderers/video.js +80 -0
- package/app/modules/vgfilepreview/js/renderers/zip-modal.js +522 -0
- package/app/modules/vgfilepreview/js/renderers/zip.js +89 -0
- package/app/modules/vgfilepreview/js/vgfilepreview.js +594 -0
- package/app/modules/vgfilepreview/readme.md +68 -0
- package/app/modules/vgfilepreview/scss/_variables.scss +113 -0
- package/app/modules/vgfilepreview/scss/vgfilepreview.scss +460 -0
- package/app/modules/vgfiles/js/base.js +463 -175
- package/app/modules/vgfiles/js/droppable.js +260 -260
- package/app/modules/vgfiles/js/render.js +153 -153
- package/app/modules/vgfiles/js/vgfiles.js +41 -29
- package/app/modules/vgfiles/readme.md +116 -217
- package/app/modules/vgfiles/scss/_variables.scss +18 -10
- package/app/modules/vgfiles/scss/vgfiles.scss +153 -59
- package/app/modules/vgformsender/js/vgformsender.js +13 -13
- package/app/modules/vgmodal/js/vgmodal.js +12 -0
- package/app/modules/vgnav/js/vgnav.js +135 -135
- package/app/modules/vgnav/readme.md +67 -67
- package/app/modules/vgnestable/README.md +307 -307
- package/app/modules/vgnestable/scss/_variables.scss +60 -60
- package/app/modules/vgnestable/scss/vgnestable.scss +163 -163
- package/app/modules/vgselect/js/vgselect.js +39 -39
- package/app/modules/vgselect/scss/vgselect.scss +22 -22
- package/app/modules/vgspy/readme.md +28 -28
- package/app/utils/js/components/audio-metadata.js +240 -0
- package/app/utils/js/components/file-icon.js +109 -0
- package/app/utils/js/components/file-preview.js +304 -0
- package/app/utils/js/components/sanitize.js +150 -150
- package/build/vgapp.css +1 -1
- package/build/vgapp.css.map +1 -1
- package/build/vgapp.js.map +1 -1
- package/index.js +1 -0
- package/index.scss +9 -6
- 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
|
+
```
|