tiny-essentials 1.12.0 → 1.12.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.
@@ -4204,6 +4204,7 @@ class TinyDragger {
4204
4204
  #offsetY = 0;
4205
4205
  #offsetX = 0;
4206
4206
 
4207
+ #multiCollision = false;
4207
4208
  #lockInsideJail = false;
4208
4209
  #revertOnDrop = false;
4209
4210
  #dragging = false;
@@ -4248,6 +4249,7 @@ class TinyDragger {
4248
4249
  * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
4249
4250
  * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
4250
4251
  * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
4252
+ * @param {boolean} [options.multiCollision=false] - Enables returning multiple collided elements.
4251
4253
  * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
4252
4254
  * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
4253
4255
  * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
@@ -4294,6 +4296,7 @@ class TinyDragger {
4294
4296
  validateBoolean(options.lockInsideJail, 'lockInsideJail');
4295
4297
  validateBoolean(options.dropInJailOnly, 'dropInJailOnly');
4296
4298
  validateBoolean(options.revertOnDrop, 'revertOnDrop');
4299
+ validateBoolean(options.multiCollision, 'multiCollision');
4297
4300
 
4298
4301
  validateString(options.classDragging, 'classDragging');
4299
4302
  validateString(options.classBodyDragging, 'classBodyDragging');
@@ -4327,6 +4330,7 @@ class TinyDragger {
4327
4330
  if (typeof options.revertOnDrop === 'boolean') this.#revertOnDrop = options.revertOnDrop;
4328
4331
  if (typeof options.lockInsideJail === 'boolean') this.#lockInsideJail = options.lockInsideJail;
4329
4332
  if (typeof options.dropInJailOnly === 'boolean') this.#dropInJailOnly = options.dropInJailOnly;
4333
+ if (typeof options.multiCollision === 'boolean') this.#multiCollision = options.multiCollision;
4330
4334
 
4331
4335
  /** @private */
4332
4336
  this._onMouseDown = this.#startDrag.bind(this);
@@ -4518,22 +4522,67 @@ class TinyDragger {
4518
4522
  this.#dispatchEvent('drag');
4519
4523
  }
4520
4524
 
4525
+ /** @type {HTMLElement[]} */
4526
+ #collisionsMarked = [];
4527
+
4528
+ /**
4529
+ * Marks an element as currently collided by adding the collision CSS class.
4530
+ * The element is stored in an internal list for easy removal later.
4531
+ *
4532
+ * @param {HTMLElement|null} el - The element to mark as collided.
4533
+ */
4534
+ #addCollision(el) {
4535
+ if (!el) return;
4536
+ el.classList.add(this.#classDragCollision);
4537
+ this.#collisionsMarked.push(el);
4538
+ }
4539
+
4540
+ /**
4541
+ * Removes the collision CSS class from all previously marked elements.
4542
+ * Also clears the last single collision element, if set.
4543
+ *
4544
+ */
4545
+ #removeCollision() {
4546
+ while (this.#collisionsMarked.length > 0) {
4547
+ const el = this.#collisionsMarked.shift();
4548
+ if (el) el.classList.remove(this.#classDragCollision);
4549
+ }
4550
+ if (!this.#lastCollision) return;
4551
+ this.#lastCollision.classList.remove(this.#classDragCollision);
4552
+ }
4553
+
4521
4554
  /**
4522
4555
  * Handles dragging collision.
4523
4556
  * @param {MouseEvent|Touch} event - The drag event.
4524
4557
  */
4525
4558
  checkDragCollision(event) {
4526
- const { collidedElement: collided } = this.execCollision(event);
4527
- if (collided && collided !== this.#lastCollision) {
4528
- if (navigator.vibrate && Array.isArray(this.#vibration.collide)) {
4529
- navigator.vibrate(this.#vibration.collide);
4559
+ const { collidedElements } = this.execCollision(event);
4560
+ const first = collidedElements[0] || null;
4561
+
4562
+ // Removes old marking if necessary
4563
+ if (this.#lastCollision && !collidedElements.includes(this.#lastCollision)) {
4564
+ this.#removeCollision();
4565
+ }
4566
+
4567
+ // Adds Marking for All Colluded
4568
+ collidedElements.forEach((el) => this.#addCollision(el));
4569
+
4570
+ // Removes markings from who no longer collided
4571
+ this.#collidables.forEach((el) => {
4572
+ if (!collidedElements.includes(el)) {
4573
+ el.classList.remove(this.#classDragCollision);
4530
4574
  }
4531
- this.#lastCollision = collided;
4532
- if (this.#lastCollision) this.#lastCollision.classList.add(this.#classDragCollision);
4533
- } else if (!collided) {
4534
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
4535
- this.#lastCollision = null;
4575
+ });
4576
+
4577
+ if (
4578
+ navigator.vibrate &&
4579
+ Array.isArray(this.#vibration.collide) &&
4580
+ collidedElements.length > 0
4581
+ ) {
4582
+ navigator.vibrate(this.#vibration.collide);
4536
4583
  }
4584
+
4585
+ this.#lastCollision = first;
4537
4586
  }
4538
4587
 
4539
4588
  /**
@@ -4580,37 +4629,48 @@ class TinyDragger {
4580
4629
  /**
4581
4630
  * Handles the collision of a drag.
4582
4631
  * @param {MouseEvent|Touch} event - The release event.
4583
- * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
4632
+ * @returns {{ inJail: boolean; collidedElements: (HTMLElement | null)[] }}
4584
4633
  */
4585
4634
  execCollision(event) {
4586
- if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElement: null };
4635
+ if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElements: [] };
4587
4636
 
4588
- let collidedElement;
4637
+ let collidedElements = [];
4589
4638
  let inJail = true;
4590
4639
  const jailRect = this.#jail?.getBoundingClientRect();
4640
+
4591
4641
  if (this.#collisionByMouse) {
4592
4642
  const x = event.clientX;
4593
4643
  const y = event.clientY;
4644
+
4594
4645
  if (this.#dropInJailOnly && this.#jail && jailRect) {
4595
4646
  inJail =
4596
4647
  x >= jailRect.left && x <= jailRect.right && y >= jailRect.top && y <= jailRect.bottom;
4597
4648
  }
4598
4649
 
4599
- collidedElement = inJail ? this.getCollidedElement(x, y) : null;
4650
+ collidedElements = inJail
4651
+ ? this.#multiCollision
4652
+ ? this.getAllCollidedElements(x, y)
4653
+ : [this.getCollidedElement(x, y)].filter(Boolean)
4654
+ : [];
4600
4655
  } else {
4601
- const targetRect = this.#dragProxy.getBoundingClientRect();
4656
+ const rect = this.#dragProxy.getBoundingClientRect();
4657
+
4602
4658
  if (this.#dropInJailOnly && this.#jail && jailRect) {
4603
4659
  inJail =
4604
- targetRect.left >= jailRect.left &&
4605
- targetRect.right <= jailRect.right &&
4606
- targetRect.top >= jailRect.top &&
4607
- targetRect.bottom <= jailRect.bottom;
4660
+ rect.left >= jailRect.left &&
4661
+ rect.right <= jailRect.right &&
4662
+ rect.top >= jailRect.top &&
4663
+ rect.bottom <= jailRect.bottom;
4608
4664
  }
4609
4665
 
4610
- collidedElement = inJail ? this.getCollidedElementByRect(targetRect) : null;
4666
+ collidedElements = inJail
4667
+ ? this.#multiCollision
4668
+ ? this.getAllCollidedElementsByRect(rect)
4669
+ : [this.getCollidedElementByRect(rect)].filter(Boolean)
4670
+ : [];
4611
4671
  }
4612
4672
 
4613
- return { inJail, collidedElement };
4673
+ return { inJail, collidedElements };
4614
4674
  }
4615
4675
 
4616
4676
  /**
@@ -4633,7 +4693,7 @@ class TinyDragger {
4633
4693
 
4634
4694
  document.removeEventListener('touchmove', this._onTouchMove);
4635
4695
  document.removeEventListener('touchend', this._onTouchEnd);
4636
- const { collidedElement } = this.execCollision(event);
4696
+ const { collidedElements } = this.execCollision(event);
4637
4697
 
4638
4698
  if (navigator.vibrate && Array.isArray(this.#vibration.end)) {
4639
4699
  navigator.vibrate(this.#vibration.end);
@@ -4646,7 +4706,7 @@ class TinyDragger {
4646
4706
  this.#dragProxy = null;
4647
4707
  }
4648
4708
 
4649
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
4709
+ if (this.#lastCollision) this.#removeCollision();
4650
4710
  this.#lastCollision = null;
4651
4711
 
4652
4712
  this.#target.classList.remove(this.#dragHiddenClass);
@@ -4657,13 +4717,50 @@ class TinyDragger {
4657
4717
 
4658
4718
  const dropEvent = new CustomEvent('drop', {
4659
4719
  detail: {
4660
- target: collidedElement,
4720
+ targets: collidedElements,
4721
+ first: collidedElements[0] || null,
4661
4722
  },
4662
4723
  });
4663
-
4664
4724
  this.#target.dispatchEvent(dropEvent);
4665
4725
  }
4666
4726
 
4727
+ /**
4728
+ * Checks if the provided element intersects with the given bounding rectangle.
4729
+ *
4730
+ * @param {HTMLElement} el - The element to test for collision.
4731
+ * @param {DOMRect} rect - The bounding rectangle to check against.
4732
+ * @returns {boolean} True if the element intersects with the rectangle.
4733
+ */
4734
+ #getCollidedElementByRect(el, rect) {
4735
+ const elRect = el.getBoundingClientRect();
4736
+ return !(
4737
+ rect.right < elRect.left ||
4738
+ rect.left > elRect.right ||
4739
+ rect.bottom < elRect.top ||
4740
+ rect.top > elRect.bottom
4741
+ );
4742
+ }
4743
+
4744
+ /**
4745
+ * Returns all elements currently colliding with the given rectangle.
4746
+ *
4747
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
4748
+ * @returns {HTMLElement[]} A list of all collided elements.
4749
+ * @throws {Error} If the input is not a valid DOMRect with numeric bounds.
4750
+ */
4751
+ getAllCollidedElementsByRect(rect) {
4752
+ this.#checkDestroy();
4753
+ if (
4754
+ !(rect instanceof DOMRect) ||
4755
+ typeof rect.left !== 'number' ||
4756
+ typeof rect.right !== 'number' ||
4757
+ typeof rect.top !== 'number' ||
4758
+ typeof rect.bottom !== 'number'
4759
+ )
4760
+ throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
4761
+ return this.#collidables.filter((el) => this.#getCollidedElementByRect(el, rect));
4762
+ }
4763
+
4667
4764
  /**
4668
4765
  * Detects collision based on rectangle intersection.
4669
4766
  * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
@@ -4680,18 +4777,32 @@ class TinyDragger {
4680
4777
  typeof rect.bottom !== 'number'
4681
4778
  )
4682
4779
  throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
4780
+ return this.#collidables.find((el) => this.#getCollidedElementByRect(el, rect)) || null;
4781
+ }
4683
4782
 
4684
- return (
4685
- this.#collidables.find((el) => {
4686
- const elRect = el.getBoundingClientRect();
4687
- return !(
4688
- rect.right < elRect.left ||
4689
- rect.left > elRect.right ||
4690
- rect.bottom < elRect.top ||
4691
- rect.top > elRect.bottom
4692
- );
4693
- }) || null
4694
- );
4783
+ /**
4784
+ * Checks whether a given (x, y) coordinate is inside the bounding rectangle of an element.
4785
+ *
4786
+ * @param {HTMLElement} el - The element to test for collision.
4787
+ * @param {number} x - Horizontal screen coordinate.
4788
+ * @param {number} y - Vertical screen coordinate.
4789
+ * @returns {boolean} True if the point is within the element's bounds.
4790
+ */
4791
+ #getCollidedElement(el, x, y) {
4792
+ const rect = el.getBoundingClientRect();
4793
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
4794
+ }
4795
+
4796
+ /**
4797
+ * @param {number} x - Horizontal screen coordinate.
4798
+ * @param {number} y - Vertical screen coordinate.
4799
+ * @returns {HTMLElement[]} The collided element or null.
4800
+ */
4801
+ getAllCollidedElements(x, y) {
4802
+ this.#checkDestroy();
4803
+ if (typeof x !== 'number' || typeof y !== 'number')
4804
+ throw new Error('getCollidedElement expects numeric x and y coordinates.');
4805
+ return this.#collidables.filter((el) => this.#getCollidedElement(el, x, y));
4695
4806
  }
4696
4807
 
4697
4808
  /**
@@ -4704,13 +4815,7 @@ class TinyDragger {
4704
4815
  this.#checkDestroy();
4705
4816
  if (typeof x !== 'number' || typeof y !== 'number')
4706
4817
  throw new Error('getCollidedElement expects numeric x and y coordinates.');
4707
-
4708
- return (
4709
- this.#collidables.find((el) => {
4710
- const rect = el.getBoundingClientRect();
4711
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
4712
- }) || null
4713
- );
4818
+ return this.#collidables.find((el) => this.#getCollidedElement(el, x, y)) || null;
4714
4819
  }
4715
4820
 
4716
4821
  /**
@@ -4960,7 +5065,7 @@ class TinyDragger {
4960
5065
  document.removeEventListener('touchmove', this._onTouchMove);
4961
5066
  document.removeEventListener('touchend', this._onTouchEnd);
4962
5067
 
4963
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
5068
+ if (this.#lastCollision) this.#removeCollision();
4964
5069
  if (this.#dragProxy) {
4965
5070
  this.#dragProxy.remove();
4966
5071
  this.#dragProxy = null;
@@ -23,6 +23,7 @@ class TinyDragger {
23
23
  #offsetY = 0;
24
24
  #offsetX = 0;
25
25
 
26
+ #multiCollision = false;
26
27
  #lockInsideJail = false;
27
28
  #revertOnDrop = false;
28
29
  #dragging = false;
@@ -67,6 +68,7 @@ class TinyDragger {
67
68
  * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
68
69
  * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
69
70
  * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
71
+ * @param {boolean} [options.multiCollision=false] - Enables returning multiple collided elements.
70
72
  * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
71
73
  * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
72
74
  * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
@@ -113,6 +115,7 @@ class TinyDragger {
113
115
  validateBoolean(options.lockInsideJail, 'lockInsideJail');
114
116
  validateBoolean(options.dropInJailOnly, 'dropInJailOnly');
115
117
  validateBoolean(options.revertOnDrop, 'revertOnDrop');
118
+ validateBoolean(options.multiCollision, 'multiCollision');
116
119
 
117
120
  validateString(options.classDragging, 'classDragging');
118
121
  validateString(options.classBodyDragging, 'classBodyDragging');
@@ -146,6 +149,7 @@ class TinyDragger {
146
149
  if (typeof options.revertOnDrop === 'boolean') this.#revertOnDrop = options.revertOnDrop;
147
150
  if (typeof options.lockInsideJail === 'boolean') this.#lockInsideJail = options.lockInsideJail;
148
151
  if (typeof options.dropInJailOnly === 'boolean') this.#dropInJailOnly = options.dropInJailOnly;
152
+ if (typeof options.multiCollision === 'boolean') this.#multiCollision = options.multiCollision;
149
153
 
150
154
  /** @private */
151
155
  this._onMouseDown = this.#startDrag.bind(this);
@@ -337,22 +341,67 @@ class TinyDragger {
337
341
  this.#dispatchEvent('drag');
338
342
  }
339
343
 
344
+ /** @type {HTMLElement[]} */
345
+ #collisionsMarked = [];
346
+
347
+ /**
348
+ * Marks an element as currently collided by adding the collision CSS class.
349
+ * The element is stored in an internal list for easy removal later.
350
+ *
351
+ * @param {HTMLElement|null} el - The element to mark as collided.
352
+ */
353
+ #addCollision(el) {
354
+ if (!el) return;
355
+ el.classList.add(this.#classDragCollision);
356
+ this.#collisionsMarked.push(el);
357
+ }
358
+
359
+ /**
360
+ * Removes the collision CSS class from all previously marked elements.
361
+ * Also clears the last single collision element, if set.
362
+ *
363
+ */
364
+ #removeCollision() {
365
+ while (this.#collisionsMarked.length > 0) {
366
+ const el = this.#collisionsMarked.shift();
367
+ if (el) el.classList.remove(this.#classDragCollision);
368
+ }
369
+ if (!this.#lastCollision) return;
370
+ this.#lastCollision.classList.remove(this.#classDragCollision);
371
+ }
372
+
340
373
  /**
341
374
  * Handles dragging collision.
342
375
  * @param {MouseEvent|Touch} event - The drag event.
343
376
  */
344
377
  checkDragCollision(event) {
345
- const { collidedElement: collided } = this.execCollision(event);
346
- if (collided && collided !== this.#lastCollision) {
347
- if (navigator.vibrate && Array.isArray(this.#vibration.collide)) {
348
- navigator.vibrate(this.#vibration.collide);
378
+ const { collidedElements } = this.execCollision(event);
379
+ const first = collidedElements[0] || null;
380
+
381
+ // Removes old marking if necessary
382
+ if (this.#lastCollision && !collidedElements.includes(this.#lastCollision)) {
383
+ this.#removeCollision();
384
+ }
385
+
386
+ // Adds Marking for All Colluded
387
+ collidedElements.forEach((el) => this.#addCollision(el));
388
+
389
+ // Removes markings from who no longer collided
390
+ this.#collidables.forEach((el) => {
391
+ if (!collidedElements.includes(el)) {
392
+ el.classList.remove(this.#classDragCollision);
349
393
  }
350
- this.#lastCollision = collided;
351
- if (this.#lastCollision) this.#lastCollision.classList.add(this.#classDragCollision);
352
- } else if (!collided) {
353
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
354
- this.#lastCollision = null;
394
+ });
395
+
396
+ if (
397
+ navigator.vibrate &&
398
+ Array.isArray(this.#vibration.collide) &&
399
+ collidedElements.length > 0
400
+ ) {
401
+ navigator.vibrate(this.#vibration.collide);
355
402
  }
403
+
404
+ this.#lastCollision = first;
356
405
  }
357
406
 
358
407
  /**
@@ -399,37 +448,48 @@ class TinyDragger {
399
448
  /**
400
449
  * Handles the collision of a drag.
401
450
  * @param {MouseEvent|Touch} event - The release event.
402
- * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
451
+ * @returns {{ inJail: boolean; collidedElements: (HTMLElement | null)[] }}
403
452
  */
404
453
  execCollision(event) {
405
- if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElement: null };
454
+ if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElements: [] };
406
455
 
407
- let collidedElement;
456
+ let collidedElements = [];
408
457
  let inJail = true;
409
458
  const jailRect = this.#jail?.getBoundingClientRect();
459
+
410
460
  if (this.#collisionByMouse) {
411
461
  const x = event.clientX;
412
462
  const y = event.clientY;
463
+
413
464
  if (this.#dropInJailOnly && this.#jail && jailRect) {
414
465
  inJail =
415
466
  x >= jailRect.left && x <= jailRect.right && y >= jailRect.top && y <= jailRect.bottom;
416
467
  }
417
468
 
418
- collidedElement = inJail ? this.getCollidedElement(x, y) : null;
469
+ collidedElements = inJail
470
+ ? this.#multiCollision
471
+ ? this.getAllCollidedElements(x, y)
472
+ : [this.getCollidedElement(x, y)].filter(Boolean)
473
+ : [];
419
474
  } else {
420
- const targetRect = this.#dragProxy.getBoundingClientRect();
475
+ const rect = this.#dragProxy.getBoundingClientRect();
476
+
421
477
  if (this.#dropInJailOnly && this.#jail && jailRect) {
422
478
  inJail =
423
- targetRect.left >= jailRect.left &&
424
- targetRect.right <= jailRect.right &&
425
- targetRect.top >= jailRect.top &&
426
- targetRect.bottom <= jailRect.bottom;
479
+ rect.left >= jailRect.left &&
480
+ rect.right <= jailRect.right &&
481
+ rect.top >= jailRect.top &&
482
+ rect.bottom <= jailRect.bottom;
427
483
  }
428
484
 
429
- collidedElement = inJail ? this.getCollidedElementByRect(targetRect) : null;
485
+ collidedElements = inJail
486
+ ? this.#multiCollision
487
+ ? this.getAllCollidedElementsByRect(rect)
488
+ : [this.getCollidedElementByRect(rect)].filter(Boolean)
489
+ : [];
430
490
  }
431
491
 
432
- return { inJail, collidedElement };
492
+ return { inJail, collidedElements };
433
493
  }
434
494
 
435
495
  /**
@@ -452,7 +512,7 @@ class TinyDragger {
452
512
 
453
513
  document.removeEventListener('touchmove', this._onTouchMove);
454
514
  document.removeEventListener('touchend', this._onTouchEnd);
455
- const { collidedElement } = this.execCollision(event);
515
+ const { collidedElements } = this.execCollision(event);
456
516
 
457
517
  if (navigator.vibrate && Array.isArray(this.#vibration.end)) {
458
518
  navigator.vibrate(this.#vibration.end);
@@ -465,7 +525,7 @@ class TinyDragger {
465
525
  this.#dragProxy = null;
466
526
  }
467
527
 
468
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
528
+ if (this.#lastCollision) this.#removeCollision();
469
529
  this.#lastCollision = null;
470
530
 
471
531
  this.#target.classList.remove(this.#dragHiddenClass);
@@ -476,13 +536,50 @@ class TinyDragger {
476
536
 
477
537
  const dropEvent = new CustomEvent('drop', {
478
538
  detail: {
479
- target: collidedElement,
539
+ targets: collidedElements,
540
+ first: collidedElements[0] || null,
480
541
  },
481
542
  });
482
-
483
543
  this.#target.dispatchEvent(dropEvent);
484
544
  }
485
545
 
546
+ /**
547
+ * Checks if the provided element intersects with the given bounding rectangle.
548
+ *
549
+ * @param {HTMLElement} el - The element to test for collision.
550
+ * @param {DOMRect} rect - The bounding rectangle to check against.
551
+ * @returns {boolean} True if the element intersects with the rectangle.
552
+ */
553
+ #getCollidedElementByRect(el, rect) {
554
+ const elRect = el.getBoundingClientRect();
555
+ return !(
556
+ rect.right < elRect.left ||
557
+ rect.left > elRect.right ||
558
+ rect.bottom < elRect.top ||
559
+ rect.top > elRect.bottom
560
+ );
561
+ }
562
+
563
+ /**
564
+ * Returns all elements currently colliding with the given rectangle.
565
+ *
566
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
567
+ * @returns {HTMLElement[]} A list of all collided elements.
568
+ * @throws {Error} If the input is not a valid DOMRect with numeric bounds.
569
+ */
570
+ getAllCollidedElementsByRect(rect) {
571
+ this.#checkDestroy();
572
+ if (
573
+ !(rect instanceof DOMRect) ||
574
+ typeof rect.left !== 'number' ||
575
+ typeof rect.right !== 'number' ||
576
+ typeof rect.top !== 'number' ||
577
+ typeof rect.bottom !== 'number'
578
+ )
579
+ throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
580
+ return this.#collidables.filter((el) => this.#getCollidedElementByRect(el, rect));
581
+ }
582
+
486
583
  /**
487
584
  * Detects collision based on rectangle intersection.
488
585
  * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
@@ -499,18 +596,32 @@ class TinyDragger {
499
596
  typeof rect.bottom !== 'number'
500
597
  )
501
598
  throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
599
+ return this.#collidables.find((el) => this.#getCollidedElementByRect(el, rect)) || null;
600
+ }
502
601
 
503
- return (
504
- this.#collidables.find((el) => {
505
- const elRect = el.getBoundingClientRect();
506
- return !(
507
- rect.right < elRect.left ||
508
- rect.left > elRect.right ||
509
- rect.bottom < elRect.top ||
510
- rect.top > elRect.bottom
511
- );
512
- }) || null
513
- );
602
+ /**
603
+ * Checks whether a given (x, y) coordinate is inside the bounding rectangle of an element.
604
+ *
605
+ * @param {HTMLElement} el - The element to test for collision.
606
+ * @param {number} x - Horizontal screen coordinate.
607
+ * @param {number} y - Vertical screen coordinate.
608
+ * @returns {boolean} True if the point is within the element's bounds.
609
+ */
610
+ #getCollidedElement(el, x, y) {
611
+ const rect = el.getBoundingClientRect();
612
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
613
+ }
614
+
615
+ /**
616
+ * @param {number} x - Horizontal screen coordinate.
617
+ * @param {number} y - Vertical screen coordinate.
618
+ * @returns {HTMLElement[]} The collided element or null.
619
+ */
620
+ getAllCollidedElements(x, y) {
621
+ this.#checkDestroy();
622
+ if (typeof x !== 'number' || typeof y !== 'number')
623
+ throw new Error('getCollidedElement expects numeric x and y coordinates.');
624
+ return this.#collidables.filter((el) => this.#getCollidedElement(el, x, y));
514
625
  }
515
626
 
516
627
  /**
@@ -523,13 +634,7 @@ class TinyDragger {
523
634
  this.#checkDestroy();
524
635
  if (typeof x !== 'number' || typeof y !== 'number')
525
636
  throw new Error('getCollidedElement expects numeric x and y coordinates.');
526
-
527
- return (
528
- this.#collidables.find((el) => {
529
- const rect = el.getBoundingClientRect();
530
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
531
- }) || null
532
- );
637
+ return this.#collidables.find((el) => this.#getCollidedElement(el, x, y)) || null;
533
638
  }
534
639
 
535
640
  /**
@@ -779,7 +884,7 @@ class TinyDragger {
779
884
  document.removeEventListener('touchmove', this._onTouchMove);
780
885
  document.removeEventListener('touchend', this._onTouchEnd);
781
886
 
782
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
887
+ if (this.#lastCollision) this.#removeCollision();
783
888
  if (this.#dragProxy) {
784
889
  this.#dragProxy.remove();
785
890
  this.#dragProxy = null;
@@ -43,6 +43,7 @@ declare class TinyDragger {
43
43
  * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
44
44
  * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
45
45
  * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
46
+ * @param {boolean} [options.multiCollision=false] - Enables returning multiple collided elements.
46
47
  * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
47
48
  * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
48
49
  * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
@@ -58,6 +59,7 @@ declare class TinyDragger {
58
59
  classDragCollision?: string | undefined;
59
60
  lockInsideJail?: boolean | undefined;
60
61
  dropInJailOnly?: boolean | undefined;
62
+ multiCollision?: boolean | undefined;
61
63
  vibration?: false | VibrationPatterns | undefined;
62
64
  revertOnDrop?: boolean | undefined;
63
65
  classHidden?: string | undefined;
@@ -140,12 +142,20 @@ declare class TinyDragger {
140
142
  /**
141
143
  * Handles the collision of a drag.
142
144
  * @param {MouseEvent|Touch} event - The release event.
143
- * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
145
+ * @returns {{ inJail: boolean; collidedElements: (HTMLElement | null)[] }}
144
146
  */
145
147
  execCollision(event: MouseEvent | Touch): {
146
148
  inJail: boolean;
147
- collidedElement: HTMLElement | null;
149
+ collidedElements: (HTMLElement | null)[];
148
150
  };
151
+ /**
152
+ * Returns all elements currently colliding with the given rectangle.
153
+ *
154
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
155
+ * @returns {HTMLElement[]} A list of all collided elements.
156
+ * @throws {Error} If the input is not a valid DOMRect with numeric bounds.
157
+ */
158
+ getAllCollidedElementsByRect(rect: DOMRect): HTMLElement[];
149
159
  /**
150
160
  * Detects collision based on rectangle intersection.
151
161
  * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
@@ -153,6 +163,12 @@ declare class TinyDragger {
153
163
  * @throws {Error} If rect is not a DOMRect with valid numeric properties.
154
164
  */
155
165
  getCollidedElementByRect(rect: DOMRect): HTMLElement | null;
166
+ /**
167
+ * @param {number} x - Horizontal screen coordinate.
168
+ * @param {number} y - Vertical screen coordinate.
169
+ * @returns {HTMLElement[]} The collided element or null.
170
+ */
171
+ getAllCollidedElements(x: number, y: number): HTMLElement[];
156
172
  /**
157
173
  * Detects collision with a point using element bounding rectangles.
158
174
  * @param {number} x - Horizontal screen coordinate.