tiny-essentials 1.10.2 → 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 (106) hide show
  1. package/README.md +13 -4
  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/{TinyEssentials.js → v1/TinyBasicsEs.js} +1726 -1362
  6. package/dist/v1/TinyBasicsEs.min.js +2 -0
  7. package/dist/v1/TinyDragDropDetector.js +307 -0
  8. package/dist/v1/TinyDragDropDetector.min.js +1 -0
  9. package/dist/{TinyBasicsEs.js → v1/TinyDragger.js} +1004 -506
  10. package/dist/v1/TinyDragger.min.js +2 -0
  11. package/dist/v1/TinyEssentials.js +7938 -0
  12. package/dist/v1/TinyEssentials.min.js +2 -0
  13. package/dist/v1/TinyEssentials.min.js.LICENSE.txt +8 -0
  14. package/dist/v1/TinyNotifyCenter.js +493 -0
  15. package/dist/v1/TinyNotifyCenter.min.js +1 -0
  16. package/dist/v1/TinyToastNotify.js +441 -0
  17. package/dist/v1/TinyToastNotify.min.js +1 -0
  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/TinyNotifyCenter.cjs +7 -0
  40. package/dist/v1/build/TinyNotifyCenter.d.mts +3 -0
  41. package/dist/v1/build/TinyNotifyCenter.mjs +2 -0
  42. package/dist/v1/build/TinyToastNotify.cjs +7 -0
  43. package/dist/v1/build/TinyToastNotify.d.mts +3 -0
  44. package/dist/v1/build/TinyToastNotify.mjs +2 -0
  45. package/dist/v1/build/TinyUploadClicker.cjs +7 -0
  46. package/dist/v1/build/TinyUploadClicker.d.mts +3 -0
  47. package/dist/v1/build/TinyUploadClicker.mjs +2 -0
  48. package/dist/v1/css/TinyDraggerExample.css +21 -0
  49. package/dist/v1/css/TinyDraggerExample.min.css +1 -0
  50. package/dist/v1/css/TinyNotify.css +350 -0
  51. package/dist/v1/css/TinyNotify.min.css +1 -0
  52. package/dist/v1/index.cjs +48 -0
  53. package/dist/v1/index.d.mts +42 -1
  54. package/dist/v1/index.mjs +8 -1
  55. package/dist/v1/libs/TinyDragDropDetector.cjs +268 -0
  56. package/dist/v1/libs/TinyDragDropDetector.d.mts +123 -0
  57. package/dist/v1/libs/TinyDragDropDetector.mjs +228 -0
  58. package/dist/v1/libs/TinyDragger.cjs +801 -0
  59. package/dist/v1/libs/TinyDragger.d.mts +300 -0
  60. package/dist/v1/libs/TinyDragger.mjs +689 -0
  61. package/dist/v1/libs/TinyNotifyCenter.cjs +454 -0
  62. package/dist/v1/libs/TinyNotifyCenter.d.mts +175 -0
  63. package/dist/v1/libs/TinyNotifyCenter.mjs +413 -0
  64. package/dist/v1/libs/TinyToastNotify.cjs +402 -0
  65. package/dist/v1/libs/TinyToastNotify.d.mts +165 -0
  66. package/dist/v1/libs/TinyToastNotify.mjs +352 -0
  67. package/dist/v1/libs/TinyUploadClicker.cjs +223 -0
  68. package/dist/v1/libs/TinyUploadClicker.d.mts +80 -0
  69. package/dist/v1/libs/TinyUploadClicker.mjs +182 -0
  70. package/docs/{README.md → v1/README.md} +8 -0
  71. package/docs/v1/basics/fileManager.md +222 -0
  72. package/docs/v1/basics/fullScreen.md +183 -0
  73. package/docs/v1/basics/html.md +243 -0
  74. package/docs/{basics → v1/basics}/text.md +2 -2
  75. package/docs/v1/libs/TinyDragDropDetector.md +143 -0
  76. package/docs/v1/libs/TinyDragger.md +415 -0
  77. package/docs/{libs → v1/libs}/TinyLevelUp.md +1 -1
  78. package/docs/v1/libs/TinyNotifyCenter.md +311 -0
  79. package/docs/v1/libs/TinyToastNotify.md +309 -0
  80. package/docs/v1/libs/TinyUploadClicker.md +108 -0
  81. package/package.json +14 -2
  82. package/dist/TinyBasicsEs.min.js +0 -2
  83. package/dist/TinyEssentials.min.js +0 -2
  84. package/dist/v1/libs/TinyRateLimit.cjs +0 -196
  85. package/dist/v1/libs/TinyRateLimit.d.mts +0 -86
  86. package/dist/v1/libs/TinyRateLimit.mjs +0 -171
  87. /package/dist/{ColorSafeStringify.js → v1/ColorSafeStringify.js} +0 -0
  88. /package/dist/{ColorSafeStringify.min.js → v1/ColorSafeStringify.min.js} +0 -0
  89. /package/dist/{TinyBasicsEs.min.js.LICENSE.txt → v1/TinyBasicsEs.min.js.LICENSE.txt} +0 -0
  90. /package/dist/{TinyEssentials.min.js.LICENSE.txt → v1/TinyDragger.min.js.LICENSE.txt} +0 -0
  91. /package/dist/{TinyLevelUp.js → v1/TinyLevelUp.js} +0 -0
  92. /package/dist/{TinyLevelUp.min.js → v1/TinyLevelUp.min.js} +0 -0
  93. /package/dist/{TinyPromiseQueue.js → v1/TinyPromiseQueue.js} +0 -0
  94. /package/dist/{TinyPromiseQueue.min.js → v1/TinyPromiseQueue.min.js} +0 -0
  95. /package/dist/{TinyRateLimiter.js → v1/TinyRateLimiter.js} +0 -0
  96. /package/dist/{TinyRateLimiter.min.js → v1/TinyRateLimiter.min.js} +0 -0
  97. /package/dist/{aiMarker.css → v1/css/aiMarker.css} +0 -0
  98. /package/dist/{aiMarker.min.css → v1/css/aiMarker.min.css} +0 -0
  99. /package/docs/{basics → v1/basics}/array.md +0 -0
  100. /package/docs/{basics → v1/basics}/asyncReplace.md +0 -0
  101. /package/docs/{basics → v1/basics}/clock.md +0 -0
  102. /package/docs/{basics → v1/basics}/objFilter.md +0 -0
  103. /package/docs/{basics → v1/basics}/simpleMath.md +0 -0
  104. /package/docs/{libs → v1/libs}/ColorSafeStringify.md +0 -0
  105. /package/docs/{libs → v1/libs}/TinyPromiseQueue.md +0 -0
  106. /package/docs/{libs → v1/libs}/TinyRateLimiter.md +0 -0
@@ -2121,351 +2121,17 @@ function fromByteArray (uint8) {
2121
2121
  /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
2122
2122
  /******/ })();
2123
2123
  /******/
2124
- /******/ /* webpack/runtime/make namespace object */
2125
- /******/ (() => {
2126
- /******/ // define __esModule on exports
2127
- /******/ __webpack_require__.r = (exports) => {
2128
- /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
2129
- /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2130
- /******/ }
2131
- /******/ Object.defineProperty(exports, '__esModule', { value: true });
2132
- /******/ };
2133
- /******/ })();
2134
- /******/
2135
2124
  /************************************************************************/
2136
2125
  var __webpack_exports__ = {};
2137
2126
  // This entry needs to be wrapped in an IIFE because it needs to be in strict mode.
2138
2127
  (() => {
2139
2128
  "use strict";
2140
- // ESM COMPAT FLAG
2141
- __webpack_require__.r(__webpack_exports__);
2142
2129
 
2143
2130
  // EXPORTS
2144
2131
  __webpack_require__.d(__webpack_exports__, {
2145
- addAiMarkerShortcut: () => (/* reexport */ addAiMarkerShortcut),
2146
- arraySortPositions: () => (/* reexport */ arraySortPositions),
2147
- asyncReplace: () => (/* reexport */ asyncReplace),
2148
- cloneObjTypeOrder: () => (/* reexport */ cloneObjTypeOrder),
2149
- countObj: () => (/* reexport */ countObj),
2150
- extendObjType: () => (/* reexport */ extendObjType),
2151
- formatBytes: () => (/* reexport */ formatBytes),
2152
- formatCustomTimer: () => (/* reexport */ formatCustomTimer),
2153
- formatDayTimer: () => (/* reexport */ formatDayTimer),
2154
- formatTimer: () => (/* reexport */ formatTimer),
2155
- getAge: () => (/* reexport */ getAge),
2156
- getSimplePerc: () => (/* reexport */ getSimplePerc),
2157
- getTimeDuration: () => (/* reexport */ getTimeDuration),
2158
- isJsonObject: () => (/* reexport */ isJsonObject),
2159
- objType: () => (/* reexport */ objType),
2160
- reorderObjTypeOrder: () => (/* reexport */ reorderObjTypeOrder),
2161
- ruleOfThree: () => (/* reexport */ ruleOfThree),
2162
- shuffleArray: () => (/* reexport */ shuffleArray),
2163
- toTitleCase: () => (/* reexport */ toTitleCase),
2164
- toTitleCaseLowerFirst: () => (/* reexport */ toTitleCaseLowerFirst)
2132
+ TinyDragger: () => (/* reexport */ libs_TinyDragger)
2165
2133
  });
2166
2134
 
2167
- ;// ./src/legacy/libs/arraySortPositions.mjs
2168
- /**
2169
- * Generates a comparator function to sort an array of objects by a given key.
2170
- *
2171
- * @param {string} item - The object key to sort by.
2172
- * @param {boolean} [isReverse=false] - If `true`, the sorting will be in descending order.
2173
- * @returns {(a: Object<string|number, *>, b: Object<string|number, *>) => number} Comparator function compatible with Array.prototype.sort().
2174
- *
2175
- * @example
2176
- * const arr = [{ pos: 2 }, { pos: 1 }, { pos: 3 }];
2177
- * arr.sort(arraySortPositions('pos')); // Ascending: [{pos: 1}, {pos: 2}, {pos: 3}]
2178
- *
2179
- * @example
2180
- * const arr = [{ pos: 2 }, { pos: 1 }, { pos: 3 }];
2181
- * arr.sort(arraySortPositions('pos', true)); // Descending: [{pos: 3}, {pos: 2}, {pos: 1}]
2182
- */
2183
- function arraySortPositions(item, isReverse = false) {
2184
- if (!isReverse) {
2185
- return function (a, b) {
2186
- return a[item] < b[item] ? -1 : a[item] > b[item] ? 1 : 0;
2187
- };
2188
- } else {
2189
- return function (a, b) {
2190
- return a[item] > b[item] ? -1 : a[item] < b[item] ? 1 : 0;
2191
- };
2192
- }
2193
- }
2194
-
2195
- ;// ./src/legacy/libs/replaceAsync.mjs
2196
- /**
2197
- * Asynchronously replaces matches in a string using a regex and an async function.
2198
- *
2199
- * @param {string} str - The input string to perform replacements on.
2200
- * @param {RegExp} regex - The regular expression to match substrings for replacement.
2201
- * @param {Function} asyncFn - An asynchronous function that returns a replacement for each match.
2202
- * It receives the same arguments as a standard `replace` callback.
2203
- * @returns {Promise<string>} The resulting string with all async replacements applied.
2204
- *
2205
- * @example
2206
- * await asyncReplace("Hello @user1 and @user2!", /@\w+/g, async (mention) => {
2207
- * return await getUserNameFromMention(mention);
2208
- * });
2209
- */
2210
- async function asyncReplace(str, regex, asyncFn) {
2211
- /**
2212
- * @type {any[]}
2213
- */
2214
- const promises = [];
2215
-
2216
- // Collect promises
2217
- str.replace(regex, (match, ...args) => {
2218
- promises.push(asyncFn(match, ...args));
2219
- return match;
2220
- });
2221
-
2222
- const data = await Promise.all(promises);
2223
-
2224
- // Replace using the resolved data
2225
- return str.replace(regex, () => data.shift());
2226
- }
2227
-
2228
- ;// ./src/v1/basics/array.mjs
2229
- // https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array
2230
-
2231
- /**
2232
- * Randomly shuffles the elements of an array in place using the Fisher–Yates algorithm.
2233
- *
2234
- * This implementation ensures a uniform distribution of permutations.
2235
- * Original algorithm source: StackOverflow (link above).
2236
- *
2237
- * @param {any[]} items - The array to shuffle.
2238
- * @returns {any[]} The same array instance, now shuffled in place.
2239
- */
2240
- function shuffleArray(items) {
2241
- let currentIndex = items.length,
2242
- randomIndex;
2243
-
2244
- // While there remain elements to shuffle...
2245
- while (currentIndex !== 0) {
2246
- // Pick a remaining element...
2247
- randomIndex = Math.floor(Math.random() * currentIndex);
2248
- currentIndex--;
2249
-
2250
- // And swap it with the current element.
2251
- [items[currentIndex], items[randomIndex]] = [items[randomIndex], items[currentIndex]];
2252
- }
2253
-
2254
- return items;
2255
- }
2256
-
2257
- ;// ./src/v1/basics/clock.mjs
2258
- /**
2259
- * Calculates the time duration between the current time and a given time offset.
2260
- *
2261
- * @param {Date} timeData - The target time as a Date object.
2262
- * @param {string} [durationType='asSeconds'] - The type of duration to return. Can be 'asMilliseconds', 'asSeconds', 'asMinutes', 'asHours', 'asDays'.
2263
- * @param {Date|null} [now=null] - The current time as a Date object. Defaults to the current date and time if not provided.
2264
- * @returns {number|null} The calculated duration in the specified unit, or `null` if `timeData` is not provided.
2265
- */
2266
- function getTimeDuration(timeData = new Date(), durationType = 'asSeconds', now = null) {
2267
- if (timeData instanceof Date) {
2268
- const currentTime = now instanceof Date ? now : new Date();
2269
- /** @type {number} */
2270
- const diffMs = timeData.getTime() - currentTime.getTime();
2271
-
2272
- switch (durationType) {
2273
- case 'asMilliseconds':
2274
- return diffMs;
2275
- case 'asSeconds':
2276
- return diffMs / 1000;
2277
- case 'asMinutes':
2278
- return diffMs / (1000 * 60);
2279
- case 'asHours':
2280
- return diffMs / (1000 * 60 * 60);
2281
- case 'asDays':
2282
- return diffMs / (1000 * 60 * 60 * 24);
2283
- default:
2284
- return diffMs / 1000; // default to seconds
2285
- }
2286
- }
2287
-
2288
- return null;
2289
- }
2290
-
2291
- /**
2292
- * Formats a custom timer string based on total seconds and a detail level.
2293
- * Includes proper reallocation of lower units into higher ones, ensuring consistent hierarchy.
2294
- *
2295
- * @param {number} totalSeconds - The total amount of seconds to convert.
2296
- * @param {'seconds'|'minutes'|'hours'|'days'|'months'|'years'} [level] - The highest level to calculate and display.
2297
- * @param {string} [format='{time}'] - Output template with placeholders like {years}, {months}, {days}, {hours}, {minutes}, {seconds}, {time}, {total}.
2298
- * @returns {string} The formatted timer string.
2299
- */
2300
- function formatCustomTimer(totalSeconds, level = 'seconds', format = '{time}') {
2301
- totalSeconds = Math.max(0, Math.floor(totalSeconds));
2302
-
2303
- const levels = ['seconds', 'minutes', 'hours', 'days', 'months', 'years'];
2304
- const index = levels.indexOf(level);
2305
-
2306
- const include = {
2307
- years: index >= 5,
2308
- months: index >= 4,
2309
- days: index >= 3,
2310
- hours: index >= 2,
2311
- minutes: index >= 1,
2312
- seconds: index >= 0,
2313
- };
2314
-
2315
- /**
2316
- * @type {{
2317
- * years: number|NaN,
2318
- * months: number|NaN,
2319
- * days: number|NaN,
2320
- * hours: number|NaN,
2321
- * minutes: number|NaN,
2322
- * seconds: number|NaN,
2323
- * total: number|NaN
2324
- * }}
2325
- */
2326
- const parts = {
2327
- years: include.years ? 0 : NaN,
2328
- months: include.months ? 0 : NaN,
2329
- days: include.days ? 0 : NaN,
2330
- hours: include.hours ? 0 : NaN,
2331
- minutes: include.minutes ? 0 : NaN,
2332
- seconds: include.seconds ? 0 : NaN,
2333
- total: NaN,
2334
- };
2335
-
2336
- let remaining = totalSeconds;
2337
-
2338
- if (include.years || include.months || include.days) {
2339
- const baseDate = new Date(1980, 0, 1);
2340
- const targetDate = new Date(baseDate.getTime() + remaining * 1000);
2341
- const workingDate = new Date(baseDate);
2342
-
2343
- // Years
2344
- if (include.years) {
2345
- while (
2346
- new Date(
2347
- workingDate.getFullYear() + 1,
2348
- workingDate.getMonth(),
2349
- workingDate.getDate(),
2350
- ).getTime() <= targetDate.getTime()
2351
- ) {
2352
- workingDate.setFullYear(workingDate.getFullYear() + 1);
2353
- parts.years++;
2354
- }
2355
- }
2356
-
2357
- // Months
2358
- if (include.months) {
2359
- while (
2360
- new Date(
2361
- workingDate.getFullYear(),
2362
- workingDate.getMonth() + 1,
2363
- workingDate.getDate(),
2364
- ).getTime() <= targetDate.getTime()
2365
- ) {
2366
- workingDate.setMonth(workingDate.getMonth() + 1);
2367
- parts.months++;
2368
- }
2369
- }
2370
-
2371
- // Days
2372
- if (include.days) {
2373
- while (
2374
- new Date(
2375
- workingDate.getFullYear(),
2376
- workingDate.getMonth(),
2377
- workingDate.getDate() + 1,
2378
- ).getTime() <= targetDate.getTime()
2379
- ) {
2380
- workingDate.setDate(workingDate.getDate() + 1);
2381
- parts.days++;
2382
- }
2383
- }
2384
-
2385
- remaining = Math.floor((targetDate.getTime() - workingDate.getTime()) / 1000);
2386
- }
2387
-
2388
- if (include.hours) {
2389
- parts.hours = Math.floor(remaining / 3600);
2390
- remaining %= 3600;
2391
- }
2392
-
2393
- if (include.minutes) {
2394
- parts.minutes = Math.floor(remaining / 60);
2395
- remaining %= 60;
2396
- }
2397
-
2398
- if (include.seconds) {
2399
- parts.seconds = remaining;
2400
- }
2401
-
2402
- // Calculate total
2403
- const totalMap = {
2404
- seconds: include.seconds ? totalSeconds : NaN,
2405
- minutes: include.minutes ? totalSeconds / 60 : NaN,
2406
- hours: include.hours ? totalSeconds / 3600 : NaN,
2407
- days: include.days ? totalSeconds / 86400 : NaN,
2408
- months: include.months ? parts.years * 12 + parts.months + (parts.days || 0) / 30 : NaN,
2409
- years: include.years ? parts.years + (parts.months || 0) / 12 + (parts.days || 0) / 365 : NaN,
2410
- };
2411
-
2412
- parts.total = +(totalMap[level] || 0).toFixed(2).replace(/\.00$/, '');
2413
-
2414
- /**
2415
- * Pads a number to ensure it is at least two digits long, using leading zeros if necessary.
2416
- *
2417
- * @param {number|string} n - The number or string to pad.
2418
- * @returns {string} The padded string.
2419
- */
2420
- const pad = (n) => {
2421
- const num = typeof n === 'string' ? parseInt(n) : n;
2422
- return Number.isNaN(num) ? 'NaN' : String(num).padStart(2, '0');
2423
- };
2424
-
2425
- const timeString = [
2426
- include.hours ? pad(parts.hours) : null,
2427
- include.minutes ? pad(parts.minutes) : null,
2428
- include.seconds ? pad(parts.seconds) : null,
2429
- ]
2430
- .filter((v) => v !== null)
2431
- .join(':');
2432
-
2433
- return format
2434
- .replace(/\{years\}/g, String(parts.years))
2435
- .replace(/\{months\}/g, String(parts.months))
2436
- .replace(/\{days\}/g, String(parts.days))
2437
- .replace(/\{hours\}/g, pad(parts.hours))
2438
- .replace(/\{minutes\}/g, pad(parts.minutes))
2439
- .replace(/\{seconds\}/g, pad(parts.seconds))
2440
- .replace(/\{time\}/g, timeString)
2441
- .replace(/\{total\}/g, String(parts.total))
2442
- .trim();
2443
- }
2444
-
2445
- /**
2446
- * Formats a duration (in seconds) into a timer string showing only hours, minutes, and seconds.
2447
- *
2448
- * Example output: "05:32:10"
2449
- *
2450
- * @param {number} seconds - The total number of seconds to format.
2451
- * @returns {string} The formatted timer string in "HH:MM:SS" format.
2452
- */
2453
- function formatTimer(seconds) {
2454
- return formatCustomTimer(seconds, 'hours', '{hours}:{minutes}:{seconds}');
2455
- }
2456
-
2457
- /**
2458
- * Formats a duration (in seconds) into a timer string including days, hours, minutes, and seconds.
2459
- *
2460
- * Example output: "2d 05:32:10"
2461
- *
2462
- * @param {number} seconds - The total number of seconds to format.
2463
- * @returns {string} The formatted timer string in "Xd HH:MM:SS" format.
2464
- */
2465
- function formatDayTimer(seconds) {
2466
- return formatCustomTimer(seconds, 'days', '{days}d {hours}:{minutes}:{seconds}');
2467
- }
2468
-
2469
2135
  // EXTERNAL MODULE: ./node_modules/buffer/index.js
2470
2136
  var buffer = __webpack_require__(287);
2471
2137
  ;// ./src/v1/basics/objFilter.mjs
@@ -2698,7 +2364,7 @@ function countObj(obj) {
2698
2364
  * @param {unknown} value - The value to test.
2699
2365
  * @returns {value is Record<string | number | symbol, unknown>} Returns true if the value is a pure object.
2700
2366
  */
2701
- function isJsonObject(value) {
2367
+ function objFilter_isJsonObject(value) {
2702
2368
  if (value === null || typeof value !== 'object') return false;
2703
2369
  if (Array.isArray(value)) return false;
2704
2370
  if (Object.prototype.toString.call(value) !== '[object Object]') return false;
@@ -2827,239 +2493,1071 @@ extendObjType([
2827
2493
  [
2828
2494
  'object',
2829
2495
  /** @param {*} val @returns {val is Record<string | number | symbol, unknown>} */
2830
- (val) => isJsonObject(val),
2496
+ (val) => objFilter_isJsonObject(val),
2831
2497
  ],
2832
2498
  ]);
2833
2499
 
2834
- ;// ./src/v1/basics/simpleMath.mjs
2500
+ ;// ./src/v1/basics/html.mjs
2501
+
2502
+
2835
2503
  /**
2836
- * Executes a Rule of Three calculation.
2837
- *
2838
- * @param {number} val1 - The first reference value (numerator in direct proportion, denominator in inverse).
2839
- * @param {number} val2 - The second reference value (denominator in direct proportion, numerator in inverse).
2840
- * @param {number} val3 - The third value (numerator in direct proportion, denominator in inverse).
2841
- * @param {boolean} [inverse] - Whether the calculation should use inverse proportion (true for inverse, false for direct).
2842
- * @returns {number} The result of the Rule of Three operation.
2843
- *
2844
- * Rule of Three Formula (Direct Proportion):
2845
- * val1 / val2 = val3 / result
2846
- *
2847
- * For Inverse Proportion:
2848
- * val1 / val3 = val2 / result
2849
- *
2850
- * Visual Representation:
2851
- *
2852
- * For Direct Proportion:
2853
- * val1 val2
2854
- * ----- = ------
2855
- * val3 result
2856
- *
2857
- * For Inverse Proportion:
2858
- * val1 val2
2859
- * ----- = ------
2860
- * val3 result
2861
- *
2862
- * @example
2863
- * // Direct proportion:
2864
- * ruleOfThree.execute(2, 6, 3, false); // → 9
2504
+ * Checks if two DOM elements are colliding on the screen.
2865
2505
  *
2866
- * @example
2867
- * // Inverse proportion:
2868
- * ruleOfThree.execute(2, 6, 3, true); // 4
2506
+ * @param {Element} elem1 - First DOM element.
2507
+ * @param {Element} elem2 - Second DOM element.
2508
+ * @returns {boolean} - Returns true if the elements are colliding.
2869
2509
  */
2870
- function ruleOfThree(val1, val2, val3, inverse = false) {
2871
- return inverse ? Number(val1 * val2) / val3 : Number(val3 * val2) / val1;
2510
+ function areHtmlElsColliding(elem1, elem2) {
2511
+ const rect1 = elem1.getBoundingClientRect();
2512
+ const rect2 = elem2.getBoundingClientRect();
2513
+
2514
+ return !(
2515
+ rect1.right < rect2.left ||
2516
+ rect1.left > rect2.right ||
2517
+ rect1.bottom < rect2.top ||
2518
+ rect1.top > rect2.bottom
2519
+ );
2872
2520
  }
2873
2521
 
2874
2522
  /**
2875
- * Calculates a percentage of a given base value.
2876
- * @param {number} price - The base value.
2877
- * @param {number} percentage - The percentage to apply.
2878
- * @returns {number} The result of the percentage calculation.
2879
- *
2880
- * @example
2881
- * getSimplePerc(200, 15); // 30
2523
+ * Reads and parses a JSON data using FileReader.
2524
+ * Throws an error if the content is not valid JSON.
2525
+ * @param {File} file
2526
+ * @returns {Promise<any>}
2882
2527
  */
2883
- function getSimplePerc(price, percentage) {
2884
- return price * (percentage / 100);
2528
+ function readJsonBlob(file) {
2529
+ return new Promise((resolve, reject) => {
2530
+ const reader = new FileReader();
2531
+
2532
+ reader.onload = () => {
2533
+ try {
2534
+ // @ts-ignore
2535
+ const result = JSON.parse(reader.result);
2536
+ resolve(result);
2537
+ } catch (error) {
2538
+ // @ts-ignore
2539
+ reject(new Error(`Invalid JSON in file: ${file.name}\n${error.message}`));
2540
+ }
2541
+ };
2542
+
2543
+ reader.onerror = () => {
2544
+ reject(new Error(`Error reading file: ${file.name}`));
2545
+ };
2546
+
2547
+ reader.readAsText(file);
2548
+ });
2885
2549
  }
2886
2550
 
2887
2551
  /**
2888
- * Calculates the age based on the given date.
2552
+ * Saves a JSON object as a downloadable file.
2553
+ * @param {string} filename
2554
+ * @param {any} data
2555
+ * @param {number} [spaces=2]
2556
+ */
2557
+ function saveJsonFile(filename, data, spaces = 2) {
2558
+ const json = JSON.stringify(data, null, spaces);
2559
+ const blob = new Blob([json], { type: 'application/json' });
2560
+ const url = URL.createObjectURL(blob);
2561
+
2562
+ const link = document.createElement('a');
2563
+ link.href = url;
2564
+ link.download = filename;
2565
+
2566
+ document.body.appendChild(link);
2567
+ link.click();
2568
+ document.body.removeChild(link);
2569
+
2570
+ URL.revokeObjectURL(url);
2571
+ }
2572
+
2573
+ /**
2574
+ * Loads and parses a JSON from a remote URL using Fetch API.
2889
2575
  *
2890
- * @param {number|string|Date} timeData - The birth date (can be a timestamp, ISO string, or Date object).
2891
- * @param {Date|null} [now=null] - The Date object representing the current date. Defaults to the current date and time if not provided.
2892
- * @returns {number|null} The age in years, or null if `timeData` is not provided or invalid.
2576
+ * @param {string} url - The full URL to fetch JSON from.
2577
+ * @param {Object} [options] - Optional settings.
2578
+ * @param {string} [options.method="GET"] - HTTP method to use (GET, POST, etc.).
2579
+ * @param {any} [options.body] - Request body (only for methods like POST, PUT).
2580
+ * @param {number} [options.timeout=0] - Timeout in milliseconds (ignored if signal is provided).
2581
+ * @param {number} [options.retries=0] - Number of retry attempts (ignored if signal is provided).
2582
+ * @param {Headers|Record<string, *>} [options.headers={}] - Additional headers.
2583
+ * @param {AbortSignal|null} [options.signal] - External AbortSignal; disables timeout and retries.
2584
+ * @returns {Promise<*>} Parsed JSON object.
2585
+ * @throws {Error} Throws if fetch fails, times out, or returns invalid JSON.
2893
2586
  */
2894
- function getAge(timeData = 0, now = null) {
2895
- if (typeof timeData !== 'undefined' && timeData !== null && timeData !== 0) {
2896
- const birthDate = new Date(timeData);
2897
- if (Number.isNaN(birthDate.getTime())) return null;
2587
+ async function fetchJson(
2588
+ url,
2589
+ { method = 'GET', body, timeout = 0, retries = 0, headers = {}, signal = null } = {},
2590
+ ) {
2591
+ if (
2592
+ typeof url !== 'string' ||
2593
+ (!url.startsWith('../') &&
2594
+ !url.startsWith('./') &&
2595
+ !url.startsWith('/') &&
2596
+ !url.startsWith('https://') &&
2597
+ !url.startsWith('http://'))
2598
+ )
2599
+ throw new Error('Invalid URL: must be a valid http or https address.');
2600
+
2601
+ if (typeof method !== 'string' || !method.trim())
2602
+ throw new Error('Invalid method: must be a non-empty string.');
2603
+
2604
+ if (!signal) {
2605
+ if (
2606
+ typeof timeout !== 'number' ||
2607
+ !Number.isFinite(timeout) ||
2608
+ Number.isNaN(timeout) ||
2609
+ timeout < 0
2610
+ )
2611
+ throw new Error('Invalid timeout: must be a positive number.');
2612
+
2613
+ if (
2614
+ typeof retries !== 'number' ||
2615
+ !Number.isFinite(retries) ||
2616
+ Number.isNaN(retries) ||
2617
+ retries < 0
2618
+ )
2619
+ throw new Error('Invalid retries: must be a positive number.');
2620
+ }
2621
+
2622
+ const attempts = signal ? 1 : retries + 1;
2623
+ /** @type {Error|null} */
2624
+ let lastError = null;
2625
+
2626
+ for (let attempt = 0; attempt < attempts; attempt++) {
2627
+ const controller = signal ? null : new AbortController();
2628
+ const localSignal = signal || (controller?.signal ?? null);
2629
+ const timer =
2630
+ !signal && timeout && controller ? setTimeout(() => controller.abort(), timeout) : null;
2631
+
2632
+ try {
2633
+ const response = await fetch(url, {
2634
+ method: method.toUpperCase(),
2635
+ headers: {
2636
+ Accept: 'application/json',
2637
+ ...headers,
2638
+ },
2639
+ body: body !== undefined ? (isJsonObject(body) ? JSON.stringify(body) : body) : undefined,
2640
+ signal: localSignal,
2641
+ });
2898
2642
 
2899
- const currentDate = now instanceof Date ? now : new Date();
2643
+ if (timer) clearTimeout(timer);
2900
2644
 
2901
- let age = currentDate.getFullYear() - birthDate.getFullYear();
2645
+ if (!response.ok) throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
2902
2646
 
2903
- const currentMonth = currentDate.getMonth();
2904
- const birthMonth = birthDate.getMonth();
2647
+ const contentType = response.headers.get('content-type') || '';
2648
+ if (!contentType.includes('application/json'))
2649
+ throw new Error(`Unexpected content-type: ${contentType}`);
2905
2650
 
2906
- const currentDay = currentDate.getDate();
2907
- const birthDay = birthDate.getDate();
2651
+ const data = await response.json();
2908
2652
 
2909
- // Adjust if birthday hasn't occurred yet this year
2910
- if (currentMonth < birthMonth || (currentMonth === birthMonth && currentDay < birthDay)) age--;
2653
+ if (!isJsonObject(data)) throw new Error('Received invalid data instead of valid JSON.');
2911
2654
 
2912
- return Math.abs(age);
2655
+ return data;
2656
+ } catch (err) {
2657
+ lastError = /** @type {Error} */ (err);
2658
+ if (signal) break; // if an external signal came, it does not retry
2659
+ if (attempt < retries)
2660
+ await new Promise((resolve) => setTimeout(resolve, 300 * (attempt + 1)));
2661
+ }
2913
2662
  }
2914
2663
 
2915
- return null;
2664
+ throw new Error(
2665
+ `Failed to fetch JSON from "${url}"${lastError ? `: ${lastError.message}` : '.'}`,
2666
+ );
2916
2667
  }
2917
2668
 
2918
2669
  /**
2919
- * @typedef {Object} FormattedByteResult
2920
- * @property {string|null} unit - The resulting unit (e.g., 'MB', 'GB') or null if input is invalid.
2921
- * @property {number|null} value - The numerical value in the chosen unit, or null if input is invalid.
2670
+ * @typedef {Object} HtmlElBoxSides
2671
+ * @property {number} x - Total horizontal size (left + right)
2672
+ * @property {number} y - Total vertical size (top + bottom)
2673
+ * @property {number} left
2674
+ * @property {number} right
2675
+ * @property {number} top
2676
+ * @property {number} bottom
2922
2677
  */
2923
2678
 
2924
2679
  /**
2925
- * Converts a byte value into a human-readable format with unit and value separated.
2680
+ * Returns the total border width and individual sides from `border{Side}Width` CSS properties.
2926
2681
  *
2927
- * @param {number} bytes - The number of bytes to format. Must be a non-negative number.
2928
- * @param {number|null} [decimals=null] - The number of decimal places to include in the result. Defaults to null. If negative, it will be treated as 0. If null, no rounding is applied.
2929
- * @param {string|null} [maxUnit=null] - Optional unit limit. If provided, restricts conversion to this unit at most (e.g., 'MB' prevents conversion to 'GB' or higher). Must be one of: 'Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'.
2930
- * @returns {FormattedByteResult} An object with the converted value and its corresponding unit. Returns nulls if input is invalid.
2931
- *
2932
- * @example
2933
- * formatBytes(123456789);
2934
- * // → { unit: 'MB', value: 117.74 }
2935
- *
2936
- * @example
2937
- * formatBytes(1073741824, 2, 'MB');
2938
- * // → { unit: 'MB', value: 1024 }
2682
+ * @param {Element} el - The target DOM element.
2683
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border widths, and each side individually.
2939
2684
  */
2940
- function formatBytes(bytes, decimals = null, maxUnit = null) {
2941
- if (typeof bytes !== 'number' || bytes < 0) return { unit: null, value: null };
2942
- if (bytes === 0) return { unit: 'Bytes', value: 0 };
2943
-
2944
- const k = 1024;
2945
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
2685
+ const getHtmlElBordersWidth = (el) => {
2686
+ const styles = getComputedStyle(el);
2687
+ const left = parseFloat(styles.borderLeftWidth) || 0;
2688
+ const right = parseFloat(styles.borderRightWidth) || 0;
2689
+ const top = parseFloat(styles.borderTopWidth) || 0;
2690
+ const bottom = parseFloat(styles.borderBottomWidth) || 0;
2691
+ const x = left + right;
2692
+ const y = top + bottom;
2693
+
2694
+ return { x, y, left, right, top, bottom };
2695
+ };
2946
2696
 
2947
- const maxIndex = maxUnit && sizes.includes(maxUnit) ? sizes.indexOf(maxUnit) : sizes.length - 1;
2948
- const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), maxIndex);
2697
+ /**
2698
+ * Returns the total border size and individual sides from `border{Side}` CSS properties.
2699
+ *
2700
+ * @param {Element} el - The target DOM element.
2701
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) border sizes, and each side individually.
2702
+ */
2703
+ const getHtmlElBorders = (el) => {
2704
+ const styles = getComputedStyle(el);
2705
+ const left = parseFloat(styles.borderLeft) || 0;
2706
+ const right = parseFloat(styles.borderRight) || 0;
2707
+ const top = parseFloat(styles.borderTop) || 0;
2708
+ const bottom = parseFloat(styles.borderBottom) || 0;
2709
+ const x = left + right;
2710
+ const y = top + bottom;
2711
+
2712
+ return { x, y, left, right, top, bottom };
2713
+ };
2949
2714
 
2950
- let value = bytes / Math.pow(k, i);
2715
+ /**
2716
+ * Returns the total margin and individual sides from `margin{Side}` CSS properties.
2717
+ *
2718
+ * @param {Element} el - The target DOM element.
2719
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) margins, and each side individually.
2720
+ */
2721
+ const getHtmlElMargin = (el) => {
2722
+ const styles = getComputedStyle(el);
2723
+ const left = parseFloat(styles.marginLeft) || 0;
2724
+ const right = parseFloat(styles.marginRight) || 0;
2725
+ const top = parseFloat(styles.marginTop) || 0;
2726
+ const bottom = parseFloat(styles.marginBottom) || 0;
2951
2727
 
2952
- if (decimals !== null) {
2953
- const dm = decimals < 0 ? 0 : decimals;
2954
- value = parseFloat(value.toFixed(dm));
2955
- }
2728
+ const x = left + right;
2729
+ const y = top + bottom;
2956
2730
 
2957
- const unit = sizes[i];
2958
- return { unit, value };
2959
- }
2731
+ return { x, y, left, right, top, bottom };
2732
+ };
2960
2733
 
2961
- ;// ./src/v1/basics/text.mjs
2962
2734
  /**
2963
- * Converts a string to title case where the first letter of each word is capitalized.
2964
- * All other letters are converted to lowercase.
2965
- *
2966
- * Example: "hello world" -> "Hello World"
2735
+ * Returns the total padding and individual sides from `padding{Side}` CSS properties.
2967
2736
  *
2968
- * @param {string} str - The string to be converted to title case.
2969
- * @returns {string} The string converted to title case.
2737
+ * @param {Element} el - The target DOM element.
2738
+ * @returns {HtmlElBoxSides} - Total horizontal (x) and vertical (y) paddings, and each side individually.
2970
2739
  */
2971
- function toTitleCase(str) {
2972
- return str.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
2973
- }
2740
+ const getHtmlElPadding = (el) => {
2741
+ const styles = getComputedStyle(el);
2742
+ const left = parseFloat(styles.paddingLeft) || 0;
2743
+ const right = parseFloat(styles.paddingRight) || 0;
2744
+ const top = parseFloat(styles.paddingTop) || 0;
2745
+ const bottom = parseFloat(styles.paddingBottom) || 0;
2746
+
2747
+ const x = left + right;
2748
+ const y = top + bottom;
2749
+
2750
+ return { x, y, left, right, top, bottom };
2751
+ };
2752
+
2753
+ ;// ./src/v1/libs/TinyDragger.mjs
2754
+
2755
+
2974
2756
 
2975
2757
  /**
2976
- * Converts a string to title case where the first letter of each word is capitalized,
2977
- * but the first letter of the entire string is left lowercase.
2978
- *
2979
- * Example: "hello world" -> "hello World"
2980
- *
2981
- * @param {string} str - The string to be converted to title case with the first letter in lowercase.
2982
- * @returns {string} The string converted to title case with the first letter in lowercase.
2758
+ * @typedef {Object} VibrationPatterns
2759
+ * @property {number[]|false} start - Pattern to vibrate on start
2760
+ * @property {number[]|false} end - Pattern to vibrate on end
2761
+ * @property {number[]|false} collide - Pattern to vibrate on collision
2762
+ * @property {number[]|false} move - Pattern to vibrate while moving
2983
2763
  */
2984
- function toTitleCaseLowerFirst(str) {
2985
- const titleCased = str.replace(
2986
- /\w\S*/g,
2987
- (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(),
2988
- );
2989
- return titleCased.charAt(0).toLowerCase() + titleCased.slice(1);
2990
- }
2991
2764
 
2992
2765
  /**
2993
- * Enables a keyboard shortcut to toggle a CSS class on the document body.
2994
- *
2995
- * This function listens for a specific key combination: `Ctrl + Alt + [key]`.
2996
- * When triggered, it prevents the default behavior and toggles the
2997
- * `detect-made-by-ai` class on the `<body>`, which can be used to apply visual
2998
- * indicators or filters on AI-generated content.
2999
- *
3000
- * If executed outside of a browser environment (e.g., in Node.js), the function logs an error and exits.
3001
- * If the `<body>` is not available at the moment the shortcut is triggered, a warning is logged.
3002
- *
3003
- * @param {string} [key='a'] - The lowercase character key to be used in combination with Ctrl and Alt.
2766
+ * TinyDragger enables drag-and-drop functionality for a DOM element.
2767
+ * It supports jail boundaries, optional collision detection, vibration feedback,
2768
+ * automatic reverting, proxy dragging, and event dispatching.
3004
2769
  */
3005
- function addAiMarkerShortcut(key = 'a') {
3006
- if (typeof HTMLElement === 'undefined') {
3007
- console.error(
3008
- '[AiMarkerShortcut] Environment does not support the DOM. This function must be run in a browser.',
2770
+ class TinyDragger {
2771
+ #enabled = true;
2772
+ #destroyed = false;
2773
+
2774
+ #offsetY = 0;
2775
+ #offsetX = 0;
2776
+
2777
+ #lockInsideJail = false;
2778
+ #revertOnDrop = false;
2779
+ #dragging = false;
2780
+ #collisionByMouse = false;
2781
+ #dropInJailOnly = false;
2782
+
2783
+ /** @type {HTMLElement|null} */
2784
+ #lastCollision = null;
2785
+
2786
+ /** @type {HTMLElement[]} */
2787
+ #collidables = [];
2788
+
2789
+ /** @type {HTMLElement|null} */
2790
+ #dragProxy = null;
2791
+
2792
+ /** @type {VibrationPatterns} */
2793
+ #vibration = { start: false, end: false, collide: false, move: false };
2794
+
2795
+ /** @type {HTMLElement|null} */
2796
+ #jail = null;
2797
+
2798
+ #target;
2799
+
2800
+ #dragHiddenClass = 'drag-hidden';
2801
+ #classDragging = 'dragging';
2802
+ #classBodyDragging = 'drag-active';
2803
+ #classJailDragging = 'jail-drag-active';
2804
+ #classJailDragDisabled = 'jail-drag-disabled';
2805
+ #classDragCollision = 'dragging-collision';
2806
+
2807
+ /** @typedef {(event: TouchEvent) => void} TouchDragEvent */
2808
+
2809
+ /**
2810
+ * @param {HTMLElement} targetElement - The element to make draggable.
2811
+ * @param {Object} [options={}] - Configuration options.
2812
+ * @param {HTMLElement} [options.jail] - Optional container to restrict dragging within.
2813
+ * @param {boolean} [options.collisionByMouse=false] - Use mouse position for collision instead of element rect.
2814
+ * @param {string} [options.classDragging='dragging'] - CSS class applied to the clone during dragging.
2815
+ * @param {string} [options.classBodyDragging='drag-active'] - CSS class applied to <body> during dragging.
2816
+ * @param {string} [options.classJailDragging='jail-drag-active'] - CSS class applied to jail element during drag.
2817
+ * @param {string} [options.classJailDragDisabled='jail-drag-disabled'] - CSS class applied to jail element disabled.
2818
+ * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
2819
+ * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
2820
+ * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
2821
+ * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
2822
+ * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
2823
+ * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
2824
+ * @throws {Error} If any option has an invalid type.
2825
+ */
2826
+ constructor(targetElement, options = {}) {
2827
+ if (!(targetElement instanceof HTMLElement))
2828
+ throw new Error('TinyDragger requires a valid target HTMLElement to initialize.');
2829
+
2830
+ this.#target = targetElement;
2831
+
2832
+ // === Validations ===
2833
+ if (options.jail !== undefined && !(options.jail instanceof HTMLElement))
2834
+ throw new Error('The "jail" option must be an HTMLElement if provided.');
2835
+
2836
+ if (
2837
+ options.vibration !== undefined &&
2838
+ options.vibration !== false &&
2839
+ !objFilter_isJsonObject(options.vibration)
2840
+ )
2841
+ throw new Error('The "vibration" option must be an object or false.');
2842
+
2843
+ /**
2844
+ * @param {any} val
2845
+ * @param {string} name
2846
+ */
2847
+ const validateBoolean = (val, name) => {
2848
+ if (val !== undefined && typeof val !== 'boolean') {
2849
+ throw new Error(`The "${name}" option must be a boolean.`);
2850
+ }
2851
+ };
2852
+
2853
+ /**
2854
+ * @param {any} val
2855
+ * @param {string} name
2856
+ */
2857
+ const validateString = (val, name) => {
2858
+ if (val !== undefined && typeof val !== 'string') {
2859
+ throw new Error(`The "${name}" option must be a string.`);
2860
+ }
2861
+ };
2862
+
2863
+ validateBoolean(options.collisionByMouse, 'collisionByMouse');
2864
+ validateBoolean(options.lockInsideJail, 'lockInsideJail');
2865
+ validateBoolean(options.dropInJailOnly, 'dropInJailOnly');
2866
+ validateBoolean(options.revertOnDrop, 'revertOnDrop');
2867
+
2868
+ validateString(options.classDragging, 'classDragging');
2869
+ validateString(options.classBodyDragging, 'classBodyDragging');
2870
+ validateString(options.classJailDragging, 'classJailDragging');
2871
+ validateString(options.classJailDragDisabled, 'classJailDragDisabled');
2872
+ validateString(options.classDragCollision, 'classDragCollision');
2873
+ validateString(options.classHidden, 'classHidden');
2874
+
2875
+ if (options.jail instanceof HTMLElement) this.#jail = options.jail;
2876
+
2877
+ /** @type {VibrationPatterns} */
2878
+ const vibrationTemplate = { start: false, end: false, collide: false, move: false };
2879
+ this.#vibration = Object.assign(
2880
+ vibrationTemplate,
2881
+ objFilter_isJsonObject(options.vibration) ? options.vibration : {},
3009
2882
  );
3010
- return;
3011
- }
3012
- document.addEventListener('keydown', function (event) {
3013
- if (event.ctrlKey && event.altKey && event.key.toLowerCase() === key) {
3014
- event.preventDefault(); // Prevent any default behavior
3015
- if (!document.body) {
3016
- console.warn(
3017
- '[AiMarkerShortcut] <body> element not found. Cannot toggle class. Ensure the DOM is fully loaded when using the shortcut.',
3018
- );
3019
- return;
2883
+
2884
+ if (typeof options.classDragging === 'string') this.#classDragging = options.classDragging;
2885
+ if (typeof options.classBodyDragging === 'string')
2886
+ this.#classBodyDragging = options.classBodyDragging;
2887
+ if (typeof options.classJailDragging === 'string')
2888
+ this.#classJailDragging = options.classJailDragging;
2889
+ if (typeof options.classJailDragDisabled === 'string')
2890
+ this.#classJailDragDisabled = options.classJailDragDisabled;
2891
+ if (typeof options.classHidden === 'string') this.#dragHiddenClass = options.classHidden;
2892
+ if (typeof options.classDragCollision === 'string')
2893
+ this.#classDragCollision = options.classDragCollision;
2894
+
2895
+ if (typeof options.collisionByMouse === 'boolean')
2896
+ this.#collisionByMouse = options.collisionByMouse;
2897
+ if (typeof options.revertOnDrop === 'boolean') this.#revertOnDrop = options.revertOnDrop;
2898
+ if (typeof options.lockInsideJail === 'boolean') this.#lockInsideJail = options.lockInsideJail;
2899
+ if (typeof options.dropInJailOnly === 'boolean') this.#dropInJailOnly = options.dropInJailOnly;
2900
+
2901
+ /** @private */
2902
+ this._onMouseDown = this.#startDrag.bind(this);
2903
+ /** @private */
2904
+ this._onMouseMove = this.#drag.bind(this);
2905
+ /** @private */
2906
+ this._onMouseUp = this.#endDrag.bind(this);
2907
+
2908
+ /**
2909
+ * @type {TouchDragEvent}
2910
+ * @private
2911
+ */
2912
+ this._onTouchStart = (e) => this.#startDrag(e.touches[0]);
2913
+
2914
+ /**
2915
+ * @type {TouchDragEvent}
2916
+ * @private
2917
+ */
2918
+ this._onTouchMove = (e) => this.#drag(e.touches[0]);
2919
+
2920
+ /**
2921
+ * @type {TouchDragEvent}
2922
+ * @private
2923
+ */
2924
+ this._onTouchEnd = (e) => this.#endDrag(e.changedTouches[0]);
2925
+
2926
+ this.#target.addEventListener('mousedown', this._onMouseDown);
2927
+ this.#target.addEventListener('touchstart', this._onTouchStart, { passive: false });
2928
+ }
2929
+
2930
+ /**
2931
+ * Enables the drag functionality.
2932
+ */
2933
+ enable() {
2934
+ this.#checkDestroy();
2935
+ if (this.#jail) this.#jail.classList.add(this.#classJailDragDisabled);
2936
+ this.#enabled = true;
2937
+ }
2938
+
2939
+ /**
2940
+ * Disables the drag functionality.
2941
+ */
2942
+ disable() {
2943
+ if (this.#jail) this.#jail.classList.remove(this.#classJailDragDisabled);
2944
+ this.#enabled = false;
2945
+ }
2946
+
2947
+ /**
2948
+ * Adds an element to be considered for collision detection.
2949
+ * @param {HTMLElement} element - The element to track collisions with.
2950
+ * @throws {Error} If the element is not a valid HTMLElement.
2951
+ */
2952
+ addCollidable(element) {
2953
+ this.#checkDestroy();
2954
+ if (!(element instanceof HTMLElement))
2955
+ throw new Error('addCollidable expects an HTMLElement as argument.');
2956
+ if (!this.#collidables.includes(element)) this.#collidables.push(element);
2957
+ }
2958
+
2959
+ /**
2960
+ * Removes a collidable element from the tracking list.
2961
+ * @param {HTMLElement} element - The element to remove.
2962
+ * @throws {Error} If the element is not a valid HTMLElement.
2963
+ */
2964
+ removeCollidable(element) {
2965
+ this.#checkDestroy();
2966
+ if (!(element instanceof HTMLElement))
2967
+ throw new Error('removeCollidable expects an HTMLElement as argument.');
2968
+ this.#collidables = this.#collidables.filter((el) => el !== element);
2969
+ }
2970
+
2971
+ /**
2972
+ * Sets vibration patterns for drag events.
2973
+ * @param {Object} [param0={}] - Vibration pattern configuration.
2974
+ * @param {number[]|false} [param0.startPattern=false] - Vibration on drag start.
2975
+ * @param {number[]|false} [param0.endPattern=false] - Vibration on drag end.
2976
+ * @param {number[]|false} [param0.collidePattern=false] - Vibration on collision.
2977
+ * @param {number[]|false} [param0.movePattern=false] - Vibration during movement.
2978
+ * @throws {Error} If any pattern is not false or an array of numbers.
2979
+ */
2980
+ setVibrationPattern({
2981
+ startPattern = false,
2982
+ endPattern = false,
2983
+ collidePattern = false,
2984
+ movePattern = false,
2985
+ } = {}) {
2986
+ this.#checkDestroy();
2987
+
2988
+ /** @param {any} value */
2989
+ const isValidPattern = (value) =>
2990
+ value === false || (Array.isArray(value) && value.every((n) => typeof n === 'number'));
2991
+
2992
+ if (!isValidPattern(startPattern))
2993
+ throw new Error('Invalid "startPattern": must be false or an array of numbers.');
2994
+ if (!isValidPattern(endPattern))
2995
+ throw new Error('Invalid "endPattern": must be false or an array of numbers.');
2996
+ if (!isValidPattern(collidePattern))
2997
+ throw new Error('Invalid "collidePattern": must be false or an array of numbers.');
2998
+ if (!isValidPattern(movePattern))
2999
+ throw new Error('Invalid "movePattern": must be false or an array of numbers.');
3000
+
3001
+ this.#vibration = {
3002
+ start: startPattern,
3003
+ end: endPattern,
3004
+ collide: collidePattern,
3005
+ move: movePattern,
3006
+ };
3007
+ }
3008
+
3009
+ /**
3010
+ * Disables all vibration feedback.
3011
+ */
3012
+ disableVibration() {
3013
+ this.#checkDestroy();
3014
+ this.#vibration = { start: false, end: false, collide: false, move: false };
3015
+ }
3016
+
3017
+ /**
3018
+ * Calculates the cursor offset relative to the top-left of the target element.
3019
+ * @param {MouseEvent|Touch} event - The mouse or touch event.
3020
+ * @returns {{x: number, y: number}} The offset in pixels.
3021
+ * @throws {Error} If event is not a MouseEvent or Touch with clientX/clientY.
3022
+ */
3023
+ getOffset(event) {
3024
+ this.#checkDestroy();
3025
+ if (
3026
+ (!(event instanceof MouseEvent) && !(event instanceof Touch)) ||
3027
+ typeof event.clientX !== 'number' ||
3028
+ typeof event.clientY !== 'number'
3029
+ )
3030
+ throw new Error('getOffset expects an event with valid clientX and clientY coordinates.');
3031
+
3032
+ const targetRect = this.#target.getBoundingClientRect();
3033
+ const { left: borderLeft, top: borderTop } = getHtmlElBordersWidth(this.#target);
3034
+ return {
3035
+ x: event.clientX - targetRect.left + borderLeft,
3036
+ y: event.clientY - targetRect.top + borderTop,
3037
+ };
3038
+ }
3039
+
3040
+ /**
3041
+ * Handles the start of a drag event.
3042
+ * @param {MouseEvent|Touch} event - The initiating event.
3043
+ */
3044
+ #startDrag(event) {
3045
+ if (event instanceof MouseEvent) event.preventDefault();
3046
+ if (this.#destroyed || !this.#enabled || !this.#target.parentElement) return;
3047
+
3048
+ const dragProxy = this.#target.cloneNode(true);
3049
+ if (!(dragProxy instanceof HTMLElement)) return;
3050
+ this.#dragProxy = dragProxy;
3051
+
3052
+ this.#dragging = true;
3053
+
3054
+ const rect = this.#target.getBoundingClientRect();
3055
+
3056
+ Object.assign(this.#dragProxy.style, {
3057
+ position: 'absolute',
3058
+ pointerEvents: 'none',
3059
+ left: `${this.#target.offsetLeft}px`,
3060
+ top: `${this.#target.offsetTop}px`,
3061
+ width: `${rect.width}px`,
3062
+ height: `${rect.height}px`,
3063
+ zIndex: 9999,
3064
+ });
3065
+
3066
+ this.#target.classList.add(this.#dragHiddenClass);
3067
+ this.#target.parentElement.appendChild(this.#dragProxy);
3068
+
3069
+ const { x: offsetX, y: offsetY } = this.getOffset(event);
3070
+ this.#offsetX = offsetX;
3071
+ this.#offsetY = offsetY;
3072
+
3073
+ this.#dragProxy.classList.add(this.#classDragging);
3074
+ document.body.classList.add(this.#classBodyDragging);
3075
+ if (this.#jail) this.#jail.classList.add(this.#classJailDragging);
3076
+
3077
+ document.addEventListener('mousemove', this._onMouseMove);
3078
+ document.addEventListener('mouseup', this._onMouseUp);
3079
+
3080
+ document.addEventListener('touchmove', this._onTouchMove, { passive: false });
3081
+ document.addEventListener('touchend', this._onTouchEnd);
3082
+
3083
+ if (navigator.vibrate && Array.isArray(this.#vibration.start)) {
3084
+ navigator.vibrate(this.#vibration.start);
3085
+ }
3086
+
3087
+ this.checkDragCollision(event);
3088
+ this.#dispatchEvent('drag');
3089
+ }
3090
+
3091
+ /**
3092
+ * Handles dragging collision.
3093
+ * @param {MouseEvent|Touch} event - The drag event.
3094
+ */
3095
+ checkDragCollision(event) {
3096
+ const { collidedElement: collided } = this.execCollision(event);
3097
+ if (collided && collided !== this.#lastCollision) {
3098
+ if (navigator.vibrate && Array.isArray(this.#vibration.collide)) {
3099
+ navigator.vibrate(this.#vibration.collide);
3020
3100
  }
3021
- document.body.classList.toggle('detect-made-by-ai');
3101
+ this.#lastCollision = collided;
3102
+ if (this.#lastCollision) this.#lastCollision.classList.add(this.#classDragCollision);
3103
+ } else if (!collided) {
3104
+ if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
3105
+ this.#lastCollision = null;
3022
3106
  }
3023
- });
3024
- }
3107
+ }
3025
3108
 
3026
- /*
3027
- import { useEffect } from "react";
3028
-
3029
- function KeyPressHandler() {
3030
- useEffect(() => {
3031
- const handleKeyDown = (event) => {
3032
- if (event.ctrlKey && event.altKey && event.key.toLowerCase() === "a") {
3033
- event.preventDefault();
3034
- document.body.classList.toggle("detect-made-by-ai");
3035
- }
3036
- };
3037
-
3038
- document.addEventListener("keydown", handleKeyDown);
3039
- return () => {
3040
- document.removeEventListener("keydown", handleKeyDown);
3041
- };
3042
- }, []);
3109
+ /**
3110
+ * Handles dragging movement.
3111
+ * @param {MouseEvent|Touch} event - The drag event.
3112
+ */
3113
+ #drag(event) {
3114
+ if (event instanceof MouseEvent) event.preventDefault();
3115
+ if (this.#destroyed || !this.#dragging || !this.#enabled || !this.#dragProxy) return;
3043
3116
 
3044
- return null;
3045
- }
3117
+ const parent = this.#dragProxy.offsetParent || document.body;
3118
+ const parentRect = parent.getBoundingClientRect();
3119
+
3120
+ let x = event.clientX - parentRect.left - this.#offsetX;
3121
+ let y = event.clientY - parentRect.top - this.#offsetY;
3122
+
3123
+ if (this.#lockInsideJail && this.#jail) {
3124
+ const jailRect = this.#jail.getBoundingClientRect();
3125
+ const targetRect = this.#dragProxy.getBoundingClientRect();
3126
+
3127
+ const jailLeft = jailRect.left - parentRect.left;
3128
+ const jailTop = jailRect.top - parentRect.top;
3129
+
3130
+ const { x: borderX, y: borderY } = getHtmlElBordersWidth(this.#jail);
3131
+ const maxX = jailLeft + jailRect.width - targetRect.width - borderY;
3132
+ const maxY = jailTop + jailRect.height - targetRect.height - borderX;
3133
+
3134
+ x = Math.max(jailLeft, Math.min(x, maxX));
3135
+ y = Math.max(jailTop, Math.min(y, maxY));
3136
+ }
3137
+
3138
+ this.#dragProxy.style.position = 'absolute';
3139
+ this.#dragProxy.style.left = `${x}px`;
3140
+ this.#dragProxy.style.top = `${y}px`;
3141
+
3142
+ if (navigator.vibrate && Array.isArray(this.#vibration.move)) {
3143
+ navigator.vibrate(this.#vibration.move);
3144
+ }
3145
+
3146
+ this.checkDragCollision(event);
3147
+ this.#dispatchEvent('dragging');
3148
+ }
3149
+
3150
+ /**
3151
+ * Handles the collision of a drag.
3152
+ * @param {MouseEvent|Touch} event - The release event.
3153
+ * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
3154
+ */
3155
+ execCollision(event) {
3156
+ if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElement: null };
3157
+
3158
+ let collidedElement;
3159
+ let inJail = true;
3160
+ const jailRect = this.#jail?.getBoundingClientRect();
3161
+ if (this.#collisionByMouse) {
3162
+ const x = event.clientX;
3163
+ const y = event.clientY;
3164
+ if (this.#dropInJailOnly && this.#jail && jailRect) {
3165
+ inJail =
3166
+ x >= jailRect.left && x <= jailRect.right && y >= jailRect.top && y <= jailRect.bottom;
3167
+ }
3168
+
3169
+ collidedElement = inJail ? this.getCollidedElement(x, y) : null;
3170
+ } else {
3171
+ const targetRect = this.#dragProxy.getBoundingClientRect();
3172
+ if (this.#dropInJailOnly && this.#jail && jailRect) {
3173
+ inJail =
3174
+ targetRect.left >= jailRect.left &&
3175
+ targetRect.right <= jailRect.right &&
3176
+ targetRect.top >= jailRect.top &&
3177
+ targetRect.bottom <= jailRect.bottom;
3178
+ }
3179
+
3180
+ collidedElement = inJail ? this.getCollidedElementByRect(targetRect) : null;
3181
+ }
3182
+
3183
+ return { inJail, collidedElement };
3184
+ }
3185
+
3186
+ /**
3187
+ * Handles the end of a drag.
3188
+ * @param {MouseEvent|Touch} event - The release event.
3189
+ */
3190
+ #endDrag(event) {
3191
+ if (event instanceof MouseEvent) event.preventDefault();
3192
+ if (this.#destroyed || !this.#dragging) return;
3193
+
3194
+ this.#dragging = false;
3195
+ if (!this.#dragProxy) return;
3196
+
3197
+ this.#target.classList.remove(this.#classDragging);
3198
+ document.body.classList.remove(this.#classBodyDragging);
3199
+ if (this.#jail) this.#jail.classList.remove(this.#classJailDragging);
3200
+
3201
+ document.removeEventListener('mousemove', this._onMouseMove);
3202
+ document.removeEventListener('mouseup', this._onMouseUp);
3203
+
3204
+ document.removeEventListener('touchmove', this._onTouchMove);
3205
+ document.removeEventListener('touchend', this._onTouchEnd);
3206
+ const { collidedElement } = this.execCollision(event);
3207
+
3208
+ if (navigator.vibrate && Array.isArray(this.#vibration.end)) {
3209
+ navigator.vibrate(this.#vibration.end);
3210
+ }
3211
+
3212
+ const newX = this.#dragProxy.style.left;
3213
+ const newY = this.#dragProxy.style.top;
3214
+ if (this.#dragProxy) {
3215
+ this.#dragProxy.remove();
3216
+ this.#dragProxy = null;
3217
+ }
3218
+
3219
+ if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
3220
+ this.#lastCollision = null;
3221
+
3222
+ this.#target.classList.remove(this.#dragHiddenClass);
3223
+ if (!this.#revertOnDrop) {
3224
+ this.#target.style.left = newX;
3225
+ this.#target.style.top = newY;
3226
+ }
3227
+
3228
+ const dropEvent = new CustomEvent('drop', {
3229
+ detail: {
3230
+ target: collidedElement,
3231
+ },
3232
+ });
3233
+
3234
+ this.#target.dispatchEvent(dropEvent);
3235
+ }
3236
+
3237
+ /**
3238
+ * Detects collision based on rectangle intersection.
3239
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
3240
+ * @returns {HTMLElement|null} The collided element or null.
3241
+ * @throws {Error} If rect is not a DOMRect with valid numeric properties.
3242
+ */
3243
+ getCollidedElementByRect(rect) {
3244
+ this.#checkDestroy();
3245
+ if (
3246
+ !(rect instanceof DOMRect) ||
3247
+ typeof rect.left !== 'number' ||
3248
+ typeof rect.right !== 'number' ||
3249
+ typeof rect.top !== 'number' ||
3250
+ typeof rect.bottom !== 'number'
3251
+ )
3252
+ throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
3253
+
3254
+ return (
3255
+ this.#collidables.find((el) => {
3256
+ const elRect = el.getBoundingClientRect();
3257
+ return !(
3258
+ rect.right < elRect.left ||
3259
+ rect.left > elRect.right ||
3260
+ rect.bottom < elRect.top ||
3261
+ rect.top > elRect.bottom
3262
+ );
3263
+ }) || null
3264
+ );
3265
+ }
3266
+
3267
+ /**
3268
+ * Detects collision with a point using element bounding rectangles.
3269
+ * @param {number} x - Horizontal screen coordinate.
3270
+ * @param {number} y - Vertical screen coordinate.
3271
+ * @returns {HTMLElement|null} The collided element or null.
3272
+ */
3273
+ getCollidedElement(x, y) {
3274
+ this.#checkDestroy();
3275
+ if (typeof x !== 'number' || typeof y !== 'number')
3276
+ throw new Error('getCollidedElement expects numeric x and y coordinates.');
3277
+
3278
+ return (
3279
+ this.#collidables.find((el) => {
3280
+ const rect = el.getBoundingClientRect();
3281
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
3282
+ }) || null
3283
+ );
3284
+ }
3285
+
3286
+ /**
3287
+ * Dispatches a custom event from the target element.
3288
+ * @param {string} type - The event name.
3289
+ */
3290
+ #dispatchEvent(type) {
3291
+ const event = new CustomEvent(type);
3292
+ this.#target.dispatchEvent(event);
3293
+ }
3294
+
3295
+ /**
3296
+ * Gets whether dragging is currently active.
3297
+ * @returns {boolean}
3298
+ */
3299
+ getDragging() {
3300
+ return this.#dragging;
3301
+ }
3302
+
3303
+ /**
3304
+ * Gets whether movement is restricted inside the jail container.
3305
+ * @returns {boolean}
3306
+ */
3307
+ getLockInsideJail() {
3308
+ return this.#lockInsideJail;
3309
+ }
3310
+
3311
+ /**
3312
+ * Sets whether movement is restricted inside the jail container.
3313
+ * @param {boolean} value
3314
+ */
3315
+ setLockInsideJail(value) {
3316
+ if (typeof value !== 'boolean') throw new Error('lockInsideJail must be a boolean.');
3317
+ this.#lockInsideJail = value;
3318
+ }
3046
3319
 
3047
- export default KeyPressHandler;
3048
- */
3320
+ /**
3321
+ * Gets whether the element should revert to original position on drop.
3322
+ * @returns {boolean}
3323
+ */
3324
+ getRevertOnDrop() {
3325
+ return this.#revertOnDrop;
3326
+ }
3049
3327
 
3050
- ;// ./src/v1/basics/index.mjs
3328
+ /**
3329
+ * Sets whether the element should revert to original position on drop.
3330
+ * @param {boolean} value
3331
+ */
3332
+ setRevertOnDrop(value) {
3333
+ if (typeof value !== 'boolean') throw new Error('revertOnDrop must be a boolean.');
3334
+ this.#revertOnDrop = value;
3335
+ }
3051
3336
 
3337
+ /**
3338
+ * Gets whether collision detection uses mouse position.
3339
+ * @returns {boolean}
3340
+ */
3341
+ getCollisionByMouse() {
3342
+ return this.#collisionByMouse;
3343
+ }
3052
3344
 
3345
+ /**
3346
+ * Sets whether collision detection uses mouse position.
3347
+ * @param {boolean} value
3348
+ */
3349
+ setCollisionByMouse(value) {
3350
+ if (typeof value !== 'boolean') throw new Error('collisionByMouse must be a boolean.');
3351
+ this.#collisionByMouse = value;
3352
+ }
3053
3353
 
3354
+ /**
3355
+ * Gets whether dropping is restricted inside the jail container.
3356
+ * @returns {boolean}
3357
+ */
3358
+ getDropInJailOnly() {
3359
+ return this.#dropInJailOnly;
3360
+ }
3361
+
3362
+ /**
3363
+ * Sets whether dropping is restricted inside the jail container.
3364
+ * @param {boolean} value
3365
+ */
3366
+ setDropInJailOnly(value) {
3367
+ if (typeof value !== 'boolean') throw new Error('dropInJailOnly must be a boolean.');
3368
+ this.#dropInJailOnly = value;
3369
+ }
3370
+
3371
+ /**
3372
+ * Returns the original target element being dragged.
3373
+ * @returns {HTMLElement}
3374
+ */
3375
+ getTarget() {
3376
+ return this.#target;
3377
+ }
3378
+
3379
+ /**
3380
+ * Returns the current jail container (if any).
3381
+ * @returns {HTMLElement|null}
3382
+ */
3383
+ getJail() {
3384
+ return this.#jail;
3385
+ }
3386
+
3387
+ /**
3388
+ * Returns the current proxy element being dragged (if any).
3389
+ * @returns {HTMLElement|null}
3390
+ */
3391
+ getDragProxy() {
3392
+ return this.#dragProxy;
3393
+ }
3394
+
3395
+ /**
3396
+ * Returns the last collided element (if any).
3397
+ * @returns {HTMLElement|null}
3398
+ */
3399
+ getLastCollision() {
3400
+ return this.#lastCollision;
3401
+ }
3402
+
3403
+ /**
3404
+ * Returns all registered collidable elements.
3405
+ * @returns {HTMLElement[]}
3406
+ */
3407
+ getCollidables() {
3408
+ return [...this.#collidables];
3409
+ }
3410
+
3411
+ /**
3412
+ * Returns the CSS class used to hide the target during drag.
3413
+ * @returns {string}
3414
+ */
3415
+ getDragHiddenClass() {
3416
+ return this.#dragHiddenClass;
3417
+ }
3418
+
3419
+ /**
3420
+ * Returns the CSS class applied to the clone during dragging.
3421
+ * @returns {string}
3422
+ */
3423
+ getClassDragging() {
3424
+ return this.#classDragging;
3425
+ }
3426
+
3427
+ /**
3428
+ * Returns the CSS class applied to <body> during dragging.
3429
+ * @returns {string}
3430
+ */
3431
+ getClassBodyDragging() {
3432
+ return this.#classBodyDragging;
3433
+ }
3054
3434
 
3435
+ /**
3436
+ * Returns the CSS class applied to the jail during dragging.
3437
+ * @returns {string}
3438
+ */
3439
+ getClassJailDragging() {
3440
+ return this.#classJailDragging;
3441
+ }
3442
+
3443
+ /**
3444
+ * Returns the CSS class applied to the jail when dragging is disabled.
3445
+ * @returns {string}
3446
+ */
3447
+ getClassJailDragDisabled() {
3448
+ return this.#classJailDragDisabled;
3449
+ }
3450
+
3451
+ /**
3452
+ * Returns the CSS class applied to a collided element.
3453
+ * @returns {string}
3454
+ */
3455
+ getClassDragCollision() {
3456
+ return this.#classDragCollision;
3457
+ }
3458
+
3459
+ /**
3460
+ * Returns the full vibration configuration.
3461
+ * @returns {VibrationPatterns}
3462
+ */
3463
+ getVibrations() {
3464
+ return { ...this.#vibration };
3465
+ }
3466
+
3467
+ /**
3468
+ * Returns the vibration pattern for drag start.
3469
+ * @returns {number[]|boolean}
3470
+ */
3471
+ getStartVibration() {
3472
+ return this.#vibration.start;
3473
+ }
3474
+
3475
+ /**
3476
+ * Returns the vibration pattern for drag end.
3477
+ * @returns {number[]|boolean}
3478
+ */
3479
+ getEndVibration() {
3480
+ return this.#vibration.end;
3481
+ }
3482
+
3483
+ /**
3484
+ * Returns the vibration pattern for collisions.
3485
+ * @returns {number[]|boolean}
3486
+ */
3487
+ getCollideVibration() {
3488
+ return this.#vibration.collide;
3489
+ }
3490
+
3491
+ /**
3492
+ * Returns the vibration pattern during movement.
3493
+ * @returns {number[]|boolean}
3494
+ */
3495
+ getMoveVibration() {
3496
+ return this.#vibration.move;
3497
+ }
3498
+
3499
+ /**
3500
+ * Returns whether the dragger is currently enabled.
3501
+ * @returns {boolean}
3502
+ */
3503
+ isEnabled() {
3504
+ return this.#enabled;
3505
+ }
3506
+
3507
+ /**
3508
+ * Internal method to verify if the instance has been destroyed.
3509
+ * Throws an error if any operation is attempted after destruction.
3510
+ */
3511
+ #checkDestroy() {
3512
+ if (this.#destroyed)
3513
+ throw new Error('This TinyDragger instance has been destroyed and can no longer be used.');
3514
+ }
3515
+
3516
+ /**
3517
+ * Completely disables drag-and-drop and cleans up all event listeners.
3518
+ * Does NOT remove the original HTML element.
3519
+ */
3520
+ destroy() {
3521
+ if (this.#destroyed) return;
3522
+ this.disable();
3523
+
3524
+ this.#target.removeEventListener('mousedown', this._onMouseDown);
3525
+ this.#target.removeEventListener('touchstart', this._onTouchStart);
3526
+
3527
+ document.removeEventListener('mousemove', this._onMouseMove);
3528
+ document.removeEventListener('mouseup', this._onMouseUp);
3529
+
3530
+ document.removeEventListener('touchmove', this._onTouchMove);
3531
+ document.removeEventListener('touchend', this._onTouchEnd);
3532
+
3533
+ if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
3534
+ if (this.#dragProxy) {
3535
+ this.#dragProxy.remove();
3536
+ this.#dragProxy = null;
3537
+ }
3538
+
3539
+ this.#collidables = [];
3540
+ this.#dragging = false;
3541
+ this.#lastCollision = null;
3542
+
3543
+ this.#target.classList.remove(this.#dragHiddenClass, this.#classDragging);
3544
+ document.body.classList.remove(this.#classBodyDragging);
3545
+ if (this.#jail)
3546
+ this.#jail.classList.remove(this.#classJailDragging, this.#classJailDragDisabled);
3547
+
3548
+ this.#destroyed = true;
3549
+ }
3550
+ }
3055
3551
 
3552
+ /* harmony default export */ const libs_TinyDragger = (TinyDragger);
3056
3553
 
3554
+ ;// ./src/v1/build/TinyDragger.mjs
3057
3555
 
3058
3556
 
3059
3557
 
3060
3558
 
3061
3559
  })();
3062
3560
 
3063
- window.TinyBasicsEs = __webpack_exports__;
3561
+ window.TinyDragger = __webpack_exports__.TinyDragger;
3064
3562
  /******/ })()
3065
3563
  ;