tiny-essentials 1.11.0 → 1.12.0

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.
Files changed (72) hide show
  1. package/README.md +12 -3
  2. package/dist/node_modules/firebase-functions/lib/common/trace.cjs +82 -0
  3. package/dist/node_modules/firebase-functions/lib/logger/common.cjs +57 -0
  4. package/dist/node_modules/firebase-functions/lib/logger/index.cjs +158 -0
  5. package/dist/v1/TinyBasicsEs.js +1653 -1
  6. package/dist/v1/TinyBasicsEs.min.js +1 -1
  7. package/dist/v1/TinyDragDropDetector.js +307 -0
  8. package/dist/v1/TinyDragDropDetector.min.js +1 -0
  9. package/dist/v1/TinyDragger.js +3563 -0
  10. package/dist/v1/TinyDragger.min.js +2 -0
  11. package/dist/v1/TinyDragger.min.js.LICENSE.txt +8 -0
  12. package/dist/v1/TinyEssentials.js +2783 -2
  13. package/dist/v1/TinyEssentials.min.js +1 -1
  14. package/dist/v1/TinyNotifyCenter.js +32 -0
  15. package/dist/v1/TinyNotifyCenter.min.js +1 -1
  16. package/dist/v1/TinyToastNotify.js +24 -0
  17. package/dist/v1/TinyToastNotify.min.js +1 -1
  18. package/dist/v1/TinyUploadClicker.js +5235 -0
  19. package/dist/v1/TinyUploadClicker.min.js +2 -0
  20. package/dist/v1/TinyUploadClicker.min.js.LICENSE.txt +8 -0
  21. package/dist/v1/basics/fileManager.cjs +524 -0
  22. package/dist/v1/basics/fileManager.d.mts +209 -0
  23. package/dist/v1/basics/fileManager.mjs +443 -0
  24. package/dist/v1/basics/fullScreen.cjs +134 -0
  25. package/dist/v1/basics/fullScreen.d.mts +8 -0
  26. package/dist/v1/basics/fullScreen.mjs +121 -0
  27. package/dist/v1/basics/html.cjs +262 -0
  28. package/dist/v1/basics/html.d.mts +63 -0
  29. package/dist/v1/basics/html.mjs +208 -0
  30. package/dist/v1/basics/index.cjs +40 -0
  31. package/dist/v1/basics/index.d.mts +38 -1
  32. package/dist/v1/basics/index.mjs +4 -1
  33. package/dist/v1/build/TinyDragDropDetector.cjs +7 -0
  34. package/dist/v1/build/TinyDragDropDetector.d.mts +3 -0
  35. package/dist/v1/build/TinyDragDropDetector.mjs +2 -0
  36. package/dist/v1/build/TinyDragger.cjs +7 -0
  37. package/dist/v1/build/TinyDragger.d.mts +3 -0
  38. package/dist/v1/build/TinyDragger.mjs +2 -0
  39. package/dist/v1/build/TinyUploadClicker.cjs +7 -0
  40. package/dist/v1/build/TinyUploadClicker.d.mts +3 -0
  41. package/dist/v1/build/TinyUploadClicker.mjs +2 -0
  42. package/dist/v1/css/TinyDraggerExample.css +21 -0
  43. package/dist/v1/css/TinyDraggerExample.min.css +1 -0
  44. package/dist/v1/index.cjs +44 -0
  45. package/dist/v1/index.d.mts +40 -1
  46. package/dist/v1/index.mjs +6 -1
  47. package/dist/v1/libs/TinyDragDropDetector.cjs +268 -0
  48. package/dist/v1/libs/TinyDragDropDetector.d.mts +123 -0
  49. package/dist/v1/libs/TinyDragDropDetector.mjs +228 -0
  50. package/dist/v1/libs/TinyDragger.cjs +801 -0
  51. package/dist/v1/libs/TinyDragger.d.mts +300 -0
  52. package/dist/v1/libs/TinyDragger.mjs +689 -0
  53. package/dist/v1/libs/TinyNotifyCenter.cjs +32 -0
  54. package/dist/v1/libs/TinyNotifyCenter.d.mts +9 -0
  55. package/dist/v1/libs/TinyNotifyCenter.mjs +28 -0
  56. package/dist/v1/libs/TinyToastNotify.cjs +24 -0
  57. package/dist/v1/libs/TinyToastNotify.d.mts +8 -0
  58. package/dist/v1/libs/TinyToastNotify.mjs +20 -0
  59. package/dist/v1/libs/TinyUploadClicker.cjs +223 -0
  60. package/dist/v1/libs/TinyUploadClicker.d.mts +80 -0
  61. package/dist/v1/libs/TinyUploadClicker.mjs +182 -0
  62. package/docs/v1/README.md +8 -2
  63. package/docs/v1/basics/fileManager.md +222 -0
  64. package/docs/v1/basics/fullScreen.md +183 -0
  65. package/docs/v1/basics/html.md +243 -0
  66. package/docs/v1/libs/TinyDragDropDetector.md +143 -0
  67. package/docs/v1/libs/TinyDragger.md +415 -0
  68. package/docs/v1/libs/TinyLevelUp.md +1 -1
  69. package/docs/v1/libs/TinyNotifyCenter.md +20 -0
  70. package/docs/v1/libs/TinyToastNotify.md +19 -0
  71. package/docs/v1/libs/TinyUploadClicker.md +108 -0
  72. package/package.json +12 -2
@@ -0,0 +1,689 @@
1
+ import { getHtmlElBordersWidth } from '../basics/html.mjs';
2
+ import { isJsonObject } from '../basics/objFilter.mjs';
3
+ /**
4
+ * @typedef {Object} VibrationPatterns
5
+ * @property {number[]|false} start - Pattern to vibrate on start
6
+ * @property {number[]|false} end - Pattern to vibrate on end
7
+ * @property {number[]|false} collide - Pattern to vibrate on collision
8
+ * @property {number[]|false} move - Pattern to vibrate while moving
9
+ */
10
+ /**
11
+ * TinyDragger enables drag-and-drop functionality for a DOM element.
12
+ * It supports jail boundaries, optional collision detection, vibration feedback,
13
+ * automatic reverting, proxy dragging, and event dispatching.
14
+ */
15
+ class TinyDragger {
16
+ #enabled = true;
17
+ #destroyed = false;
18
+ #offsetY = 0;
19
+ #offsetX = 0;
20
+ #lockInsideJail = false;
21
+ #revertOnDrop = false;
22
+ #dragging = false;
23
+ #collisionByMouse = false;
24
+ #dropInJailOnly = false;
25
+ /** @type {HTMLElement|null} */
26
+ #lastCollision = null;
27
+ /** @type {HTMLElement[]} */
28
+ #collidables = [];
29
+ /** @type {HTMLElement|null} */
30
+ #dragProxy = null;
31
+ /** @type {VibrationPatterns} */
32
+ #vibration = { start: false, end: false, collide: false, move: false };
33
+ /** @type {HTMLElement|null} */
34
+ #jail = null;
35
+ #target;
36
+ #dragHiddenClass = 'drag-hidden';
37
+ #classDragging = 'dragging';
38
+ #classBodyDragging = 'drag-active';
39
+ #classJailDragging = 'jail-drag-active';
40
+ #classJailDragDisabled = 'jail-drag-disabled';
41
+ #classDragCollision = 'dragging-collision';
42
+ /** @typedef {(event: TouchEvent) => void} TouchDragEvent */
43
+ /**
44
+ * @param {HTMLElement} targetElement - The element to make draggable.
45
+ * @param {Object} [options={}] - Configuration options.
46
+ * @param {HTMLElement} [options.jail] - Optional container to restrict dragging within.
47
+ * @param {boolean} [options.collisionByMouse=false] - Use mouse position for collision instead of element rect.
48
+ * @param {string} [options.classDragging='dragging'] - CSS class applied to the clone during dragging.
49
+ * @param {string} [options.classBodyDragging='drag-active'] - CSS class applied to <body> during dragging.
50
+ * @param {string} [options.classJailDragging='jail-drag-active'] - CSS class applied to jail element during drag.
51
+ * @param {string} [options.classJailDragDisabled='jail-drag-disabled'] - CSS class applied to jail element disabled.
52
+ * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
53
+ * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
54
+ * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
55
+ * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
56
+ * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
57
+ * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
58
+ * @throws {Error} If any option has an invalid type.
59
+ */
60
+ constructor(targetElement, options = {}) {
61
+ if (!(targetElement instanceof HTMLElement))
62
+ throw new Error('TinyDragger requires a valid target HTMLElement to initialize.');
63
+ this.#target = targetElement;
64
+ // === Validations ===
65
+ if (options.jail !== undefined && !(options.jail instanceof HTMLElement))
66
+ throw new Error('The "jail" option must be an HTMLElement if provided.');
67
+ if (options.vibration !== undefined &&
68
+ options.vibration !== false &&
69
+ !isJsonObject(options.vibration))
70
+ throw new Error('The "vibration" option must be an object or false.');
71
+ /**
72
+ * @param {any} val
73
+ * @param {string} name
74
+ */
75
+ const validateBoolean = (val, name) => {
76
+ if (val !== undefined && typeof val !== 'boolean') {
77
+ throw new Error(`The "${name}" option must be a boolean.`);
78
+ }
79
+ };
80
+ /**
81
+ * @param {any} val
82
+ * @param {string} name
83
+ */
84
+ const validateString = (val, name) => {
85
+ if (val !== undefined && typeof val !== 'string') {
86
+ throw new Error(`The "${name}" option must be a string.`);
87
+ }
88
+ };
89
+ validateBoolean(options.collisionByMouse, 'collisionByMouse');
90
+ validateBoolean(options.lockInsideJail, 'lockInsideJail');
91
+ validateBoolean(options.dropInJailOnly, 'dropInJailOnly');
92
+ validateBoolean(options.revertOnDrop, 'revertOnDrop');
93
+ validateString(options.classDragging, 'classDragging');
94
+ validateString(options.classBodyDragging, 'classBodyDragging');
95
+ validateString(options.classJailDragging, 'classJailDragging');
96
+ validateString(options.classJailDragDisabled, 'classJailDragDisabled');
97
+ validateString(options.classDragCollision, 'classDragCollision');
98
+ validateString(options.classHidden, 'classHidden');
99
+ if (options.jail instanceof HTMLElement)
100
+ this.#jail = options.jail;
101
+ /** @type {VibrationPatterns} */
102
+ const vibrationTemplate = { start: false, end: false, collide: false, move: false };
103
+ this.#vibration = Object.assign(vibrationTemplate, isJsonObject(options.vibration) ? options.vibration : {});
104
+ if (typeof options.classDragging === 'string')
105
+ this.#classDragging = options.classDragging;
106
+ if (typeof options.classBodyDragging === 'string')
107
+ this.#classBodyDragging = options.classBodyDragging;
108
+ if (typeof options.classJailDragging === 'string')
109
+ this.#classJailDragging = options.classJailDragging;
110
+ if (typeof options.classJailDragDisabled === 'string')
111
+ this.#classJailDragDisabled = options.classJailDragDisabled;
112
+ if (typeof options.classHidden === 'string')
113
+ this.#dragHiddenClass = options.classHidden;
114
+ if (typeof options.classDragCollision === 'string')
115
+ this.#classDragCollision = options.classDragCollision;
116
+ if (typeof options.collisionByMouse === 'boolean')
117
+ this.#collisionByMouse = options.collisionByMouse;
118
+ if (typeof options.revertOnDrop === 'boolean')
119
+ this.#revertOnDrop = options.revertOnDrop;
120
+ if (typeof options.lockInsideJail === 'boolean')
121
+ this.#lockInsideJail = options.lockInsideJail;
122
+ if (typeof options.dropInJailOnly === 'boolean')
123
+ this.#dropInJailOnly = options.dropInJailOnly;
124
+ /** @private */
125
+ this._onMouseDown = this.#startDrag.bind(this);
126
+ /** @private */
127
+ this._onMouseMove = this.#drag.bind(this);
128
+ /** @private */
129
+ this._onMouseUp = this.#endDrag.bind(this);
130
+ /**
131
+ * @type {TouchDragEvent}
132
+ * @private
133
+ */
134
+ this._onTouchStart = (e) => this.#startDrag(e.touches[0]);
135
+ /**
136
+ * @type {TouchDragEvent}
137
+ * @private
138
+ */
139
+ this._onTouchMove = (e) => this.#drag(e.touches[0]);
140
+ /**
141
+ * @type {TouchDragEvent}
142
+ * @private
143
+ */
144
+ this._onTouchEnd = (e) => this.#endDrag(e.changedTouches[0]);
145
+ this.#target.addEventListener('mousedown', this._onMouseDown);
146
+ this.#target.addEventListener('touchstart', this._onTouchStart, { passive: false });
147
+ }
148
+ /**
149
+ * Enables the drag functionality.
150
+ */
151
+ enable() {
152
+ this.#checkDestroy();
153
+ if (this.#jail)
154
+ this.#jail.classList.add(this.#classJailDragDisabled);
155
+ this.#enabled = true;
156
+ }
157
+ /**
158
+ * Disables the drag functionality.
159
+ */
160
+ disable() {
161
+ if (this.#jail)
162
+ this.#jail.classList.remove(this.#classJailDragDisabled);
163
+ this.#enabled = false;
164
+ }
165
+ /**
166
+ * Adds an element to be considered for collision detection.
167
+ * @param {HTMLElement} element - The element to track collisions with.
168
+ * @throws {Error} If the element is not a valid HTMLElement.
169
+ */
170
+ addCollidable(element) {
171
+ this.#checkDestroy();
172
+ if (!(element instanceof HTMLElement))
173
+ throw new Error('addCollidable expects an HTMLElement as argument.');
174
+ if (!this.#collidables.includes(element))
175
+ this.#collidables.push(element);
176
+ }
177
+ /**
178
+ * Removes a collidable element from the tracking list.
179
+ * @param {HTMLElement} element - The element to remove.
180
+ * @throws {Error} If the element is not a valid HTMLElement.
181
+ */
182
+ removeCollidable(element) {
183
+ this.#checkDestroy();
184
+ if (!(element instanceof HTMLElement))
185
+ throw new Error('removeCollidable expects an HTMLElement as argument.');
186
+ this.#collidables = this.#collidables.filter((el) => el !== element);
187
+ }
188
+ /**
189
+ * Sets vibration patterns for drag events.
190
+ * @param {Object} [param0={}] - Vibration pattern configuration.
191
+ * @param {number[]|false} [param0.startPattern=false] - Vibration on drag start.
192
+ * @param {number[]|false} [param0.endPattern=false] - Vibration on drag end.
193
+ * @param {number[]|false} [param0.collidePattern=false] - Vibration on collision.
194
+ * @param {number[]|false} [param0.movePattern=false] - Vibration during movement.
195
+ * @throws {Error} If any pattern is not false or an array of numbers.
196
+ */
197
+ setVibrationPattern({ startPattern = false, endPattern = false, collidePattern = false, movePattern = false, } = {}) {
198
+ this.#checkDestroy();
199
+ /** @param {any} value */
200
+ const isValidPattern = (value) => value === false || (Array.isArray(value) && value.every((n) => typeof n === 'number'));
201
+ if (!isValidPattern(startPattern))
202
+ throw new Error('Invalid "startPattern": must be false or an array of numbers.');
203
+ if (!isValidPattern(endPattern))
204
+ throw new Error('Invalid "endPattern": must be false or an array of numbers.');
205
+ if (!isValidPattern(collidePattern))
206
+ throw new Error('Invalid "collidePattern": must be false or an array of numbers.');
207
+ if (!isValidPattern(movePattern))
208
+ throw new Error('Invalid "movePattern": must be false or an array of numbers.');
209
+ this.#vibration = {
210
+ start: startPattern,
211
+ end: endPattern,
212
+ collide: collidePattern,
213
+ move: movePattern,
214
+ };
215
+ }
216
+ /**
217
+ * Disables all vibration feedback.
218
+ */
219
+ disableVibration() {
220
+ this.#checkDestroy();
221
+ this.#vibration = { start: false, end: false, collide: false, move: false };
222
+ }
223
+ /**
224
+ * Calculates the cursor offset relative to the top-left of the target element.
225
+ * @param {MouseEvent|Touch} event - The mouse or touch event.
226
+ * @returns {{x: number, y: number}} The offset in pixels.
227
+ * @throws {Error} If event is not a MouseEvent or Touch with clientX/clientY.
228
+ */
229
+ getOffset(event) {
230
+ this.#checkDestroy();
231
+ if ((!(event instanceof MouseEvent) && !(event instanceof Touch)) ||
232
+ typeof event.clientX !== 'number' ||
233
+ typeof event.clientY !== 'number')
234
+ throw new Error('getOffset expects an event with valid clientX and clientY coordinates.');
235
+ const targetRect = this.#target.getBoundingClientRect();
236
+ const { left: borderLeft, top: borderTop } = getHtmlElBordersWidth(this.#target);
237
+ return {
238
+ x: event.clientX - targetRect.left + borderLeft,
239
+ y: event.clientY - targetRect.top + borderTop,
240
+ };
241
+ }
242
+ /**
243
+ * Handles the start of a drag event.
244
+ * @param {MouseEvent|Touch} event - The initiating event.
245
+ */
246
+ #startDrag(event) {
247
+ if (event instanceof MouseEvent)
248
+ event.preventDefault();
249
+ if (this.#destroyed || !this.#enabled || !this.#target.parentElement)
250
+ return;
251
+ const dragProxy = this.#target.cloneNode(true);
252
+ if (!(dragProxy instanceof HTMLElement))
253
+ return;
254
+ this.#dragProxy = dragProxy;
255
+ this.#dragging = true;
256
+ const rect = this.#target.getBoundingClientRect();
257
+ Object.assign(this.#dragProxy.style, {
258
+ position: 'absolute',
259
+ pointerEvents: 'none',
260
+ left: `${this.#target.offsetLeft}px`,
261
+ top: `${this.#target.offsetTop}px`,
262
+ width: `${rect.width}px`,
263
+ height: `${rect.height}px`,
264
+ zIndex: 9999,
265
+ });
266
+ this.#target.classList.add(this.#dragHiddenClass);
267
+ this.#target.parentElement.appendChild(this.#dragProxy);
268
+ const { x: offsetX, y: offsetY } = this.getOffset(event);
269
+ this.#offsetX = offsetX;
270
+ this.#offsetY = offsetY;
271
+ this.#dragProxy.classList.add(this.#classDragging);
272
+ document.body.classList.add(this.#classBodyDragging);
273
+ if (this.#jail)
274
+ this.#jail.classList.add(this.#classJailDragging);
275
+ document.addEventListener('mousemove', this._onMouseMove);
276
+ document.addEventListener('mouseup', this._onMouseUp);
277
+ document.addEventListener('touchmove', this._onTouchMove, { passive: false });
278
+ document.addEventListener('touchend', this._onTouchEnd);
279
+ if (navigator.vibrate && Array.isArray(this.#vibration.start)) {
280
+ navigator.vibrate(this.#vibration.start);
281
+ }
282
+ this.checkDragCollision(event);
283
+ this.#dispatchEvent('drag');
284
+ }
285
+ /**
286
+ * Handles dragging collision.
287
+ * @param {MouseEvent|Touch} event - The drag event.
288
+ */
289
+ checkDragCollision(event) {
290
+ const { collidedElement: collided } = this.execCollision(event);
291
+ if (collided && collided !== this.#lastCollision) {
292
+ if (navigator.vibrate && Array.isArray(this.#vibration.collide)) {
293
+ navigator.vibrate(this.#vibration.collide);
294
+ }
295
+ this.#lastCollision = collided;
296
+ if (this.#lastCollision)
297
+ this.#lastCollision.classList.add(this.#classDragCollision);
298
+ }
299
+ else if (!collided) {
300
+ if (this.#lastCollision)
301
+ this.#lastCollision.classList.remove(this.#classDragCollision);
302
+ this.#lastCollision = null;
303
+ }
304
+ }
305
+ /**
306
+ * Handles dragging movement.
307
+ * @param {MouseEvent|Touch} event - The drag event.
308
+ */
309
+ #drag(event) {
310
+ if (event instanceof MouseEvent)
311
+ event.preventDefault();
312
+ if (this.#destroyed || !this.#dragging || !this.#enabled || !this.#dragProxy)
313
+ return;
314
+ const parent = this.#dragProxy.offsetParent || document.body;
315
+ const parentRect = parent.getBoundingClientRect();
316
+ let x = event.clientX - parentRect.left - this.#offsetX;
317
+ let y = event.clientY - parentRect.top - this.#offsetY;
318
+ if (this.#lockInsideJail && this.#jail) {
319
+ const jailRect = this.#jail.getBoundingClientRect();
320
+ const targetRect = this.#dragProxy.getBoundingClientRect();
321
+ const jailLeft = jailRect.left - parentRect.left;
322
+ const jailTop = jailRect.top - parentRect.top;
323
+ const { x: borderX, y: borderY } = getHtmlElBordersWidth(this.#jail);
324
+ const maxX = jailLeft + jailRect.width - targetRect.width - borderY;
325
+ const maxY = jailTop + jailRect.height - targetRect.height - borderX;
326
+ x = Math.max(jailLeft, Math.min(x, maxX));
327
+ y = Math.max(jailTop, Math.min(y, maxY));
328
+ }
329
+ this.#dragProxy.style.position = 'absolute';
330
+ this.#dragProxy.style.left = `${x}px`;
331
+ this.#dragProxy.style.top = `${y}px`;
332
+ if (navigator.vibrate && Array.isArray(this.#vibration.move)) {
333
+ navigator.vibrate(this.#vibration.move);
334
+ }
335
+ this.checkDragCollision(event);
336
+ this.#dispatchEvent('dragging');
337
+ }
338
+ /**
339
+ * Handles the collision of a drag.
340
+ * @param {MouseEvent|Touch} event - The release event.
341
+ * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
342
+ */
343
+ execCollision(event) {
344
+ if (this.#destroyed || !this.#dragProxy)
345
+ return { inJail: false, collidedElement: null };
346
+ let collidedElement;
347
+ let inJail = true;
348
+ const jailRect = this.#jail?.getBoundingClientRect();
349
+ if (this.#collisionByMouse) {
350
+ const x = event.clientX;
351
+ const y = event.clientY;
352
+ if (this.#dropInJailOnly && this.#jail && jailRect) {
353
+ inJail =
354
+ x >= jailRect.left && x <= jailRect.right && y >= jailRect.top && y <= jailRect.bottom;
355
+ }
356
+ collidedElement = inJail ? this.getCollidedElement(x, y) : null;
357
+ }
358
+ else {
359
+ const targetRect = this.#dragProxy.getBoundingClientRect();
360
+ if (this.#dropInJailOnly && this.#jail && jailRect) {
361
+ inJail =
362
+ targetRect.left >= jailRect.left &&
363
+ targetRect.right <= jailRect.right &&
364
+ targetRect.top >= jailRect.top &&
365
+ targetRect.bottom <= jailRect.bottom;
366
+ }
367
+ collidedElement = inJail ? this.getCollidedElementByRect(targetRect) : null;
368
+ }
369
+ return { inJail, collidedElement };
370
+ }
371
+ /**
372
+ * Handles the end of a drag.
373
+ * @param {MouseEvent|Touch} event - The release event.
374
+ */
375
+ #endDrag(event) {
376
+ if (event instanceof MouseEvent)
377
+ event.preventDefault();
378
+ if (this.#destroyed || !this.#dragging)
379
+ return;
380
+ this.#dragging = false;
381
+ if (!this.#dragProxy)
382
+ return;
383
+ this.#target.classList.remove(this.#classDragging);
384
+ document.body.classList.remove(this.#classBodyDragging);
385
+ if (this.#jail)
386
+ this.#jail.classList.remove(this.#classJailDragging);
387
+ document.removeEventListener('mousemove', this._onMouseMove);
388
+ document.removeEventListener('mouseup', this._onMouseUp);
389
+ document.removeEventListener('touchmove', this._onTouchMove);
390
+ document.removeEventListener('touchend', this._onTouchEnd);
391
+ const { collidedElement } = this.execCollision(event);
392
+ if (navigator.vibrate && Array.isArray(this.#vibration.end)) {
393
+ navigator.vibrate(this.#vibration.end);
394
+ }
395
+ const newX = this.#dragProxy.style.left;
396
+ const newY = this.#dragProxy.style.top;
397
+ if (this.#dragProxy) {
398
+ this.#dragProxy.remove();
399
+ this.#dragProxy = null;
400
+ }
401
+ if (this.#lastCollision)
402
+ this.#lastCollision.classList.remove(this.#classDragCollision);
403
+ this.#lastCollision = null;
404
+ this.#target.classList.remove(this.#dragHiddenClass);
405
+ if (!this.#revertOnDrop) {
406
+ this.#target.style.left = newX;
407
+ this.#target.style.top = newY;
408
+ }
409
+ const dropEvent = new CustomEvent('drop', {
410
+ detail: {
411
+ target: collidedElement,
412
+ },
413
+ });
414
+ this.#target.dispatchEvent(dropEvent);
415
+ }
416
+ /**
417
+ * Detects collision based on rectangle intersection.
418
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
419
+ * @returns {HTMLElement|null} The collided element or null.
420
+ * @throws {Error} If rect is not a DOMRect with valid numeric properties.
421
+ */
422
+ getCollidedElementByRect(rect) {
423
+ this.#checkDestroy();
424
+ if (!(rect instanceof DOMRect) ||
425
+ typeof rect.left !== 'number' ||
426
+ typeof rect.right !== 'number' ||
427
+ typeof rect.top !== 'number' ||
428
+ typeof rect.bottom !== 'number')
429
+ throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
430
+ return (this.#collidables.find((el) => {
431
+ const elRect = el.getBoundingClientRect();
432
+ return !(rect.right < elRect.left ||
433
+ rect.left > elRect.right ||
434
+ rect.bottom < elRect.top ||
435
+ rect.top > elRect.bottom);
436
+ }) || null);
437
+ }
438
+ /**
439
+ * Detects collision with a point using element bounding rectangles.
440
+ * @param {number} x - Horizontal screen coordinate.
441
+ * @param {number} y - Vertical screen coordinate.
442
+ * @returns {HTMLElement|null} The collided element or null.
443
+ */
444
+ getCollidedElement(x, y) {
445
+ this.#checkDestroy();
446
+ if (typeof x !== 'number' || typeof y !== 'number')
447
+ throw new Error('getCollidedElement expects numeric x and y coordinates.');
448
+ return (this.#collidables.find((el) => {
449
+ const rect = el.getBoundingClientRect();
450
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
451
+ }) || null);
452
+ }
453
+ /**
454
+ * Dispatches a custom event from the target element.
455
+ * @param {string} type - The event name.
456
+ */
457
+ #dispatchEvent(type) {
458
+ const event = new CustomEvent(type);
459
+ this.#target.dispatchEvent(event);
460
+ }
461
+ /**
462
+ * Gets whether dragging is currently active.
463
+ * @returns {boolean}
464
+ */
465
+ getDragging() {
466
+ return this.#dragging;
467
+ }
468
+ /**
469
+ * Gets whether movement is restricted inside the jail container.
470
+ * @returns {boolean}
471
+ */
472
+ getLockInsideJail() {
473
+ return this.#lockInsideJail;
474
+ }
475
+ /**
476
+ * Sets whether movement is restricted inside the jail container.
477
+ * @param {boolean} value
478
+ */
479
+ setLockInsideJail(value) {
480
+ if (typeof value !== 'boolean')
481
+ throw new Error('lockInsideJail must be a boolean.');
482
+ this.#lockInsideJail = value;
483
+ }
484
+ /**
485
+ * Gets whether the element should revert to original position on drop.
486
+ * @returns {boolean}
487
+ */
488
+ getRevertOnDrop() {
489
+ return this.#revertOnDrop;
490
+ }
491
+ /**
492
+ * Sets whether the element should revert to original position on drop.
493
+ * @param {boolean} value
494
+ */
495
+ setRevertOnDrop(value) {
496
+ if (typeof value !== 'boolean')
497
+ throw new Error('revertOnDrop must be a boolean.');
498
+ this.#revertOnDrop = value;
499
+ }
500
+ /**
501
+ * Gets whether collision detection uses mouse position.
502
+ * @returns {boolean}
503
+ */
504
+ getCollisionByMouse() {
505
+ return this.#collisionByMouse;
506
+ }
507
+ /**
508
+ * Sets whether collision detection uses mouse position.
509
+ * @param {boolean} value
510
+ */
511
+ setCollisionByMouse(value) {
512
+ if (typeof value !== 'boolean')
513
+ throw new Error('collisionByMouse must be a boolean.');
514
+ this.#collisionByMouse = value;
515
+ }
516
+ /**
517
+ * Gets whether dropping is restricted inside the jail container.
518
+ * @returns {boolean}
519
+ */
520
+ getDropInJailOnly() {
521
+ return this.#dropInJailOnly;
522
+ }
523
+ /**
524
+ * Sets whether dropping is restricted inside the jail container.
525
+ * @param {boolean} value
526
+ */
527
+ setDropInJailOnly(value) {
528
+ if (typeof value !== 'boolean')
529
+ throw new Error('dropInJailOnly must be a boolean.');
530
+ this.#dropInJailOnly = value;
531
+ }
532
+ /**
533
+ * Returns the original target element being dragged.
534
+ * @returns {HTMLElement}
535
+ */
536
+ getTarget() {
537
+ return this.#target;
538
+ }
539
+ /**
540
+ * Returns the current jail container (if any).
541
+ * @returns {HTMLElement|null}
542
+ */
543
+ getJail() {
544
+ return this.#jail;
545
+ }
546
+ /**
547
+ * Returns the current proxy element being dragged (if any).
548
+ * @returns {HTMLElement|null}
549
+ */
550
+ getDragProxy() {
551
+ return this.#dragProxy;
552
+ }
553
+ /**
554
+ * Returns the last collided element (if any).
555
+ * @returns {HTMLElement|null}
556
+ */
557
+ getLastCollision() {
558
+ return this.#lastCollision;
559
+ }
560
+ /**
561
+ * Returns all registered collidable elements.
562
+ * @returns {HTMLElement[]}
563
+ */
564
+ getCollidables() {
565
+ return [...this.#collidables];
566
+ }
567
+ /**
568
+ * Returns the CSS class used to hide the target during drag.
569
+ * @returns {string}
570
+ */
571
+ getDragHiddenClass() {
572
+ return this.#dragHiddenClass;
573
+ }
574
+ /**
575
+ * Returns the CSS class applied to the clone during dragging.
576
+ * @returns {string}
577
+ */
578
+ getClassDragging() {
579
+ return this.#classDragging;
580
+ }
581
+ /**
582
+ * Returns the CSS class applied to <body> during dragging.
583
+ * @returns {string}
584
+ */
585
+ getClassBodyDragging() {
586
+ return this.#classBodyDragging;
587
+ }
588
+ /**
589
+ * Returns the CSS class applied to the jail during dragging.
590
+ * @returns {string}
591
+ */
592
+ getClassJailDragging() {
593
+ return this.#classJailDragging;
594
+ }
595
+ /**
596
+ * Returns the CSS class applied to the jail when dragging is disabled.
597
+ * @returns {string}
598
+ */
599
+ getClassJailDragDisabled() {
600
+ return this.#classJailDragDisabled;
601
+ }
602
+ /**
603
+ * Returns the CSS class applied to a collided element.
604
+ * @returns {string}
605
+ */
606
+ getClassDragCollision() {
607
+ return this.#classDragCollision;
608
+ }
609
+ /**
610
+ * Returns the full vibration configuration.
611
+ * @returns {VibrationPatterns}
612
+ */
613
+ getVibrations() {
614
+ return { ...this.#vibration };
615
+ }
616
+ /**
617
+ * Returns the vibration pattern for drag start.
618
+ * @returns {number[]|boolean}
619
+ */
620
+ getStartVibration() {
621
+ return this.#vibration.start;
622
+ }
623
+ /**
624
+ * Returns the vibration pattern for drag end.
625
+ * @returns {number[]|boolean}
626
+ */
627
+ getEndVibration() {
628
+ return this.#vibration.end;
629
+ }
630
+ /**
631
+ * Returns the vibration pattern for collisions.
632
+ * @returns {number[]|boolean}
633
+ */
634
+ getCollideVibration() {
635
+ return this.#vibration.collide;
636
+ }
637
+ /**
638
+ * Returns the vibration pattern during movement.
639
+ * @returns {number[]|boolean}
640
+ */
641
+ getMoveVibration() {
642
+ return this.#vibration.move;
643
+ }
644
+ /**
645
+ * Returns whether the dragger is currently enabled.
646
+ * @returns {boolean}
647
+ */
648
+ isEnabled() {
649
+ return this.#enabled;
650
+ }
651
+ /**
652
+ * Internal method to verify if the instance has been destroyed.
653
+ * Throws an error if any operation is attempted after destruction.
654
+ */
655
+ #checkDestroy() {
656
+ if (this.#destroyed)
657
+ throw new Error('This TinyDragger instance has been destroyed and can no longer be used.');
658
+ }
659
+ /**
660
+ * Completely disables drag-and-drop and cleans up all event listeners.
661
+ * Does NOT remove the original HTML element.
662
+ */
663
+ destroy() {
664
+ if (this.#destroyed)
665
+ return;
666
+ this.disable();
667
+ this.#target.removeEventListener('mousedown', this._onMouseDown);
668
+ this.#target.removeEventListener('touchstart', this._onTouchStart);
669
+ document.removeEventListener('mousemove', this._onMouseMove);
670
+ document.removeEventListener('mouseup', this._onMouseUp);
671
+ document.removeEventListener('touchmove', this._onTouchMove);
672
+ document.removeEventListener('touchend', this._onTouchEnd);
673
+ if (this.#lastCollision)
674
+ this.#lastCollision.classList.remove(this.#classDragCollision);
675
+ if (this.#dragProxy) {
676
+ this.#dragProxy.remove();
677
+ this.#dragProxy = null;
678
+ }
679
+ this.#collidables = [];
680
+ this.#dragging = false;
681
+ this.#lastCollision = null;
682
+ this.#target.classList.remove(this.#dragHiddenClass, this.#classDragging);
683
+ document.body.classList.remove(this.#classBodyDragging);
684
+ if (this.#jail)
685
+ this.#jail.classList.remove(this.#classJailDragging, this.#classJailDragDisabled);
686
+ this.#destroyed = true;
687
+ }
688
+ }
689
+ export default TinyDragger;