tiny-essentials 1.12.0 → 1.12.2

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 (32) hide show
  1. package/dist/legacy/firebase/index.mjs +5 -5
  2. package/dist/v1/TinyBasicsEs.js +1 -1257
  3. package/dist/v1/TinyBasicsEs.min.js +1 -1
  4. package/dist/v1/TinyDragger.js +148 -43
  5. package/dist/v1/TinyDragger.min.js +1 -1
  6. package/dist/v1/TinyEssentials.js +484 -189
  7. package/dist/v1/TinyEssentials.min.js +1 -1
  8. package/dist/v1/TinyUploadClicker.js +478 -190
  9. package/dist/v1/TinyUploadClicker.min.js +1 -1
  10. package/dist/v1/basics/index.cjs +0 -23
  11. package/dist/v1/basics/index.d.mts +1 -23
  12. package/dist/v1/basics/index.mjs +1 -2
  13. package/dist/v1/fileManager/asyncFuncs.cjs +272 -0
  14. package/dist/v1/fileManager/asyncFuncs.d.mts +93 -0
  15. package/dist/v1/fileManager/asyncFuncs.mjs +236 -0
  16. package/dist/v1/fileManager/index.cjs +35 -0
  17. package/dist/v1/fileManager/index.d.mts +30 -0
  18. package/dist/v1/fileManager/index.mjs +3 -0
  19. package/dist/v1/{basics/fileManager.cjs → fileManager/normalFuncs.cjs} +10 -105
  20. package/dist/v1/{basics/fileManager.d.mts → fileManager/normalFuncs.d.mts} +3 -56
  21. package/dist/v1/{basics/fileManager.mjs → fileManager/normalFuncs.mjs} +48 -131
  22. package/dist/v1/index.cjs +30 -23
  23. package/dist/v1/index.d.mts +29 -23
  24. package/dist/v1/index.mjs +3 -2
  25. package/dist/v1/libs/TinyDragger.cjs +148 -43
  26. package/dist/v1/libs/TinyDragger.d.mts +18 -2
  27. package/dist/v1/libs/TinyDragger.mjs +132 -38
  28. package/dist/v1/libs/TinyUploadClicker.cjs +1 -0
  29. package/docs/v1/README.md +3 -1
  30. package/docs/v1/{basics/fileManager.md → fileManager/main.md} +12 -0
  31. package/docs/v1/libs/TinyDragger.md +15 -0
  32. package/package.json +5 -1
@@ -1,7 +1,7 @@
1
1
  /******/ (() => { // webpackBootstrap
2
2
  /******/ var __webpack_modules__ = ({
3
3
 
4
- /***/ 41:
4
+ /***/ 133:
5
5
  /***/ (() => {
6
6
 
7
7
  /* (ignored) */
@@ -1924,6 +1924,13 @@ var hexSliceLookupTable = (function () {
1924
1924
  })()
1925
1925
 
1926
1926
 
1927
+ /***/ }),
1928
+
1929
+ /***/ 370:
1930
+ /***/ (() => {
1931
+
1932
+ /* (ignored) */
1933
+
1927
1934
  /***/ }),
1928
1935
 
1929
1936
  /***/ 526:
@@ -3683,10 +3690,10 @@ const getHtmlElPadding = (el) => {
3683
3690
  };
3684
3691
 
3685
3692
  // EXTERNAL MODULE: fs (ignored)
3686
- var fs_ignored_ = __webpack_require__(41);
3693
+ var fs_ignored_ = __webpack_require__(133);
3687
3694
  // EXTERNAL MODULE: ./node_modules/path-browserify/index.js
3688
3695
  var path_browserify = __webpack_require__(975);
3689
- ;// ./src/v1/basics/fileManager.mjs
3696
+ ;// ./src/v1/fileManager/normalFuncs.mjs
3690
3697
 
3691
3698
 
3692
3699
 
@@ -3702,8 +3709,8 @@ var path_browserify = __webpack_require__(975);
3702
3709
  * @returns {any}
3703
3710
  */
3704
3711
  function readJsonFile(filePath) {
3705
- if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3706
- const content = fs.readFileSync(filePath, 'utf-8');
3712
+ if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3713
+ const content = readFileSync(filePath, 'utf-8');
3707
3714
  return JSON.parse(content);
3708
3715
  }
3709
3716
 
@@ -3716,32 +3723,7 @@ function readJsonFile(filePath) {
3716
3723
  */
3717
3724
  function writeJsonFile(filePath, data, spaces = 2) {
3718
3725
  const json = JSON.stringify(data, null, spaces);
3719
- fs.writeFileSync(filePath, json, 'utf-8');
3720
- }
3721
-
3722
- /**
3723
- * Reads and parses a JSON file.
3724
- * Throws an error if the file content is not valid JSON.
3725
- * @param {string} filePath
3726
- * @returns {Promise<any>}
3727
- */
3728
- async function readJsonFileAsync(filePath) {
3729
- if (!fs.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
3730
- const content = await fs.promises.readFile(filePath, 'utf-8');
3731
- return JSON.parse(content);
3732
- }
3733
-
3734
- /**
3735
- * Saves an object as JSON to a file.
3736
- * Automatically creates the directory if it does not exist.
3737
- * @param {string} filePath
3738
- * @param {any} data
3739
- * @param {number} [spaces=2]
3740
- * @returns {Promise<void>}
3741
- */
3742
- function writeJsonFileAsync(filePath, data, spaces = 2) {
3743
- const json = JSON.stringify(data, null, spaces);
3744
- return fs.promises.writeFile(filePath, json, 'utf-8');
3726
+ writeFileSync(filePath, json, 'utf-8');
3745
3727
  }
3746
3728
 
3747
3729
  /*========================*
@@ -3752,9 +3734,9 @@ function writeJsonFileAsync(filePath, data, spaces = 2) {
3752
3734
  * Ensures that the directory exists, creating it recursively if needed.
3753
3735
  * @param {string} dirPath
3754
3736
  */
3755
- function ensureDirectory(dirPath) {
3756
- if (!fs.existsSync(dirPath)) {
3757
- fs.mkdirSync(dirPath, { recursive: true });
3737
+ function normalFuncs_ensureDirectory(dirPath) {
3738
+ if (!existsSync(dirPath)) {
3739
+ mkdirSync(dirPath, { recursive: true });
3758
3740
  }
3759
3741
  }
3760
3742
 
@@ -3763,15 +3745,15 @@ function ensureDirectory(dirPath) {
3763
3745
  * @param {string} dirPath
3764
3746
  */
3765
3747
  function clearDirectory(dirPath) {
3766
- if (!fs.existsSync(dirPath)) return;
3767
- const files = fs.readdirSync(dirPath);
3748
+ if (!existsSync(dirPath)) return;
3749
+ const files = readdirSync(dirPath);
3768
3750
  for (const file of files) {
3769
- const fullPath = path.join(dirPath, file);
3770
- const stat = fs.lstatSync(fullPath);
3771
- if (stat.isDirectory()) {
3772
- fs.rmSync(fullPath, { recursive: true, force: true });
3751
+ const fullPath = join(dirPath, file);
3752
+ const statData = lstatSync(fullPath);
3753
+ if (statData.isDirectory()) {
3754
+ rmSync(fullPath, { recursive: true, force: true });
3773
3755
  } else {
3774
- fs.unlinkSync(fullPath);
3756
+ unlinkSync(fullPath);
3775
3757
  }
3776
3758
  }
3777
3759
  }
@@ -3785,8 +3767,8 @@ function clearDirectory(dirPath) {
3785
3767
  * @param {string} filePath
3786
3768
  * @returns {boolean}
3787
3769
  */
3788
- function fileExists(filePath) {
3789
- return fs.existsSync(filePath) && fs.lstatSync(filePath).isFile();
3770
+ function normalFuncs_fileExists(filePath) {
3771
+ return existsSync(filePath) && lstatSync(filePath).isFile();
3790
3772
  }
3791
3773
 
3792
3774
  /**
@@ -3794,8 +3776,8 @@ function fileExists(filePath) {
3794
3776
  * @param {string} dirPath
3795
3777
  * @returns {boolean}
3796
3778
  */
3797
- function dirExists(dirPath) {
3798
- return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory();
3779
+ function normalFuncs_dirExists(dirPath) {
3780
+ return existsSync(dirPath) && lstatSync(dirPath).isDirectory();
3799
3781
  }
3800
3782
 
3801
3783
  /**
@@ -3804,7 +3786,7 @@ function dirExists(dirPath) {
3804
3786
  * @returns {boolean}
3805
3787
  */
3806
3788
  function isDirEmpty(dirPath) {
3807
- return fs.existsSync(dirPath) && fs.readdirSync(dirPath).length === 0;
3789
+ return readdirSync(dirPath).length === 0;
3808
3790
  }
3809
3791
 
3810
3792
  /*========================*
@@ -3818,8 +3800,8 @@ function isDirEmpty(dirPath) {
3818
3800
  * @param {number} [mode]
3819
3801
  */
3820
3802
  function ensureCopyFile(src, dest, mode) {
3821
- ensureDirectory(path.dirname(dest));
3822
- fs.copyFileSync(src, dest, mode);
3803
+ normalFuncs_ensureDirectory(dirname(dest));
3804
+ copyFileSync(src, dest, mode);
3823
3805
  }
3824
3806
 
3825
3807
  /**
@@ -3828,33 +3810,8 @@ function ensureCopyFile(src, dest, mode) {
3828
3810
  * @returns {boolean}
3829
3811
  */
3830
3812
  function tryDeleteFile(filePath) {
3831
- if (fileExists(filePath)) {
3832
- fs.unlinkSync(filePath);
3833
- return true;
3834
- }
3835
- return false;
3836
- }
3837
-
3838
- /**
3839
- * Copies a file to a destination.
3840
- * @param {string} src
3841
- * @param {string} dest
3842
- * @param {number} [mode]
3843
- * @returns {Promise<void>}
3844
- */
3845
- function ensureCopyFileAsync(src, dest, mode) {
3846
- ensureDirectory(path.dirname(dest));
3847
- return fs.promises.copyFile(src, dest, mode);
3848
- }
3849
-
3850
- /**
3851
- * Deletes a file (If the file exists).
3852
- * @param {string} filePath
3853
- * @returns {Promise<boolean>}
3854
- */
3855
- async function tryDeleteFileAsync(filePath) {
3856
- if (fileExists(filePath)) {
3857
- await fs.promises.unlink(filePath);
3813
+ if (normalFuncs_fileExists(filePath)) {
3814
+ unlinkSync(filePath);
3858
3815
  return true;
3859
3816
  }
3860
3817
  return false;
@@ -3868,25 +3825,12 @@ async function tryDeleteFileAsync(filePath) {
3868
3825
  * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
3869
3826
  * @param {string} filePath
3870
3827
  * @param {string} content
3871
- * @param {fs.WriteFileOptions} [ops='utf-8']
3828
+ * @param {import('fs').WriteFileOptions} [ops='utf-8']
3872
3829
  */
3873
3830
  function writeTextFile(filePath, content, ops = 'utf-8') {
3874
- const dir = path.dirname(filePath);
3875
- ensureDirectory(dir);
3876
- fs.writeFileSync(filePath, content, ops);
3877
- }
3878
-
3879
- /**
3880
- * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
3881
- * @param {string} filePath
3882
- * @param {string} content
3883
- * @param {fs.WriteFileOptions} [ops='utf-8']
3884
- * @returns {Promise<void>}
3885
- */
3886
- function writeTextFileAsync(filePath, content, ops = 'utf-8') {
3887
- const dir = path.dirname(filePath);
3888
- ensureDirectory(dir);
3889
- return fs.promises.writeFile(filePath, content, ops);
3831
+ const dir = dirname(filePath);
3832
+ normalFuncs_ensureDirectory(dir);
3833
+ writeFileSync(filePath, content, ops);
3890
3834
  }
3891
3835
 
3892
3836
  /*========================*
@@ -3902,13 +3846,13 @@ function writeTextFileAsync(filePath, content, ops = 'utf-8') {
3902
3846
  function listFiles(dirPath, recursive = false) {
3903
3847
  /** @type {{ files: string[]; dirs: string[] }} */
3904
3848
  const results = { files: [], dirs: [] };
3905
- if (!dirExists(dirPath)) return results;
3849
+ if (!normalFuncs_dirExists(dirPath)) return results;
3906
3850
 
3907
- const entries = fs.readdirSync(dirPath);
3851
+ const entries = readdirSync(dirPath);
3908
3852
  for (const entry of entries) {
3909
- const fullPath = path.join(dirPath, entry);
3910
- const stat = fs.lstatSync(fullPath);
3911
- if (stat.isDirectory()) {
3853
+ const fullPath = join(dirPath, entry);
3854
+ const statData = lstatSync(fullPath);
3855
+ if (statData.isDirectory()) {
3912
3856
  results.dirs.push(fullPath);
3913
3857
  if (recursive) {
3914
3858
  const results2 = listFiles(fullPath, true);
@@ -3931,13 +3875,13 @@ function listFiles(dirPath, recursive = false) {
3931
3875
  function listDirs(dirPath, recursive = false) {
3932
3876
  /** @type {string[]} */
3933
3877
  const results = [];
3934
- if (!dirExists(dirPath)) return results;
3878
+ if (!normalFuncs_dirExists(dirPath)) return results;
3935
3879
 
3936
- const entries = fs.readdirSync(dirPath);
3880
+ const entries = readdirSync(dirPath);
3937
3881
  for (const entry of entries) {
3938
- const fullPath = path.join(dirPath, entry);
3939
- const stat = fs.lstatSync(fullPath);
3940
- if (stat.isDirectory()) {
3882
+ const fullPath = join(dirPath, entry);
3883
+ const statData = lstatSync(fullPath);
3884
+ if (statData.isDirectory()) {
3941
3885
  results.push(fullPath);
3942
3886
  if (recursive) {
3943
3887
  results.push(...listDirs(fullPath, true));
@@ -3957,8 +3901,8 @@ function listDirs(dirPath, recursive = false) {
3957
3901
  * @returns {number}
3958
3902
  */
3959
3903
  function fileSize(filePath) {
3960
- if (!fileExists(filePath)) return 0;
3961
- const stats = fs.statSync(filePath);
3904
+ if (!normalFuncs_fileExists(filePath)) return 0;
3905
+ const stats = statSync(filePath);
3962
3906
  return stats.size;
3963
3907
  }
3964
3908
 
@@ -3986,7 +3930,7 @@ function dirSize(dirPath) {
3986
3930
  * @param {string} [ext='bak']
3987
3931
  */
3988
3932
  function backupFile(filePath, ext = 'bak') {
3989
- if (!fileExists(filePath)) return;
3933
+ if (!normalFuncs_fileExists(filePath)) return;
3990
3934
  const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
3991
3935
  const backupPath = `${filePath}.${ext}.${timestamp}`;
3992
3936
  ensureCopyFile(filePath, backupPath);
@@ -3998,18 +3942,17 @@ function backupFile(filePath, ext = 'bak') {
3998
3942
  * @param {string} [ext='bak']
3999
3943
  * @returns {string} Full path to the most recent backup
4000
3944
  */
4001
- function getLatestBackupPath(filePath, ext = 'bak') {
4002
- const dir = path.dirname(filePath);
4003
- const baseName = path.basename(filePath);
4004
- const backups = fs
4005
- .readdirSync(dir)
3945
+ function normalFuncs_getLatestBackupPath(filePath, ext = 'bak') {
3946
+ const dir = dirname(filePath);
3947
+ const baseName = basename(filePath);
3948
+ const backups = readdirSync(dir)
4006
3949
  .filter((name) => name.startsWith(`${baseName}.${ext}.`))
4007
3950
  .sort()
4008
3951
  .reverse();
4009
3952
 
4010
3953
  if (backups.length === 0) throw new Error(`No backups found for ${filePath}`);
4011
3954
 
4012
- return path.join(dir, backups[0]);
3955
+ return join(dir, backups[0]);
4013
3956
  }
4014
3957
 
4015
3958
  /**
@@ -4018,34 +3961,10 @@ function getLatestBackupPath(filePath, ext = 'bak') {
4018
3961
  * @param {string} [ext='bak']
4019
3962
  */
4020
3963
  function restoreLatestBackup(filePath, ext = 'bak') {
4021
- const latestBackup = getLatestBackupPath(filePath, ext);
3964
+ const latestBackup = normalFuncs_getLatestBackupPath(filePath, ext);
4022
3965
  ensureCopyFile(latestBackup, filePath);
4023
3966
  }
4024
3967
 
4025
- /**
4026
- * Restores the most recent backup of a file.
4027
- * @param {string} filePath
4028
- * @param {string} [ext='bak']
4029
- * @returns {Promise<void>}
4030
- */
4031
- function restoreLatestBackupAsync(filePath, ext = 'bak') {
4032
- const latestBackup = getLatestBackupPath(filePath, ext);
4033
- return ensureCopyFileAsync(latestBackup, filePath);
4034
- }
4035
-
4036
- /**
4037
- * Creates a backup copy of a file with .bak timestamp suffix.
4038
- * @param {string} filePath
4039
- * @param {string} [ext='bak']
4040
- * @returns {Promise<void>}
4041
- */
4042
- async function backupFileAsync(filePath, ext = 'bak') {
4043
- if (!fileExists(filePath)) return;
4044
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
4045
- const backupPath = `${filePath}.${ext}.${timestamp}`;
4046
- return ensureCopyFileAsync(filePath, backupPath);
4047
- }
4048
-
4049
3968
  /*========================*
4050
3969
  * Rename Utilities
4051
3970
  *========================*/
@@ -4064,7 +3983,7 @@ function renameFileBatch(dirPath, renameFn, extensions = []) {
4064
3983
  if (typeof dirPath !== 'string') throw new TypeError('dirPath must be a string');
4065
3984
  if (typeof renameFn !== 'function') throw new TypeError('renameFn must be a function');
4066
3985
  if (!Array.isArray(extensions)) throw new TypeError('extensions must be an array of strings');
4067
- if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory())
3986
+ if (!existsSync(dirPath) || !statSync(dirPath).isDirectory())
4068
3987
  throw new Error(`Directory not found or invalid: ${dirPath}`);
4069
3988
  for (const ext of extensions) {
4070
3989
  if (typeof ext !== 'string' || !ext.startsWith('.'))
@@ -4075,16 +3994,16 @@ function renameFileBatch(dirPath, renameFn, extensions = []) {
4075
3994
  let index = 0;
4076
3995
 
4077
3996
  for (const file of files) {
4078
- const ext = path.extname(file);
3997
+ const ext = extname(file);
4079
3998
  if (extensions.length && !extensions.includes(ext)) continue;
4080
3999
 
4081
- const originalName = path.basename(file);
4000
+ const originalName = basename(file);
4082
4001
  const newName = renameFn(originalName, index++);
4083
- const newPath = path.join(dirPath, newName);
4002
+ const newPath = join(dirPath, newName);
4084
4003
 
4085
4004
  if (originalName === newName) continue;
4086
4005
 
4087
- fs.renameSync(file, newPath);
4006
+ renameSync(file, newPath);
4088
4007
  }
4089
4008
  }
4090
4009
 
@@ -4099,8 +4018,8 @@ function renameFileRegex(dirPath, pattern, replacement, extensions = []) {
4099
4018
  renameFileBatch(
4100
4019
  dirPath,
4101
4020
  (filename) => {
4102
- const ext = path.extname(filename);
4103
- const name = path.basename(filename, ext).replace(pattern, replacement);
4021
+ const ext = extname(filename);
4022
+ const name = basename(filename, ext).replace(pattern, replacement);
4104
4023
  return `${name}${ext}`;
4105
4024
  },
4106
4025
  extensions,
@@ -4117,8 +4036,8 @@ function renameFileAddPrefixSuffix(dirPath, { prefix = '', suffix = '' }, extens
4117
4036
  renameFileBatch(
4118
4037
  dirPath,
4119
4038
  (filename) => {
4120
- const ext = path.extname(filename);
4121
- const name = path.basename(filename, ext);
4039
+ const ext = extname(filename);
4040
+ const name = basename(filename, ext);
4122
4041
  return `${prefix}${name}${suffix}${ext}`;
4123
4042
  },
4124
4043
  extensions,
@@ -4155,9 +4074,9 @@ function renameFileNormalizeCase(
4155
4074
  else return text;
4156
4075
  };
4157
4076
 
4158
- const rawExt = path.extname(filename);
4077
+ const rawExt = extname(filename);
4159
4078
  const ext = normalizeExt ? changeToMode(rawExt) : rawExt;
4160
- const name = changeToMode(path.basename(filename, rawExt));
4079
+ const name = changeToMode(basename(filename, rawExt));
4161
4080
  return `${name}${ext}`;
4162
4081
  },
4163
4082
  extensions,
@@ -4180,6 +4099,266 @@ function renameFilePadNumbers(dirPath, totalDigits = 3, extensions = []) {
4180
4099
  );
4181
4100
  }
4182
4101
 
4102
+ // EXTERNAL MODULE: fs/promises (ignored)
4103
+ var promises_ignored_ = __webpack_require__(370);
4104
+ ;// ./src/v1/fileManager/asyncFuncs.mjs
4105
+
4106
+
4107
+
4108
+
4109
+
4110
+ /*========================*
4111
+ * JSON Operations
4112
+ *========================*/
4113
+
4114
+ /**
4115
+ * Reads and parses a JSON file.
4116
+ * Throws an error if the file content is not valid JSON.
4117
+ * @param {string} filePath
4118
+ * @returns {Promise<any>}
4119
+ */
4120
+ async function readJsonFileAsync(filePath) {
4121
+ if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
4122
+ const content = await readFile(filePath, 'utf-8');
4123
+ return JSON.parse(content);
4124
+ }
4125
+
4126
+ /**
4127
+ * Saves an object as JSON to a file.
4128
+ * Automatically creates the directory if it does not exist.
4129
+ * @param {string} filePath
4130
+ * @param {any} data
4131
+ * @param {number} [spaces=2]
4132
+ * @returns {Promise<void>}
4133
+ */
4134
+ function writeJsonFileAsync(filePath, data, spaces = 2) {
4135
+ const json = JSON.stringify(data, null, spaces);
4136
+ return writeFile(filePath, json, 'utf-8');
4137
+ }
4138
+
4139
+ /*========================*
4140
+ * Directory Management
4141
+ *========================*/
4142
+
4143
+ /**
4144
+ * Clears all contents inside a directory but keeps the directory.
4145
+ * @param {string} dirPath
4146
+ */
4147
+ async function clearDirectoryAsync(dirPath) {
4148
+ if (!existsSync(dirPath)) return;
4149
+ const files = await readdir(dirPath);
4150
+
4151
+ /** @type {Record<string, import('fs').Stats>} */
4152
+ const dataList = {};
4153
+ const promises = [];
4154
+
4155
+ for (const file of files) {
4156
+ const fullPath = join(dirPath, file);
4157
+ const lsResult = lstat(fullPath);
4158
+
4159
+ lsResult.then((statData) => {
4160
+ dataList[fullPath] = statData;
4161
+ return statData;
4162
+ });
4163
+ promises.push(lsResult);
4164
+ }
4165
+
4166
+ await Promise.all(promises);
4167
+ const promises2 = [];
4168
+ for (const fullPath in dataList) {
4169
+ const statData = dataList[fullPath];
4170
+ if (statData.isDirectory()) {
4171
+ promises2.push(rm(fullPath, { recursive: true, force: true }));
4172
+ } else {
4173
+ promises2.push(unlink(fullPath));
4174
+ }
4175
+ }
4176
+
4177
+ await Promise.all(promises2);
4178
+ }
4179
+
4180
+ /*========================*
4181
+ * File Checks
4182
+ *========================*/
4183
+
4184
+ /**
4185
+ * Checks whether a directory is empty.
4186
+ * @param {string} dirPath
4187
+ * @returns {Promise<boolean>}
4188
+ */
4189
+ async function isDirEmptyAsync(dirPath) {
4190
+ const data = await readdir(dirPath);
4191
+ return data.length === 0;
4192
+ }
4193
+
4194
+ /*========================*
4195
+ * File Operations
4196
+ *========================*/
4197
+
4198
+ /**
4199
+ * Copies a file to a destination.
4200
+ * @param {string} src
4201
+ * @param {string} dest
4202
+ * @param {number} [mode]
4203
+ * @returns {Promise<void>}
4204
+ */
4205
+ function ensureCopyFileAsync(src, dest, mode) {
4206
+ ensureDirectory(dirname(dest));
4207
+ return copyFile(src, dest, mode);
4208
+ }
4209
+
4210
+ /**
4211
+ * Deletes a file (If the file exists).
4212
+ * @param {string} filePath
4213
+ * @returns {Promise<boolean>}
4214
+ */
4215
+ async function tryDeleteFileAsync(filePath) {
4216
+ if (fileExists(filePath)) {
4217
+ await unlink(filePath);
4218
+ return true;
4219
+ }
4220
+ return false;
4221
+ }
4222
+
4223
+ /*========================*
4224
+ * Text Operations
4225
+ *========================*/
4226
+
4227
+ /**
4228
+ * Writes text to a file (Ensures that the directory exists, creating it recursively if needed).
4229
+ * @param {string} filePath
4230
+ * @param {string} content
4231
+ * @param {import('fs').WriteFileOptions} [ops='utf-8']
4232
+ * @returns {Promise<void>}
4233
+ */
4234
+ function writeTextFileAsync(filePath, content, ops = 'utf-8') {
4235
+ const dir = dirname(filePath);
4236
+ ensureDirectory(dir);
4237
+ return writeFile(filePath, content, ops);
4238
+ }
4239
+
4240
+ /*========================*
4241
+ * Directory Listings
4242
+ *========================*/
4243
+
4244
+ /**
4245
+ * Lists all files and dirs in a directory (optionally recursive).
4246
+ * @param {string} dirPath
4247
+ * @param {boolean} [recursive=false]
4248
+ * @returns {Promise<{ files: string[]; dirs: string[] }>}
4249
+ */
4250
+ async function listFilesAsync(dirPath, recursive = false) {
4251
+ /** @type {{ files: string[]; dirs: string[] }} */
4252
+ const results = { files: [], dirs: [] };
4253
+ if (!dirExists(dirPath)) return results;
4254
+
4255
+ const entries = await readdir(dirPath);
4256
+ for (const entry of entries) {
4257
+ const fullPath = join(dirPath, entry);
4258
+ const statData = await lstat(fullPath);
4259
+ if (statData.isDirectory()) {
4260
+ results.dirs.push(fullPath);
4261
+ if (recursive) {
4262
+ const results2 = await listFilesAsync(fullPath, true);
4263
+ results.files.push(...results2.files);
4264
+ results.dirs.push(...results2.dirs);
4265
+ }
4266
+ } else {
4267
+ results.files.push(fullPath);
4268
+ }
4269
+ }
4270
+ return results;
4271
+ }
4272
+
4273
+ /**
4274
+ * Lists all directories in a directory (optionally recursive).
4275
+ * @param {string} dirPath
4276
+ * @param {boolean} [recursive=false]
4277
+ * @returns {Promise<string[]>}
4278
+ */
4279
+ async function listDirsAsync(dirPath, recursive = false) {
4280
+ /** @type {string[]} */
4281
+ const results = [];
4282
+ if (!dirExists(dirPath)) return results;
4283
+
4284
+ const entries = await readdir(dirPath);
4285
+ for (const entry of entries) {
4286
+ const fullPath = join(dirPath, entry);
4287
+ const statData = await lstat(fullPath);
4288
+ if (statData.isDirectory()) {
4289
+ results.push(fullPath);
4290
+ if (recursive) {
4291
+ results.push(...(await listDirsAsync(fullPath, true)));
4292
+ }
4293
+ }
4294
+ }
4295
+ return results;
4296
+ }
4297
+
4298
+ /*========================*
4299
+ * File Info
4300
+ *========================*/
4301
+
4302
+ /**
4303
+ * Returns the size of a file in bytes.
4304
+ * @param {string} filePath
4305
+ * @returns {Promise<number>}
4306
+ */
4307
+ async function fileSizeAsync(filePath) {
4308
+ if (!fileExists(filePath)) return 0;
4309
+ const stats = await stat(filePath);
4310
+ return stats.size;
4311
+ }
4312
+
4313
+ /**
4314
+ * Returns the total size of a directory in bytes.
4315
+ * @param {string} dirPath
4316
+ * @returns {Promise<number>}
4317
+ */
4318
+ async function dirSizeAsync(dirPath) {
4319
+ let total = 0;
4320
+ const { files } = await listFilesAsync(dirPath, true);
4321
+ const promises = [];
4322
+ for (const file of files) {
4323
+ const result = fileSizeAsync(file);
4324
+ result.then((item) => {
4325
+ total += item;
4326
+ return item;
4327
+ });
4328
+ promises.push(result);
4329
+ }
4330
+ await Promise.all(promises);
4331
+ return total;
4332
+ }
4333
+
4334
+ /*========================*
4335
+ * Backup Utilities
4336
+ *========================*/
4337
+
4338
+ /**
4339
+ * Restores the most recent backup of a file.
4340
+ * @param {string} filePath
4341
+ * @param {string} [ext='bak']
4342
+ * @returns {Promise<void>}
4343
+ */
4344
+ function restoreLatestBackupAsync(filePath, ext = 'bak') {
4345
+ const latestBackup = getLatestBackupPath(filePath, ext);
4346
+ return ensureCopyFileAsync(latestBackup, filePath);
4347
+ }
4348
+
4349
+ /**
4350
+ * Creates a backup copy of a file with .bak timestamp suffix.
4351
+ * @param {string} filePath
4352
+ * @param {string} [ext='bak']
4353
+ * @returns {Promise<void>}
4354
+ */
4355
+ async function backupFileAsync(filePath, ext = 'bak') {
4356
+ if (!fileExists(filePath)) return;
4357
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
4358
+ const backupPath = `${filePath}.${ext}.${timestamp}`;
4359
+ return ensureCopyFileAsync(filePath, backupPath);
4360
+ }
4361
+
4183
4362
  ;// ./src/v1/libs/TinyDragger.mjs
4184
4363
 
4185
4364
 
@@ -4204,6 +4383,7 @@ class TinyDragger {
4204
4383
  #offsetY = 0;
4205
4384
  #offsetX = 0;
4206
4385
 
4386
+ #multiCollision = false;
4207
4387
  #lockInsideJail = false;
4208
4388
  #revertOnDrop = false;
4209
4389
  #dragging = false;
@@ -4248,6 +4428,7 @@ class TinyDragger {
4248
4428
  * @param {string} [options.classDragCollision='dragging-collision'] - CSS class applied to collision element.
4249
4429
  * @param {boolean} [options.lockInsideJail=false] - Restrict movement within the jail container.
4250
4430
  * @param {boolean} [options.dropInJailOnly=false] - Restrict drop within the jail container.
4431
+ * @param {boolean} [options.multiCollision=false] - Enables returning multiple collided elements.
4251
4432
  * @param {VibrationPatterns|false} [options.vibration=false] - Vibration feedback configuration.
4252
4433
  * @param {boolean} [options.revertOnDrop=false] - Whether to return to original position on drop.
4253
4434
  * @param {string} [options.classHidden='drag-hidden'] - CSS class to hide original element during dragging.
@@ -4294,6 +4475,7 @@ class TinyDragger {
4294
4475
  validateBoolean(options.lockInsideJail, 'lockInsideJail');
4295
4476
  validateBoolean(options.dropInJailOnly, 'dropInJailOnly');
4296
4477
  validateBoolean(options.revertOnDrop, 'revertOnDrop');
4478
+ validateBoolean(options.multiCollision, 'multiCollision');
4297
4479
 
4298
4480
  validateString(options.classDragging, 'classDragging');
4299
4481
  validateString(options.classBodyDragging, 'classBodyDragging');
@@ -4327,6 +4509,7 @@ class TinyDragger {
4327
4509
  if (typeof options.revertOnDrop === 'boolean') this.#revertOnDrop = options.revertOnDrop;
4328
4510
  if (typeof options.lockInsideJail === 'boolean') this.#lockInsideJail = options.lockInsideJail;
4329
4511
  if (typeof options.dropInJailOnly === 'boolean') this.#dropInJailOnly = options.dropInJailOnly;
4512
+ if (typeof options.multiCollision === 'boolean') this.#multiCollision = options.multiCollision;
4330
4513
 
4331
4514
  /** @private */
4332
4515
  this._onMouseDown = this.#startDrag.bind(this);
@@ -4518,22 +4701,67 @@ class TinyDragger {
4518
4701
  this.#dispatchEvent('drag');
4519
4702
  }
4520
4703
 
4704
+ /** @type {HTMLElement[]} */
4705
+ #collisionsMarked = [];
4706
+
4707
+ /**
4708
+ * Marks an element as currently collided by adding the collision CSS class.
4709
+ * The element is stored in an internal list for easy removal later.
4710
+ *
4711
+ * @param {HTMLElement|null} el - The element to mark as collided.
4712
+ */
4713
+ #addCollision(el) {
4714
+ if (!el) return;
4715
+ el.classList.add(this.#classDragCollision);
4716
+ this.#collisionsMarked.push(el);
4717
+ }
4718
+
4719
+ /**
4720
+ * Removes the collision CSS class from all previously marked elements.
4721
+ * Also clears the last single collision element, if set.
4722
+ *
4723
+ */
4724
+ #removeCollision() {
4725
+ while (this.#collisionsMarked.length > 0) {
4726
+ const el = this.#collisionsMarked.shift();
4727
+ if (el) el.classList.remove(this.#classDragCollision);
4728
+ }
4729
+ if (!this.#lastCollision) return;
4730
+ this.#lastCollision.classList.remove(this.#classDragCollision);
4731
+ }
4732
+
4521
4733
  /**
4522
4734
  * Handles dragging collision.
4523
4735
  * @param {MouseEvent|Touch} event - The drag event.
4524
4736
  */
4525
4737
  checkDragCollision(event) {
4526
- const { collidedElement: collided } = this.execCollision(event);
4527
- if (collided && collided !== this.#lastCollision) {
4528
- if (navigator.vibrate && Array.isArray(this.#vibration.collide)) {
4529
- navigator.vibrate(this.#vibration.collide);
4738
+ const { collidedElements } = this.execCollision(event);
4739
+ const first = collidedElements[0] || null;
4740
+
4741
+ // Removes old marking if necessary
4742
+ if (this.#lastCollision && !collidedElements.includes(this.#lastCollision)) {
4743
+ this.#removeCollision();
4744
+ }
4745
+
4746
+ // Adds Marking for All Colluded
4747
+ collidedElements.forEach((el) => this.#addCollision(el));
4748
+
4749
+ // Removes markings from who no longer collided
4750
+ this.#collidables.forEach((el) => {
4751
+ if (!collidedElements.includes(el)) {
4752
+ el.classList.remove(this.#classDragCollision);
4530
4753
  }
4531
- this.#lastCollision = collided;
4532
- if (this.#lastCollision) this.#lastCollision.classList.add(this.#classDragCollision);
4533
- } else if (!collided) {
4534
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
4535
- this.#lastCollision = null;
4754
+ });
4755
+
4756
+ if (
4757
+ navigator.vibrate &&
4758
+ Array.isArray(this.#vibration.collide) &&
4759
+ collidedElements.length > 0
4760
+ ) {
4761
+ navigator.vibrate(this.#vibration.collide);
4536
4762
  }
4763
+
4764
+ this.#lastCollision = first;
4537
4765
  }
4538
4766
 
4539
4767
  /**
@@ -4580,37 +4808,48 @@ class TinyDragger {
4580
4808
  /**
4581
4809
  * Handles the collision of a drag.
4582
4810
  * @param {MouseEvent|Touch} event - The release event.
4583
- * @returns {{ inJail: boolean; collidedElement: HTMLElement|null }}
4811
+ * @returns {{ inJail: boolean; collidedElements: (HTMLElement | null)[] }}
4584
4812
  */
4585
4813
  execCollision(event) {
4586
- if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElement: null };
4814
+ if (this.#destroyed || !this.#dragProxy) return { inJail: false, collidedElements: [] };
4587
4815
 
4588
- let collidedElement;
4816
+ let collidedElements = [];
4589
4817
  let inJail = true;
4590
4818
  const jailRect = this.#jail?.getBoundingClientRect();
4819
+
4591
4820
  if (this.#collisionByMouse) {
4592
4821
  const x = event.clientX;
4593
4822
  const y = event.clientY;
4823
+
4594
4824
  if (this.#dropInJailOnly && this.#jail && jailRect) {
4595
4825
  inJail =
4596
4826
  x >= jailRect.left && x <= jailRect.right && y >= jailRect.top && y <= jailRect.bottom;
4597
4827
  }
4598
4828
 
4599
- collidedElement = inJail ? this.getCollidedElement(x, y) : null;
4829
+ collidedElements = inJail
4830
+ ? this.#multiCollision
4831
+ ? this.getAllCollidedElements(x, y)
4832
+ : [this.getCollidedElement(x, y)].filter(Boolean)
4833
+ : [];
4600
4834
  } else {
4601
- const targetRect = this.#dragProxy.getBoundingClientRect();
4835
+ const rect = this.#dragProxy.getBoundingClientRect();
4836
+
4602
4837
  if (this.#dropInJailOnly && this.#jail && jailRect) {
4603
4838
  inJail =
4604
- targetRect.left >= jailRect.left &&
4605
- targetRect.right <= jailRect.right &&
4606
- targetRect.top >= jailRect.top &&
4607
- targetRect.bottom <= jailRect.bottom;
4839
+ rect.left >= jailRect.left &&
4840
+ rect.right <= jailRect.right &&
4841
+ rect.top >= jailRect.top &&
4842
+ rect.bottom <= jailRect.bottom;
4608
4843
  }
4609
4844
 
4610
- collidedElement = inJail ? this.getCollidedElementByRect(targetRect) : null;
4845
+ collidedElements = inJail
4846
+ ? this.#multiCollision
4847
+ ? this.getAllCollidedElementsByRect(rect)
4848
+ : [this.getCollidedElementByRect(rect)].filter(Boolean)
4849
+ : [];
4611
4850
  }
4612
4851
 
4613
- return { inJail, collidedElement };
4852
+ return { inJail, collidedElements };
4614
4853
  }
4615
4854
 
4616
4855
  /**
@@ -4633,7 +4872,7 @@ class TinyDragger {
4633
4872
 
4634
4873
  document.removeEventListener('touchmove', this._onTouchMove);
4635
4874
  document.removeEventListener('touchend', this._onTouchEnd);
4636
- const { collidedElement } = this.execCollision(event);
4875
+ const { collidedElements } = this.execCollision(event);
4637
4876
 
4638
4877
  if (navigator.vibrate && Array.isArray(this.#vibration.end)) {
4639
4878
  navigator.vibrate(this.#vibration.end);
@@ -4646,7 +4885,7 @@ class TinyDragger {
4646
4885
  this.#dragProxy = null;
4647
4886
  }
4648
4887
 
4649
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
4888
+ if (this.#lastCollision) this.#removeCollision();
4650
4889
  this.#lastCollision = null;
4651
4890
 
4652
4891
  this.#target.classList.remove(this.#dragHiddenClass);
@@ -4657,13 +4896,50 @@ class TinyDragger {
4657
4896
 
4658
4897
  const dropEvent = new CustomEvent('drop', {
4659
4898
  detail: {
4660
- target: collidedElement,
4899
+ targets: collidedElements,
4900
+ first: collidedElements[0] || null,
4661
4901
  },
4662
4902
  });
4663
-
4664
4903
  this.#target.dispatchEvent(dropEvent);
4665
4904
  }
4666
4905
 
4906
+ /**
4907
+ * Checks if the provided element intersects with the given bounding rectangle.
4908
+ *
4909
+ * @param {HTMLElement} el - The element to test for collision.
4910
+ * @param {DOMRect} rect - The bounding rectangle to check against.
4911
+ * @returns {boolean} True if the element intersects with the rectangle.
4912
+ */
4913
+ #getCollidedElementByRect(el, rect) {
4914
+ const elRect = el.getBoundingClientRect();
4915
+ return !(
4916
+ rect.right < elRect.left ||
4917
+ rect.left > elRect.right ||
4918
+ rect.bottom < elRect.top ||
4919
+ rect.top > elRect.bottom
4920
+ );
4921
+ }
4922
+
4923
+ /**
4924
+ * Returns all elements currently colliding with the given rectangle.
4925
+ *
4926
+ * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
4927
+ * @returns {HTMLElement[]} A list of all collided elements.
4928
+ * @throws {Error} If the input is not a valid DOMRect with numeric bounds.
4929
+ */
4930
+ getAllCollidedElementsByRect(rect) {
4931
+ this.#checkDestroy();
4932
+ if (
4933
+ !(rect instanceof DOMRect) ||
4934
+ typeof rect.left !== 'number' ||
4935
+ typeof rect.right !== 'number' ||
4936
+ typeof rect.top !== 'number' ||
4937
+ typeof rect.bottom !== 'number'
4938
+ )
4939
+ throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
4940
+ return this.#collidables.filter((el) => this.#getCollidedElementByRect(el, rect));
4941
+ }
4942
+
4667
4943
  /**
4668
4944
  * Detects collision based on rectangle intersection.
4669
4945
  * @param {DOMRect} rect - Bounding rectangle of the dragged proxy.
@@ -4680,18 +4956,32 @@ class TinyDragger {
4680
4956
  typeof rect.bottom !== 'number'
4681
4957
  )
4682
4958
  throw new Error('getCollidedElementByRect expects a valid DOMRect object.');
4959
+ return this.#collidables.find((el) => this.#getCollidedElementByRect(el, rect)) || null;
4960
+ }
4683
4961
 
4684
- return (
4685
- this.#collidables.find((el) => {
4686
- const elRect = el.getBoundingClientRect();
4687
- return !(
4688
- rect.right < elRect.left ||
4689
- rect.left > elRect.right ||
4690
- rect.bottom < elRect.top ||
4691
- rect.top > elRect.bottom
4692
- );
4693
- }) || null
4694
- );
4962
+ /**
4963
+ * Checks whether a given (x, y) coordinate is inside the bounding rectangle of an element.
4964
+ *
4965
+ * @param {HTMLElement} el - The element to test for collision.
4966
+ * @param {number} x - Horizontal screen coordinate.
4967
+ * @param {number} y - Vertical screen coordinate.
4968
+ * @returns {boolean} True if the point is within the element's bounds.
4969
+ */
4970
+ #getCollidedElement(el, x, y) {
4971
+ const rect = el.getBoundingClientRect();
4972
+ return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
4973
+ }
4974
+
4975
+ /**
4976
+ * @param {number} x - Horizontal screen coordinate.
4977
+ * @param {number} y - Vertical screen coordinate.
4978
+ * @returns {HTMLElement[]} The collided element or null.
4979
+ */
4980
+ getAllCollidedElements(x, y) {
4981
+ this.#checkDestroy();
4982
+ if (typeof x !== 'number' || typeof y !== 'number')
4983
+ throw new Error('getCollidedElement expects numeric x and y coordinates.');
4984
+ return this.#collidables.filter((el) => this.#getCollidedElement(el, x, y));
4695
4985
  }
4696
4986
 
4697
4987
  /**
@@ -4704,13 +4994,7 @@ class TinyDragger {
4704
4994
  this.#checkDestroy();
4705
4995
  if (typeof x !== 'number' || typeof y !== 'number')
4706
4996
  throw new Error('getCollidedElement expects numeric x and y coordinates.');
4707
-
4708
- return (
4709
- this.#collidables.find((el) => {
4710
- const rect = el.getBoundingClientRect();
4711
- return x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom;
4712
- }) || null
4713
- );
4997
+ return this.#collidables.find((el) => this.#getCollidedElement(el, x, y)) || null;
4714
4998
  }
4715
4999
 
4716
5000
  /**
@@ -4960,7 +5244,7 @@ class TinyDragger {
4960
5244
  document.removeEventListener('touchmove', this._onTouchMove);
4961
5245
  document.removeEventListener('touchend', this._onTouchEnd);
4962
5246
 
4963
- if (this.#lastCollision) this.#lastCollision.classList.remove(this.#classDragCollision);
5247
+ if (this.#lastCollision) this.#removeCollision();
4964
5248
  if (this.#dragProxy) {
4965
5249
  this.#dragProxy.remove();
4966
5250
  this.#dragProxy = null;
@@ -4998,6 +5282,10 @@ class TinyDragger {
4998
5282
 
4999
5283
 
5000
5284
 
5285
+
5286
+
5287
+
5288
+
5001
5289
 
5002
5290
 
5003
5291