@milaboratories/uikit 2.2.36 → 2.2.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@milaboratories/uikit",
3
- "version": "2.2.36",
3
+ "version": "2.2.37",
4
4
  "type": "module",
5
5
  "main": "dist/pl-uikit.umd.js",
6
6
  "module": "dist/pl-uikit.js",
@@ -32,9 +32,9 @@
32
32
  "vue-tsc": "^2.1.10",
33
33
  "yarpm": "^1.2.0",
34
34
  "svgo": "^3.3.2",
35
+ "@milaboratories/helpers": "^1.6.11",
35
36
  "@platforma-sdk/model": "^1.20.11",
36
- "@milaboratories/eslint-config": "^1.0.0",
37
- "@milaboratories/helpers": "^1.6.11"
37
+ "@milaboratories/eslint-config": "^1.0.0"
38
38
  },
39
39
  "scripts": {
40
40
  "dev": "vite",
@@ -0,0 +1,282 @@
1
+ import type { Ref } from 'vue';
2
+ import { computed, watch, watchEffect } from 'vue';
3
+ import { useEventListener } from './useEventListener';
4
+
5
+ type SortableItem = {
6
+ el: HTMLElement;
7
+ y: number;
8
+ dy: number;
9
+ orderChanged: boolean;
10
+ initialScrollTop: number;
11
+ };
12
+
13
+ export type SortableSettings = {
14
+ onChange: (indices: number[]) => void;
15
+ handle?: string;
16
+ shakeBuffer?: number;
17
+ reorderDelay?: number;
18
+ transitionDelay?: string;
19
+ };
20
+
21
+ const classes = {
22
+ item: 'sortable__item',
23
+ animate: 'sortable__animate',
24
+ };
25
+
26
+ const getOffset = (el: HTMLElement) => {
27
+ return el.getBoundingClientRect().y;
28
+ };
29
+
30
+ const getMiddle = (el: HTMLElement) => {
31
+ const { y, height } = el.getBoundingClientRect();
32
+ return y + Math.ceil(height / 2);
33
+ };
34
+
35
+ const getBottom = (el: HTMLElement) => {
36
+ const { y, height } = el.getBoundingClientRect();
37
+ return y + height;
38
+ };
39
+
40
+ /**
41
+ * Description: Scroll support has been added to the container where sorting takes place.
42
+ * Some functionality duplicates the behavior of useScrollable.
43
+ *
44
+ * Purpose: To enable automatic scrolling when dragging items beyond the visible area of the container.
45
+ * Future Plan:
46
+ * - Verify the behavior of the new scrolling functionality within blocks and text elements.
47
+ * - Merge the current implementation with useScrollable to eliminate code duplication and create a unified scrolling solution.
48
+ */
49
+ export function useSortable2(listRef: Ref<HTMLElement | undefined>, settings: SortableSettings) {
50
+ const state = {
51
+ item: undefined as SortableItem | undefined,
52
+ options() {
53
+ return [...(listRef.value?.children ?? [])] as HTMLElement[];
54
+ },
55
+ };
56
+ let oldScrollTop = 0;
57
+
58
+ watch(() => listRef.value, () => {
59
+ setTimeout(() => {
60
+ if (listRef.value) {
61
+ listRef.value.scrollTop = oldScrollTop;
62
+ }
63
+ }, 0);
64
+ });
65
+
66
+ const optionsRef = computed(() => {
67
+ return state.options();
68
+ });
69
+
70
+ const shakeBuffer = settings.shakeBuffer ?? 10;
71
+
72
+ const reorderDelay = settings.reorderDelay ?? 100;
73
+
74
+ function mouseDown(this: HTMLElement, e: { y: number; target: EventTarget | null }) {
75
+ const handle = settings.handle ? this.querySelector(settings.handle) : null;
76
+
77
+ if (!handle) {
78
+ return;
79
+ }
80
+
81
+ if (handle && !handle.contains(e.target as HTMLElement)) {
82
+ return;
83
+ }
84
+
85
+ this.classList.remove(classes.animate);
86
+ this.classList.add(classes.item);
87
+
88
+ state.item = {
89
+ el: this,
90
+ y: e.y,
91
+ dy: 0,
92
+ initialScrollTop: listRef.value?.scrollTop || 0,
93
+ orderChanged: false,
94
+ };
95
+ }
96
+
97
+ function elementsBefore(el: HTMLElement) {
98
+ const options = state.options();
99
+ return options.slice(0, options.indexOf(el));
100
+ }
101
+
102
+ function elementsAfter(el: HTMLElement) {
103
+ const children = state.options();
104
+ return children.slice(children.indexOf(el) + 1);
105
+ }
106
+
107
+ function insertBefore(before: HTMLElement, el: HTMLElement) {
108
+ const children = state.options().filter((e) => e !== el);
109
+ const index = children.indexOf(before);
110
+ children.splice(index, 0, el);
111
+ return children;
112
+ }
113
+
114
+ function insertAfter(after: HTMLElement, el: HTMLElement) {
115
+ const children = state.options().filter((e) => e !== el);
116
+ const index = children.indexOf(after);
117
+ children.splice(index + 1, 0, el);
118
+ return children;
119
+ }
120
+
121
+ function updatePosition(item: SortableItem, y: number) {
122
+ const currentScrollTop = listRef.value?.scrollTop || 0;
123
+ const scrollDiff = currentScrollTop - (item.initialScrollTop || 0);
124
+ item.dy = y - item.y + scrollDiff;
125
+ item.el.style.setProperty('transform', `translateY(${item.dy}px)`);
126
+ }
127
+
128
+ function changeOrder(reordered: HTMLElement[]) {
129
+ if (!state.item) {
130
+ return;
131
+ }
132
+
133
+ const { el } = state.item;
134
+
135
+ if (!el.isConnected) {
136
+ state.item = undefined;
137
+ return;
138
+ }
139
+
140
+ const oldPositions = reordered.map((e) => getOffset(e));
141
+
142
+ const y1 = getOffset(el);
143
+ listRef.value?.replaceChildren(...reordered);
144
+ const y2 = getOffset(el);
145
+
146
+ const newPositions = reordered.map((e) => getOffset(e));
147
+
148
+ const toAnimate: HTMLElement[] = [];
149
+
150
+ for (let i = 0; i < newPositions.length; i++) {
151
+ const option = reordered[i];
152
+
153
+ if (option === state.item.el) {
154
+ continue;
155
+ }
156
+
157
+ const newY = newPositions[i];
158
+
159
+ const oldY = oldPositions[i];
160
+
161
+ const invert = oldY - newY;
162
+
163
+ option.style.transform = `translateY(${invert}px)`;
164
+
165
+ toAnimate.push(option);
166
+ }
167
+
168
+ const dy = y2 - y1;
169
+
170
+ state.item.y = state.item.y + dy;
171
+ state.item.dy = state.item.dy - dy;
172
+ state.item.orderChanged = true;
173
+ state.item.el.style.setProperty('transform', `translateY(${state.item.dy}px)`);
174
+
175
+ toAnimate.forEach((o) => o.classList.remove(classes.animate));
176
+
177
+ requestAnimationFrame(function () {
178
+ toAnimate.forEach((option) => {
179
+ option.classList.add(classes.animate);
180
+ option.style.transform = '';
181
+ option.addEventListener('transitionend', () => {
182
+ option.classList.remove(classes.animate);
183
+ });
184
+ });
185
+ });
186
+ }
187
+
188
+ useEventListener(window, 'mousemove', (e: { y: number }) => {
189
+ if (!state.item) {
190
+ return;
191
+ }
192
+
193
+ const { el } = state.item;
194
+
195
+ updatePosition(state.item, e.y);
196
+
197
+ const upper = getOffset(state.item.el);
198
+ const bottom = getBottom(state.item.el);
199
+
200
+ const before = elementsBefore(el);
201
+ const after = elementsAfter(el);
202
+
203
+ before.forEach((e) => {
204
+ const y = getMiddle(e);
205
+
206
+ if (upper + shakeBuffer < y) {
207
+ changeOrder(insertBefore(e, el));
208
+ }
209
+ });
210
+
211
+ after.forEach((e) => {
212
+ const y = getMiddle(e);
213
+
214
+ if (bottom - shakeBuffer > y) {
215
+ changeOrder(insertAfter(e, el));
216
+ }
217
+ });
218
+
219
+ if (listRef.value) {
220
+ const rect = listRef.value.getBoundingClientRect();
221
+
222
+ const deltaUp = rect.top + el.getBoundingClientRect().height / 2;
223
+ if ((e as MouseEvent).clientY < deltaUp) {
224
+ listRef.value.scrollTop += (e as MouseEvent).clientY - deltaUp;
225
+ }
226
+
227
+ const deltaDown = rect.bottom - el.getBoundingClientRect().height / 2;
228
+ if ((e as MouseEvent).clientY > deltaDown) {
229
+ listRef.value.scrollTop += (e as MouseEvent).clientY - deltaDown;
230
+ }
231
+ }
232
+ });
233
+
234
+ useEventListener(window, 'mouseup', () => {
235
+ if (!state.item) {
236
+ return;
237
+ }
238
+
239
+ oldScrollTop = listRef.value?.scrollTop || 0;
240
+
241
+ const { el, orderChanged } = state.item;
242
+
243
+ el.classList.add(classes.animate);
244
+ el.style.removeProperty('transform');
245
+
246
+ el.addEventListener('transitionend', () => {
247
+ el.classList.remove(classes.animate, classes.item);
248
+ });
249
+
250
+ setTimeout(() => {
251
+ if (!orderChanged) {
252
+ return;
253
+ }
254
+
255
+ const newIndices = state.options().map((o) => Number(o.getAttribute('data-index')));
256
+
257
+ const list = listRef.value;
258
+
259
+ if (list) {
260
+ for (const child of state.options()) {
261
+ list.removeChild(child);
262
+ }
263
+
264
+ optionsRef.value.forEach((child) => {
265
+ list.appendChild(child);
266
+ });
267
+ }
268
+
269
+ settings.onChange(newIndices);
270
+ }, reorderDelay);
271
+
272
+ state.item = undefined;
273
+ });
274
+
275
+ watchEffect(() => {
276
+ optionsRef.value.forEach((child, i) => {
277
+ child.removeEventListener('mousedown', mouseDown);
278
+ child.addEventListener('mousedown', mouseDown);
279
+ child.setAttribute('data-index', String(i));
280
+ });
281
+ });
282
+ }
package/src/index.ts CHANGED
@@ -82,6 +82,7 @@ export { useMouseCapture } from './composition/useMouseCapture';
82
82
  export { useHover } from './composition/useHover';
83
83
  export { useMouse } from './composition/useMouse';
84
84
  export { useSortable } from './composition/useSortable';
85
+ export { useSortable2 } from './composition/useSortable2';
85
86
  export { useInterval } from './composition/useInterval';
86
87
  export { useFormState } from './composition/useFormState';
87
88
  export { useQuery } from './composition/useQuery.ts';