@urbanstudio/ua-sortable 1.0.0 → 1.0.1
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 +12 -0
- package/dist/ua-sortable.js +24 -20
- package/package.json +2 -2
- package/src/Sortable.js +35 -35
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,18 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.0.1] — 2026-06-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Drag direction detection: `direction: "auto"` now checks `display:flex/inline-flex` before reading `flex-direction` — previously non-flex containers were incorrectly detected as `horizontal` (CSS default value for `flex-direction` is `row` even on block elements)
|
|
14
|
+
- Placeholder now visible during drag as a dashed drop-indicator box (was `visibility:hidden`)
|
|
15
|
+
- `setPointerCapture` is now called before `pointer-events:none` is applied — fixes drag-down failing in some browsers
|
|
16
|
+
- Text selection during drag suppressed via `document.body.style.userSelect = "none"`
|
|
17
|
+
- Cross-frame compatibility: `instanceof HTMLElement` replaced with `nodeType === 1` check
|
|
18
|
+
- Original element is now used as its own drag ghost (no `cloneNode`) — renders with all inherited CSS intact
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
10
22
|
## [1.0.0] — 2026-06-05
|
|
11
23
|
|
|
12
24
|
### Added
|
package/dist/ua-sortable.js
CHANGED
|
@@ -38,13 +38,15 @@
|
|
|
38
38
|
#containerElement = null;
|
|
39
39
|
#options = {};
|
|
40
40
|
#draggableElements = [];
|
|
41
|
-
#ghostElement = null;
|
|
42
41
|
#draggedElement = null;
|
|
43
42
|
#placeholderElement = null;
|
|
44
43
|
#sourceContainerElement = null;
|
|
45
44
|
#currentContainerElement = null;
|
|
46
45
|
#pointerStartX = 0;
|
|
47
46
|
#pointerStartY = 0;
|
|
47
|
+
#dragOffsetX = 0;
|
|
48
|
+
#dragOffsetY = 0;
|
|
49
|
+
#savedDragStyle = "";
|
|
48
50
|
#delayTimer = null;
|
|
49
51
|
#isDragging = false;
|
|
50
52
|
#childObserver = null;
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
#boundHandlePointerCancel = null;
|
|
55
57
|
|
|
56
58
|
constructor(containerElement, options = {}) {
|
|
57
|
-
if (!
|
|
59
|
+
if (!containerElement || containerElement.nodeType !== 1) {
|
|
58
60
|
throw new Error("UA_Sortable: first argument must be an HTMLElement");
|
|
59
61
|
}
|
|
60
62
|
if (UA_Sortable.#instanceRegistry.has(containerElement)) {
|
|
@@ -212,17 +214,18 @@
|
|
|
212
214
|
this.#sourceContainerElement = this.#containerElement;
|
|
213
215
|
this.#currentContainerElement = this.#containerElement;
|
|
214
216
|
const r = dragged.getBoundingClientRect();
|
|
215
|
-
this.#
|
|
216
|
-
this.#
|
|
217
|
-
this.#ghostElement.style.cssText = `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;height:${r.height}px;margin:0;pointer-events:none;z-index:9999;`;
|
|
218
|
-
document.body.appendChild(this.#ghostElement);
|
|
217
|
+
this.#dragOffsetX = e.clientX - r.left;
|
|
218
|
+
this.#dragOffsetY = e.clientY - r.top;
|
|
219
219
|
this.#placeholderElement = document.createElement(dragged.tagName);
|
|
220
|
-
this.#placeholderElement.
|
|
220
|
+
this.#placeholderElement.classList.add("ua-sortable-placeholder");
|
|
221
|
+
this.#placeholderElement.style.cssText = `width:${r.width}px;height:${r.height}px;pointer-events:none;`;
|
|
221
222
|
dragged.parentNode.insertBefore(this.#placeholderElement, dragged);
|
|
223
|
+
try { dragged.setPointerCapture(e.pointerId); } catch (_) {}
|
|
224
|
+
this.#savedDragStyle = dragged.style.cssText;
|
|
225
|
+
dragged.style.cssText = `position:fixed;left:${r.left}px;top:${r.top}px;width:${r.width}px;margin:0;z-index:9999;pointer-events:none;`;
|
|
222
226
|
dragged.classList.add(this.#options.dragClass);
|
|
223
|
-
dragged.style.opacity = "0.001";
|
|
224
227
|
this.#containerElement.classList.add("ua-sortable-active");
|
|
225
|
-
|
|
228
|
+
document.body.style.userSelect = "none";
|
|
226
229
|
document.addEventListener("pointermove", this.#boundHandlePointerMove);
|
|
227
230
|
document.addEventListener("pointerup", this.#boundHandlePointerUp);
|
|
228
231
|
document.addEventListener("pointercancel", this.#boundHandlePointerCancel);
|
|
@@ -230,10 +233,8 @@
|
|
|
230
233
|
}
|
|
231
234
|
#handlePointerMove(e) {
|
|
232
235
|
if (!this.#isDragging) return;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
this.#ghostElement.style.left = `${or.left + dx}px`;
|
|
236
|
-
this.#ghostElement.style.top = `${or.top + dy}px`;
|
|
236
|
+
this.#draggedElement.style.left = `${e.clientX - this.#dragOffsetX}px`;
|
|
237
|
+
this.#draggedElement.style.top = `${e.clientY - this.#dragOffsetY}px`;
|
|
237
238
|
const tc = this.#findTargetContainer(e.clientX, e.clientY);
|
|
238
239
|
if (tc && tc !== this.#currentContainerElement) {
|
|
239
240
|
this.#currentContainerElement.classList.remove("ua-sortable-active");
|
|
@@ -281,28 +282,28 @@
|
|
|
281
282
|
return await Promise.resolve(this.#options.onMove(id, from, to, fromIds, toIds)) !== false;
|
|
282
283
|
}
|
|
283
284
|
#cleanupDragState() {
|
|
284
|
-
this.#ghostElement?.remove();
|
|
285
285
|
this.#placeholderElement?.remove();
|
|
286
286
|
if (this.#draggedElement) {
|
|
287
|
+
this.#draggedElement.style.cssText = this.#savedDragStyle;
|
|
287
288
|
this.#draggedElement.classList.remove(this.#options.dragClass);
|
|
288
|
-
this.#draggedElement.style.opacity = "";
|
|
289
289
|
}
|
|
290
290
|
this.#containerElement.classList.remove("ua-sortable-active");
|
|
291
291
|
if (this.#currentContainerElement && this.#currentContainerElement !== this.#containerElement) {
|
|
292
292
|
this.#currentContainerElement.classList.remove("ua-sortable-active");
|
|
293
293
|
}
|
|
294
294
|
clearTimeout(this.#delayTimer);
|
|
295
|
+
document.body.style.userSelect = "";
|
|
295
296
|
document.removeEventListener("pointermove", this.#boundHandlePointerMove);
|
|
296
297
|
document.removeEventListener("pointerup", this.#boundHandlePointerUp);
|
|
297
298
|
document.removeEventListener("pointercancel", this.#boundHandlePointerCancel);
|
|
298
|
-
this.#
|
|
299
|
+
this.#placeholderElement = this.#draggedElement =
|
|
299
300
|
this.#sourceContainerElement = this.#currentContainerElement = this.#delayTimer = null;
|
|
300
301
|
this.#isDragging = false;
|
|
301
302
|
}
|
|
302
303
|
#updatePlaceholderPosition(px, py) {
|
|
303
304
|
const tc = this.#currentContainerElement;
|
|
304
305
|
const children = [...tc.children].filter(c =>
|
|
305
|
-
c !== this.#draggedElement &&
|
|
306
|
+
c !== this.#draggedElement &&
|
|
306
307
|
c !== this.#placeholderElement &&
|
|
307
308
|
(!this.#options.filter || !c.matches(this.#options.filter))
|
|
308
309
|
);
|
|
@@ -317,8 +318,11 @@
|
|
|
317
318
|
}
|
|
318
319
|
#resolveDirection(el) {
|
|
319
320
|
if (this.#options.direction !== "auto") return this.#options.direction;
|
|
320
|
-
const
|
|
321
|
-
|
|
321
|
+
const style = getComputedStyle(el);
|
|
322
|
+
if (style.display === "flex" || style.display === "inline-flex") {
|
|
323
|
+
return (style.flexDirection === "row" || style.flexDirection === "row-reverse") ? "horizontal" : "vertical";
|
|
324
|
+
}
|
|
325
|
+
return "vertical";
|
|
322
326
|
}
|
|
323
327
|
#findDraggableParent(el) {
|
|
324
328
|
let c = el;
|
|
@@ -344,7 +348,7 @@
|
|
|
344
348
|
if (typeof document !== "undefined" && !document.getElementById("ua-sortable-styles")) {
|
|
345
349
|
const s = document.createElement("style");
|
|
346
350
|
s.id = "ua-sortable-styles";
|
|
347
|
-
s.textContent = ".ua-sortable-
|
|
351
|
+
s.textContent = ".ua-sortable-drag{opacity:.95;box-shadow:0 8px 24px rgba(0,0,0,.18);transition:box-shadow .15s;}.ua-sortable-placeholder{border:2px dashed rgba(0,0,0,.18);border-radius:3px;box-sizing:border-box;background:rgba(0,0,0,.03);}.ua-sortable-active>.ua-sortable-over{border-top:2px solid var(--accent,#2563eb);}.ua-drag-handle{cursor:grab;touch-action:none;}.ua-drag-handle:active{cursor:grabbing;}";
|
|
348
352
|
document.head.appendChild(s);
|
|
349
353
|
}
|
|
350
354
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urbanstudio/ua-sortable",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Pointer-Events-based drag-and-drop sorting for lists and grids. No dependencies.",
|
|
5
5
|
"author": "Marian Feiler <mf@urbanstudio.de> (https://urbanstudio.de)",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/urbanstudioGmbH/ua-sortable#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/urbanstudioGmbH/ua-sortable.git"
|
|
10
|
+
"url": "git+https://github.com/urbanstudioGmbH/ua-sortable.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/urbanstudioGmbH/ua-sortable/issues"
|
package/src/Sortable.js
CHANGED
|
@@ -21,13 +21,15 @@ export class UA_Sortable {
|
|
|
21
21
|
#containerElement = null;
|
|
22
22
|
#options = {};
|
|
23
23
|
#draggableElements = [];
|
|
24
|
-
#ghostElement = null;
|
|
25
24
|
#draggedElement = null;
|
|
26
25
|
#placeholderElement = null;
|
|
27
26
|
#sourceContainerElement = null;
|
|
28
27
|
#currentContainerElement = null;
|
|
29
28
|
#pointerStartX = 0;
|
|
30
29
|
#pointerStartY = 0;
|
|
30
|
+
#dragOffsetX = 0;
|
|
31
|
+
#dragOffsetY = 0;
|
|
32
|
+
#savedDragStyle = "";
|
|
31
33
|
#delayTimer = null;
|
|
32
34
|
#isDragging = false;
|
|
33
35
|
#childObserver = null;
|
|
@@ -43,7 +45,7 @@ export class UA_Sortable {
|
|
|
43
45
|
* @param {object} options
|
|
44
46
|
*/
|
|
45
47
|
constructor(containerElement, options = {}) {
|
|
46
|
-
if (!
|
|
48
|
+
if (!containerElement || containerElement.nodeType !== 1) {
|
|
47
49
|
throw new Error("UA_Sortable: first argument must be an HTMLElement");
|
|
48
50
|
}
|
|
49
51
|
if (UA_Sortable.#instanceRegistry.has(containerElement)) {
|
|
@@ -309,35 +311,36 @@ export class UA_Sortable {
|
|
|
309
311
|
this.#currentContainerElement = this.#containerElement;
|
|
310
312
|
|
|
311
313
|
const boundingRect = draggedElement.getBoundingClientRect();
|
|
312
|
-
this.#ghostElement = draggedElement.cloneNode(true);
|
|
313
|
-
this.#ghostElement.classList.add(this.#options.ghostClass);
|
|
314
|
-
this.#ghostElement.style.cssText = [
|
|
315
|
-
"position:fixed",
|
|
316
|
-
`left:${boundingRect.left}px`,
|
|
317
|
-
`top:${boundingRect.top}px`,
|
|
318
|
-
`width:${boundingRect.width}px`,
|
|
319
|
-
`height:${boundingRect.height}px`,
|
|
320
|
-
"margin:0",
|
|
321
|
-
"pointer-events:none",
|
|
322
|
-
"z-index:9999",
|
|
323
|
-
].join(";");
|
|
324
|
-
document.body.appendChild(this.#ghostElement);
|
|
325
314
|
|
|
315
|
+
this.#dragOffsetX = pointerEvent.clientX - boundingRect.left;
|
|
316
|
+
this.#dragOffsetY = pointerEvent.clientY - boundingRect.top;
|
|
317
|
+
|
|
318
|
+
// Placeholder keeps layout space — visible drop indicator
|
|
326
319
|
this.#placeholderElement = document.createElement(draggedElement.tagName);
|
|
320
|
+
this.#placeholderElement.classList.add("ua-sortable-placeholder");
|
|
327
321
|
this.#placeholderElement.style.cssText = [
|
|
328
322
|
`width:${boundingRect.width}px`,
|
|
329
323
|
`height:${boundingRect.height}px`,
|
|
330
|
-
"opacity:0",
|
|
331
324
|
"pointer-events:none",
|
|
332
325
|
].join(";");
|
|
333
326
|
draggedElement.parentNode.insertBefore(this.#placeholderElement, draggedElement);
|
|
334
327
|
|
|
328
|
+
try { draggedElement.setPointerCapture(pointerEvent.pointerId); } catch (_) {}
|
|
329
|
+
|
|
330
|
+
this.#savedDragStyle = draggedElement.style.cssText;
|
|
331
|
+
draggedElement.style.cssText = [
|
|
332
|
+
"position:fixed",
|
|
333
|
+
`left:${boundingRect.left}px`,
|
|
334
|
+
`top:${boundingRect.top}px`,
|
|
335
|
+
`width:${boundingRect.width}px`,
|
|
336
|
+
"margin:0",
|
|
337
|
+
"z-index:9999",
|
|
338
|
+
"pointer-events:none",
|
|
339
|
+
].join(";");
|
|
335
340
|
draggedElement.classList.add(this.#options.dragClass);
|
|
336
|
-
draggedElement.style.opacity = "0.001";
|
|
337
341
|
|
|
338
342
|
this.#containerElement.classList.add("ua-sortable-active");
|
|
339
|
-
|
|
340
|
-
try { draggedElement.setPointerCapture(pointerEvent.pointerId); } catch (_) {}
|
|
343
|
+
document.body.style.userSelect = "none";
|
|
341
344
|
|
|
342
345
|
document.addEventListener("pointermove", this.#boundHandlePointerMove);
|
|
343
346
|
document.addEventListener("pointerup", this.#boundHandlePointerUp);
|
|
@@ -349,12 +352,8 @@ export class UA_Sortable {
|
|
|
349
352
|
#handlePointerMove(pointerEvent) {
|
|
350
353
|
if (!this.#isDragging) return;
|
|
351
354
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const originalRect = this.#draggedElement.getBoundingClientRect();
|
|
356
|
-
this.#ghostElement.style.left = `${originalRect.left + deltaX}px`;
|
|
357
|
-
this.#ghostElement.style.top = `${originalRect.top + deltaY}px`;
|
|
355
|
+
this.#draggedElement.style.left = `${pointerEvent.clientX - this.#dragOffsetX}px`;
|
|
356
|
+
this.#draggedElement.style.top = `${pointerEvent.clientY - this.#dragOffsetY}px`;
|
|
358
357
|
|
|
359
358
|
const targetContainer = this.#findTargetContainer(pointerEvent.clientX, pointerEvent.clientY);
|
|
360
359
|
if (targetContainer && targetContainer !== this.#currentContainerElement) {
|
|
@@ -454,12 +453,11 @@ export class UA_Sortable {
|
|
|
454
453
|
}
|
|
455
454
|
|
|
456
455
|
#cleanupDragState() {
|
|
457
|
-
this.#ghostElement?.remove();
|
|
458
456
|
this.#placeholderElement?.remove();
|
|
459
457
|
|
|
460
458
|
if (this.#draggedElement) {
|
|
459
|
+
this.#draggedElement.style.cssText = this.#savedDragStyle;
|
|
461
460
|
this.#draggedElement.classList.remove(this.#options.dragClass);
|
|
462
|
-
this.#draggedElement.style.opacity = "";
|
|
463
461
|
}
|
|
464
462
|
|
|
465
463
|
this.#containerElement.classList.remove("ua-sortable-active");
|
|
@@ -468,11 +466,11 @@ export class UA_Sortable {
|
|
|
468
466
|
}
|
|
469
467
|
|
|
470
468
|
clearTimeout(this.#delayTimer);
|
|
469
|
+
document.body.style.userSelect = "";
|
|
471
470
|
document.removeEventListener("pointermove", this.#boundHandlePointerMove);
|
|
472
471
|
document.removeEventListener("pointerup", this.#boundHandlePointerUp);
|
|
473
472
|
document.removeEventListener("pointercancel", this.#boundHandlePointerCancel);
|
|
474
473
|
|
|
475
|
-
this.#ghostElement = null;
|
|
476
474
|
this.#placeholderElement = null;
|
|
477
475
|
this.#draggedElement = null;
|
|
478
476
|
this.#sourceContainerElement = null;
|
|
@@ -489,7 +487,6 @@ export class UA_Sortable {
|
|
|
489
487
|
const targetContainer = this.#currentContainerElement;
|
|
490
488
|
const children = [...targetContainer.children].filter(child =>
|
|
491
489
|
child !== this.#draggedElement &&
|
|
492
|
-
child !== this.#ghostElement &&
|
|
493
490
|
child !== this.#placeholderElement &&
|
|
494
491
|
(!this.#options.filter || !child.matches(this.#options.filter))
|
|
495
492
|
);
|
|
@@ -519,10 +516,13 @@ export class UA_Sortable {
|
|
|
519
516
|
|
|
520
517
|
#resolveDirection(containerElement) {
|
|
521
518
|
if (this.#options.direction !== "auto") return this.#options.direction;
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
519
|
+
const style = getComputedStyle(containerElement);
|
|
520
|
+
if (style.display === "flex" || style.display === "inline-flex") {
|
|
521
|
+
return (style.flexDirection === "row" || style.flexDirection === "row-reverse")
|
|
522
|
+
? "horizontal"
|
|
523
|
+
: "vertical";
|
|
524
|
+
}
|
|
525
|
+
return "vertical";
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
#findDraggableParent(targetElement) {
|
|
@@ -563,8 +563,8 @@ export class UA_Sortable {
|
|
|
563
563
|
const style = document.createElement("style");
|
|
564
564
|
style.id = "ua-sortable-styles";
|
|
565
565
|
style.textContent = [
|
|
566
|
-
".ua-sortable-
|
|
567
|
-
".ua-sortable-
|
|
566
|
+
".ua-sortable-drag{opacity:.95;box-shadow:0 8px 24px rgba(0,0,0,.18);transition:box-shadow .15s;}",
|
|
567
|
+
".ua-sortable-placeholder{border:2px dashed rgba(0,0,0,.18);border-radius:3px;box-sizing:border-box;background:rgba(0,0,0,.03);}",
|
|
568
568
|
".ua-sortable-active>.ua-sortable-over{border-top:2px solid var(--accent,#2563eb);}",
|
|
569
569
|
".ua-drag-handle{cursor:grab;touch-action:none;}",
|
|
570
570
|
".ua-drag-handle:active{cursor:grabbing;}",
|