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.
@@ -7132,6 +7132,7 @@ class TinyDragger {
7132
7132
  #offsetY = 0;
7133
7133
  #offsetX = 0;
7134
7134
 
7135
+ #multiCollision = false;
7135
7136
  #lockInsideJail = false;
7136
7137
  #revertOnDrop = false;
7137
7138
  #dragging = false;
@@ -7176,6 +7177,7 @@ class TinyDragger {
7176
7177
  * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
7177
7178
  * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
7178
7179
  * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
7180
+ * @param {boolean} [options.multiCollision=false] - Enables returning multiple collided elements.
7179
7181
  * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
7180
7182
  * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
7181
7183
  * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
@@ -7222,6 +7224,7 @@ class TinyDragger {
7222
7224
  validateBoolean(options.lockInsideJail, 'lockInsideJail');
7223
7225
  validateBoolean(options.dropInJailOnly, 'dropInJailOnly');
7224
7226
  validateBoolean(options.revertOnDrop, 'revertOnDrop');
7227
+ validateBoolean(options.multiCollision, 'multiCollision');
7225
7228
 
7226
7229
  validateString(options.classDragging, 'classDragging');
7227
7230
  validateString(options.classBodyDragging, 'classBodyDragging');
@@ -7255,6 +7258,7 @@ class TinyDragger {
7255
7258
  if (typeof options.revertOnDrop === 'boolean') this.#revertOnDrop = options.revertOnDrop;
7256
7259
  if (typeof options.lockInsideJail === 'boolean') this.#lockInsideJail = options.lockInsideJail;
7257
7260
  if (typeof options.dropInJailOnly === 'boolean') this.#dropInJailOnly = options.dropInJailOnly;
7261
+ if (typeof options.multiCollision === 'boolean') this.#multiCollision = options.multiCollision;
7258
7262
 
7259
7263
  /** @private */
7260
7264
  this._onMouseDown = this.#startDrag.bind(this);
@@ -7446,22 +7450,67 @@ class TinyDragger {
7446
7450
  this.#dispatchEvent('drag');
7447
7451
  }
7448
7452
 
7453
+ /** @type {HTMLElement[]} */
7454
+ #collisionsMarked = [];
7455
+
7456
+ /**
7457
+ * Marks an element as currently collided by adding the collision CSS class.
7458
+ * The element is stored in an internal list for easy removal later.
7459
+ *
7460
+ * @param {HTMLElement|null} el - The element to mark as collided.
7461
+ */
7462
+ #addCollision(el) {
7463
+ if (!el) return;
7464
+ el.classList.add(this.#classDragCollision);
7465
+ this.#collisionsMarked.push(el);
7466
+ }
7467
+
7468
+ /**
7469
+ * Removes the collision CSS class from all previously marked elements.
7470
+ * Also clears the last single collision element, if set.
7471
+ *
7472
+ */
7473
+ #removeCollision() {
7474
+ while (this.#collisionsMarked.length > 0) {
7475
+ const el = this.#collisionsMarked.shift();
7476
+ if (el) el.classList.remove(this.#classDragCollision);
7477
+ }
7478
+ if (!this.#lastCollision) return;
7479
+ this.#lastCollision.classList.remove(this.#classDragCollision);
7480
+ }
7481
+
7449
7482
  /**
7450
7483
  * Handles dragging collision.
7451
7484
  * @param {MouseEvent|Touch} event - The drag event.
7452
7485
  */
7453
7486
  checkDragCollision(event) {
7454
- const { collidedElement: collided } = this.execCollision(event);
7455
- if (collided && collided !== this.#lastCollision) {
7456
- if (navigator.vibrate && Array.isArray(this.#vibration.collide)) {
7457
- navigator.vibrate(this.#vibration.collide);
7487
+ const { collidedElements } = this.execCollision(event);
7488
+ const first = collidedElements[0] || null;
7489
+
7490
+ // Removes old marking if necessary
7491
+ if (this.#lastCollision && !collidedElements.includes(this.#lastCollision)) {
7492
+ this.#removeCollision();
7493
+ }
7494
+
7495
+ // Adds Marking for All Colluded
7496
+ collidedElements.forEach((el) => this.#addCollision(el));
7497
+
7498
+ // Removes markings from who no longer collided
7499
+ this.#collidables.forEach((el) => {
7500
+ if (!collidedElements.includes(el)) {
7501
+ el.classList.remove(this.#classDragCollision);
7458
7502
  }
7459
- this.#lastCollision = collided;
7460
- if (this.#lastCollision) this.#lastCollision.classList.add(this.#classDragCollision);
7461
- } else if (!collided) {
7462
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
7463
- this.#lastCollision = null;
7503
+ });
7504
+
7505
+ if (
7506
+ navigator.vibrate &&
7507
+ Array.isArray(this.#vibration.collide) &&
7508
+ collidedElements.length > 0
7509
+ ) {
7510
+ navigator.vibrate(this.#vibration.collide);
7464
7511
  }
7512
+
7513
+ this.#lastCollision = first;
7465
7514
  }
7466
7515
 
7467
7516
  /**
@@ -7508,37 +7557,48 @@ class TinyDragger {
7508
7557
  /**
7509
7558
  * Handles the collision of a drag.
7510
7559
  * @param {MouseEvent|Touch} event - The release event.
7511
- * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
7560
+ * @returns {{ inJail: boolean; collidedElements: (HTMLElement | null)[] }}
7512
7561
  */
7513
7562
  execCollision(event) {
7514
- if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElement: null };
7563
+ if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElements: [] };
7515
7564
 
7516
- let collidedElement;
7565
+ let collidedElements = [];
7517
7566
  let inJail = true;
7518
7567
  const jailRect = this.#jail?.getBoundingClientRect();
7568
+
7519
7569
  if (this.#collisionByMouse) {
7520
7570
  const x = event.clientX;
7521
7571
  const y = event.clientY;
7572
+
7522
7573
  if (this.#dropInJailOnly && this.#jail && jailRect) {
7523
7574
  inJail =
7524
7575
  x >= jailRect.left && x <= jailRect.right && y >= jailRect.top && y <= jailRect.bottom;
7525
7576
  }
7526
7577
 
7527
- collidedElement = inJail ? this.getCollidedElement(x, y) : null;
7578
+ collidedElements = inJail
7579
+ ? this.#multiCollision
7580
+ ? this.getAllCollidedElements(x, y)
7581
+ : [this.getCollidedElement(x, y)].filter(Boolean)
7582
+ : [];
7528
7583
  } else {
7529
- const targetRect = this.#dragProxy.getBoundingClientRect();
7584
+ const rect = this.#dragProxy.getBoundingClientRect();
7585
+
7530
7586
  if (this.#dropInJailOnly && this.#jail && jailRect) {
7531
7587
  inJail =
7532
- targetRect.left >= jailRect.left &&
7533
- targetRect.right <= jailRect.right &&
7534
- targetRect.top >= jailRect.top &&
7535
- targetRect.bottom <= jailRect.bottom;
7588
+ rect.left >= jailRect.left &&
7589
+ rect.right <= jailRect.right &&
7590
+ rect.top >= jailRect.top &&
7591
+ rect.bottom <= jailRect.bottom;
7536
7592
  }
7537
7593
 
7538
- collidedElement = inJail ? this.getCollidedElementByRect(targetRect) : null;
7594
+ collidedElements = inJail
7595
+ ? this.#multiCollision
7596
+ ? this.getAllCollidedElementsByRect(rect)
7597
+ : [this.getCollidedElementByRect(rect)].filter(Boolean)
7598
+ : [];
7539
7599
  }
7540
7600
 
7541
- return { inJail, collidedElement };
7601
+ return { inJail, collidedElements };
7542
7602
  }
7543
7603
 
7544
7604
  /**
@@ -7561,7 +7621,7 @@ class TinyDragger {
7561
7621
 
7562
7622
  document.removeEventListener('touchmove', this._onTouchMove);
7563
7623
  document.removeEventListener('touchend', this._onTouchEnd);
7564
- const { collidedElement } = this.execCollision(event);
7624
+ const { collidedElements } = this.execCollision(event);
7565
7625
 
7566
7626
  if (navigator.vibrate && Array.isArray(this.#vibration.end)) {
7567
7627
  navigator.vibrate(this.#vibration.end);
@@ -7574,7 +7634,7 @@ class TinyDragger {
7574
7634
  this.#dragProxy = null;
7575
7635
  }
7576
7636
 
7577
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
7637
+ if (this.#lastCollision) this.#removeCollision();
7578
7638
  this.#lastCollision = null;
7579
7639
 
7580
7640
  this.#target.classList.remove(this.#dragHiddenClass);
@@ -7585,13 +7645,50 @@ class TinyDragger {
7585
7645
 
7586
7646
  const dropEvent = new CustomEvent('drop', {
7587
7647
  detail: {
7588
- target: collidedElement,
7648
+ targets: collidedElements,
7649
+ first: collidedElements[0] || null,
7589
7650
  },
7590
7651
  });
7591
-
7592
7652
  this.#target.dispatchEvent(dropEvent);
7593
7653
  }
7594
7654
 
7655
+ /**
7656
+ * Checks if the provided element intersects with the given bounding rectangle.
7657
+ *
7658
+ * @param {HTMLElement} el - The element to test for collision.
7659
+ * @param {DOMRect} rect - The bounding rectangle to check against.
7660
+ * @returns {boolean} True if the element intersects with the rectangle.
7661
+ */
7662
+ #getCollidedElementByRect(el, rect) {
7663
+ const elRect = el.getBoundingClientRect();
7664
+ return !(
7665
+ rect.right < elRect.left ||
7666
+ rect.left > elRect.right ||
7667
+ rect.bottom < elRect.top ||
7668
+ rect.top > elRect.bottom
7669
+ );
7670
+ }
7671
+
7672
+ /**
7673
+ * Returns all elements currently colliding with the given rectangle.
7674
+ *
7675
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
7676
+ * @returns {HTMLElement[]} A list of all collided elements.
7677
+ * @throws {Error} If the input is not a valid DOMRect with numeric bounds.
7678
+ */
7679
+ getAllCollidedElementsByRect(rect) {
7680
+ this.#checkDestroy();
7681
+ if (
7682
+ !(rect instanceof DOMRect) ||
7683
+ typeof rect.left !== 'number' ||
7684
+ typeof rect.right !== 'number' ||
7685
+ typeof rect.top !== 'number' ||
7686
+ typeof rect.bottom !== 'number'
7687
+ )
7688
+ throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
7689
+ return this.#collidables.filter((el) => this.#getCollidedElementByRect(el, rect));
7690
+ }
7691
+
7595
7692
  /**
7596
7693
  * Detects collision based on rectangle intersection.
7597
7694
  * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
@@ -7608,18 +7705,32 @@ class TinyDragger {
7608
7705
  typeof rect.bottom !== 'number'
7609
7706
  )
7610
7707
  throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
7708
+ return this.#collidables.find((el) => this.#getCollidedElementByRect(el, rect)) || null;
7709
+ }
7611
7710
 
7612
- return (
7613
- this.#collidables.find((el) => {
7614
- const elRect = el.getBoundingClientRect();
7615
- return !(
7616
- rect.right < elRect.left ||
7617
- rect.left > elRect.right ||
7618
- rect.bottom < elRect.top ||
7619
- rect.top > elRect.bottom
7620
- );
7621
- }) || null
7622
- );
7711
+ /**
7712
+ * Checks whether a given (x, y) coordinate is inside the bounding rectangle of an element.
7713
+ *
7714
+ * @param {HTMLElement} el - The element to test for collision.
7715
+ * @param {number} x - Horizontal screen coordinate.
7716
+ * @param {number} y - Vertical screen coordinate.
7717
+ * @returns {boolean} True if the point is within the element's bounds.
7718
+ */
7719
+ #getCollidedElement(el, x, y) {
7720
+ const rect = el.getBoundingClientRect();
7721
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
7722
+ }
7723
+
7724
+ /**
7725
+ * @param {number} x - Horizontal screen coordinate.
7726
+ * @param {number} y - Vertical screen coordinate.
7727
+ * @returns {HTMLElement[]} The collided element or null.
7728
+ */
7729
+ getAllCollidedElements(x, y) {
7730
+ this.#checkDestroy();
7731
+ if (typeof x !== 'number' || typeof y !== 'number')
7732
+ throw new Error('getCollidedElement expects numeric x and y coordinates.');
7733
+ return this.#collidables.filter((el) => this.#getCollidedElement(el, x, y));
7623
7734
  }
7624
7735
 
7625
7736
  /**
@@ -7632,13 +7743,7 @@ class TinyDragger {
7632
7743
  this.#checkDestroy();
7633
7744
  if (typeof x !== 'number' || typeof y !== 'number')
7634
7745
  throw new Error('getCollidedElement expects numeric x and y coordinates.');
7635
-
7636
- return (
7637
- this.#collidables.find((el) => {
7638
- const rect = el.getBoundingClientRect();
7639
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
7640
- }) || null
7641
- );
7746
+ return this.#collidables.find((el) => this.#getCollidedElement(el, x, y)) || null;
7642
7747
  }
7643
7748
 
7644
7749
  /**
@@ -7888,7 +7993,7 @@ class TinyDragger {
7888
7993
  document.removeEventListener('touchmove', this._onTouchMove);
7889
7994
  document.removeEventListener('touchend', this._onTouchEnd);
7890
7995
 
7891
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
7996
+ if (this.#lastCollision) this.#removeCollision();
7892
7997
  if (this.#dragProxy) {
7893
7998
  this.#dragProxy.remove();
7894
7999
  this.#dragProxy = null;