@kupola/kupola 1.2.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 (354) hide show
  1. package/CHANGELOG.md +130 -0
  2. package/INTEGRATION.md +440 -0
  3. package/PROJECT_SUMMARY.md +312 -0
  4. package/README.md +286 -0
  5. package/SKILL.md +572 -0
  6. package/css/accessibility.css +119 -0
  7. package/css/animations.css +224 -0
  8. package/css/brand-themes.css +300 -0
  9. package/css/colors_and_type.css +441 -0
  10. package/css/components-ext.css +4165 -0
  11. package/css/components.css +1483 -0
  12. package/css/kupola.css +12 -0
  13. package/css/responsive.css +697 -0
  14. package/css/scaffold.css +145 -0
  15. package/css/states.css +316 -0
  16. package/css/theme-dark.css +296 -0
  17. package/css/theme-light.css +296 -0
  18. package/css/utilities.css +171 -0
  19. package/dist/css/kupola.css +12 -0
  20. package/dist/icons/icons/activity.svg +3 -0
  21. package/dist/icons/icons/alert-circle.svg +1 -0
  22. package/dist/icons/icons/alert-triangle.svg +1 -0
  23. package/dist/icons/icons/alert.d27e24.svg +1 -0
  24. package/dist/icons/icons/alert.svg +1 -0
  25. package/dist/icons/icons/apple.svg +1 -0
  26. package/dist/icons/icons/arrow-down.svg +1 -0
  27. package/dist/icons/icons/arrow-expand.svg +1 -0
  28. package/dist/icons/icons/arrow-left.svg +1 -0
  29. package/dist/icons/icons/arrow-minimize.svg +1 -0
  30. package/dist/icons/icons/arrow-right-to-line.svg +1 -0
  31. package/dist/icons/icons/arrow-right.svg +1 -0
  32. package/dist/icons/icons/arrow-up-right.svg +1 -0
  33. package/dist/icons/icons/arrow-up.0c0c0d.svg +1 -0
  34. package/dist/icons/icons/arrow-up.svg +1 -0
  35. package/dist/icons/icons/at.svg +1 -0
  36. package/dist/icons/icons/atom.0c0c0d.svg +1 -0
  37. package/dist/icons/icons/atom.svg +1 -0
  38. package/dist/icons/icons/bar-chart-2.svg +1 -0
  39. package/dist/icons/icons/bar.svg +1 -0
  40. package/dist/icons/icons/bell.svg +1 -0
  41. package/dist/icons/icons/box.svg +1 -0
  42. package/dist/icons/icons/bug.svg +1 -0
  43. package/dist/icons/icons/button.svg +1 -0
  44. package/dist/icons/icons/calendar.svg +1 -0
  45. package/dist/icons/icons/check-circle.33c192.svg +1 -0
  46. package/dist/icons/icons/check-circle.svg +1 -0
  47. package/dist/icons/icons/check-square.svg +4 -0
  48. package/dist/icons/icons/check.d1d3db.svg +1 -0
  49. package/dist/icons/icons/check.svg +1 -0
  50. package/dist/icons/icons/chevron-down.svg +1 -0
  51. package/dist/icons/icons/chevron-left.svg +1 -0
  52. package/dist/icons/icons/chevron-right.svg +1 -0
  53. package/dist/icons/icons/chevron-up.svg +1 -0
  54. package/dist/icons/icons/circle.svg +1 -0
  55. package/dist/icons/icons/clock.svg +1 -0
  56. package/dist/icons/icons/cmd.svg +1 -0
  57. package/dist/icons/icons/code.svg +1 -0
  58. package/dist/icons/icons/columns.svg +1 -0
  59. package/dist/icons/icons/copy.svg +1 -0
  60. package/dist/icons/icons/cpu.svg +1 -0
  61. package/dist/icons/icons/dashboard.0c0c0d.svg +1 -0
  62. package/dist/icons/icons/dashboard.svg +1 -0
  63. package/dist/icons/icons/dollar.svg +1 -0
  64. package/dist/icons/icons/download.svg +1 -0
  65. package/dist/icons/icons/edit.svg +1 -0
  66. package/dist/icons/icons/extensions.svg +1 -0
  67. package/dist/icons/icons/external.svg +1 -0
  68. package/dist/icons/icons/eye.svg +1 -0
  69. package/dist/icons/icons/file-text.svg +1 -0
  70. package/dist/icons/icons/file.svg +1 -0
  71. package/dist/icons/icons/files.svg +1 -0
  72. package/dist/icons/icons/filter.svg +1 -0
  73. package/dist/icons/icons/folder-open.svg +4 -0
  74. package/dist/icons/icons/folder.svg +1 -0
  75. package/dist/icons/icons/gear.svg +1 -0
  76. package/dist/icons/icons/git.svg +1 -0
  77. package/dist/icons/icons/github.svg +1 -0
  78. package/dist/icons/icons/globe.svg +1 -0
  79. package/dist/icons/icons/grid-2x2.svg +1 -0
  80. package/dist/icons/icons/hash.svg +1 -0
  81. package/dist/icons/icons/heart.svg +1 -0
  82. package/dist/icons/icons/help-circle.svg +5 -0
  83. package/dist/icons/icons/help.svg +1 -0
  84. package/dist/icons/icons/home.svg +1 -0
  85. package/dist/icons/icons/icons.svg +153 -0
  86. package/dist/icons/icons/image.svg +1 -0
  87. package/dist/icons/icons/info-circle.svg +1 -0
  88. package/dist/icons/icons/info-square.svg +1 -0
  89. package/dist/icons/icons/info.387bff.svg +1 -0
  90. package/dist/icons/icons/info.svg +1 -0
  91. package/dist/icons/icons/input.svg +1 -0
  92. package/dist/icons/icons/key.svg +1 -0
  93. package/dist/icons/icons/layers.svg +1 -0
  94. package/dist/icons/icons/layout-grid.svg +6 -0
  95. package/dist/icons/icons/layout.svg +1 -0
  96. package/dist/icons/icons/link.svg +1 -0
  97. package/dist/icons/icons/list.svg +1 -0
  98. package/dist/icons/icons/lock.svg +1 -0
  99. package/dist/icons/icons/log-out.svg +1 -0
  100. package/dist/icons/icons/logo.svg +1 -0
  101. package/dist/icons/icons/mail.svg +1 -0
  102. package/dist/icons/icons/menu.svg +1 -0
  103. package/dist/icons/icons/message-circle.svg +1 -0
  104. package/dist/icons/icons/message-plus.svg +1 -0
  105. package/dist/icons/icons/message-square.svg +1 -0
  106. package/dist/icons/icons/mic.svg +1 -0
  107. package/dist/icons/icons/minus.svg +1 -0
  108. package/dist/icons/icons/moon.svg +1 -0
  109. package/dist/icons/icons/more-h.svg +1 -0
  110. package/dist/icons/icons/mouse.svg +1 -0
  111. package/dist/icons/icons/palette.svg +1 -0
  112. package/dist/icons/icons/panel-bottom.svg +1 -0
  113. package/dist/icons/icons/pause.svg +1 -0
  114. package/dist/icons/icons/play.svg +1 -0
  115. package/dist/icons/icons/plug.svg +1 -0
  116. package/dist/icons/icons/plus.0c0c0d.svg +1 -0
  117. package/dist/icons/icons/plus.svg +1 -0
  118. package/dist/icons/icons/refresh.svg +1 -0
  119. package/dist/icons/icons/ruler.svg +1 -0
  120. package/dist/icons/icons/scroll-text.svg +1 -0
  121. package/dist/icons/icons/search-menu.svg +1 -0
  122. package/dist/icons/icons/search.svg +1 -0
  123. package/dist/icons/icons/send.0c0c0d.svg +1 -0
  124. package/dist/icons/icons/send.svg +1 -0
  125. package/dist/icons/icons/settings.0c0c0d.svg +1 -0
  126. package/dist/icons/icons/settings.svg +1 -0
  127. package/dist/icons/icons/shield.svg +1 -0
  128. package/dist/icons/icons/sidebar-left.svg +1 -0
  129. package/dist/icons/icons/sidebar-right.svg +1 -0
  130. package/dist/icons/icons/sliders.svg +1 -0
  131. package/dist/icons/icons/sparkles.32f08c.svg +1 -0
  132. package/dist/icons/icons/sparkles.svg +1 -0
  133. package/dist/icons/icons/square.svg +1 -0
  134. package/dist/icons/icons/star.svg +1 -0
  135. package/dist/icons/icons/sun.svg +1 -0
  136. package/dist/icons/icons/table.svg +1 -0
  137. package/dist/icons/icons/terminal.svg +1 -0
  138. package/dist/icons/icons/trash.svg +1 -0
  139. package/dist/icons/icons/trending-down.svg +1 -0
  140. package/dist/icons/icons/trending-up.svg +1 -0
  141. package/dist/icons/icons/type.svg +1 -0
  142. package/dist/icons/icons/upload.svg +1 -0
  143. package/dist/icons/icons/user-circle.svg +1 -0
  144. package/dist/icons/icons/user-plus.svg +6 -0
  145. package/dist/icons/icons/user.svg +1 -0
  146. package/dist/icons/icons/users.svg +1 -0
  147. package/dist/icons/icons/warning.svg +1 -0
  148. package/dist/icons/icons/wrench.svg +1 -0
  149. package/dist/icons/icons/x-circle.svg +1 -0
  150. package/dist/icons/icons/x.svg +1 -0
  151. package/dist/icons/icons/zap.0c0c0d.svg +1 -0
  152. package/dist/icons/icons/zap.svg +1 -0
  153. package/dist/kupola.cjs.js +258 -0
  154. package/dist/kupola.cjs.js.map +1 -0
  155. package/dist/kupola.esm.js +7640 -0
  156. package/dist/kupola.esm.js.map +1 -0
  157. package/dist/kupola.umd.js +258 -0
  158. package/dist/kupola.umd.js.map +1 -0
  159. package/dist/types/kupola.d.ts +1359 -0
  160. package/dist/utils/utils/Kupola.cs +77 -0
  161. package/dist/utils/utils/Kupola.java +104 -0
  162. package/dist/utils/utils/kupola.go +120 -0
  163. package/dist/utils/utils/kupola.js +63 -0
  164. package/dist/utils/utils/kupola.py +1392 -0
  165. package/dist/utils/utils/kupola.rb +69 -0
  166. package/icons/activity.svg +3 -0
  167. package/icons/alert-circle.svg +1 -0
  168. package/icons/alert-triangle.svg +1 -0
  169. package/icons/alert.d27e24.svg +1 -0
  170. package/icons/alert.svg +1 -0
  171. package/icons/apple.svg +1 -0
  172. package/icons/arrow-down.svg +1 -0
  173. package/icons/arrow-expand.svg +1 -0
  174. package/icons/arrow-left.svg +1 -0
  175. package/icons/arrow-minimize.svg +1 -0
  176. package/icons/arrow-right-to-line.svg +1 -0
  177. package/icons/arrow-right.svg +1 -0
  178. package/icons/arrow-up-right.svg +1 -0
  179. package/icons/arrow-up.0c0c0d.svg +1 -0
  180. package/icons/arrow-up.svg +1 -0
  181. package/icons/at.svg +1 -0
  182. package/icons/atom.0c0c0d.svg +1 -0
  183. package/icons/atom.svg +1 -0
  184. package/icons/bar-chart-2.svg +1 -0
  185. package/icons/bar.svg +1 -0
  186. package/icons/bell.svg +1 -0
  187. package/icons/box.svg +1 -0
  188. package/icons/bug.svg +1 -0
  189. package/icons/button.svg +1 -0
  190. package/icons/calendar.svg +1 -0
  191. package/icons/check-circle.33c192.svg +1 -0
  192. package/icons/check-circle.svg +1 -0
  193. package/icons/check-square.svg +4 -0
  194. package/icons/check.d1d3db.svg +1 -0
  195. package/icons/check.svg +1 -0
  196. package/icons/chevron-down.svg +1 -0
  197. package/icons/chevron-left.svg +1 -0
  198. package/icons/chevron-right.svg +1 -0
  199. package/icons/chevron-up.svg +1 -0
  200. package/icons/circle.svg +1 -0
  201. package/icons/clock.svg +1 -0
  202. package/icons/cmd.svg +1 -0
  203. package/icons/code.svg +1 -0
  204. package/icons/columns.svg +1 -0
  205. package/icons/copy.svg +1 -0
  206. package/icons/cpu.svg +1 -0
  207. package/icons/dashboard.0c0c0d.svg +1 -0
  208. package/icons/dashboard.svg +1 -0
  209. package/icons/dollar.svg +1 -0
  210. package/icons/download.svg +1 -0
  211. package/icons/edit.svg +1 -0
  212. package/icons/extensions.svg +1 -0
  213. package/icons/external.svg +1 -0
  214. package/icons/eye.svg +1 -0
  215. package/icons/file-text.svg +1 -0
  216. package/icons/file.svg +1 -0
  217. package/icons/files.svg +1 -0
  218. package/icons/filter.svg +1 -0
  219. package/icons/folder-open.svg +4 -0
  220. package/icons/folder.svg +1 -0
  221. package/icons/gear.svg +1 -0
  222. package/icons/git.svg +1 -0
  223. package/icons/github.svg +1 -0
  224. package/icons/globe.svg +1 -0
  225. package/icons/grid-2x2.svg +1 -0
  226. package/icons/hash.svg +1 -0
  227. package/icons/heart.svg +1 -0
  228. package/icons/help-circle.svg +5 -0
  229. package/icons/help.svg +1 -0
  230. package/icons/home.svg +1 -0
  231. package/icons/icons.svg +153 -0
  232. package/icons/image.svg +1 -0
  233. package/icons/info-circle.svg +1 -0
  234. package/icons/info-square.svg +1 -0
  235. package/icons/info.387bff.svg +1 -0
  236. package/icons/info.svg +1 -0
  237. package/icons/input.svg +1 -0
  238. package/icons/key.svg +1 -0
  239. package/icons/layers.svg +1 -0
  240. package/icons/layout-grid.svg +6 -0
  241. package/icons/layout.svg +1 -0
  242. package/icons/link.svg +1 -0
  243. package/icons/list.svg +1 -0
  244. package/icons/lock.svg +1 -0
  245. package/icons/log-out.svg +1 -0
  246. package/icons/logo.svg +1 -0
  247. package/icons/mail.svg +1 -0
  248. package/icons/menu.svg +1 -0
  249. package/icons/message-circle.svg +1 -0
  250. package/icons/message-plus.svg +1 -0
  251. package/icons/message-square.svg +1 -0
  252. package/icons/mic.svg +1 -0
  253. package/icons/minus.svg +1 -0
  254. package/icons/moon.svg +1 -0
  255. package/icons/more-h.svg +1 -0
  256. package/icons/mouse.svg +1 -0
  257. package/icons/palette.svg +1 -0
  258. package/icons/panel-bottom.svg +1 -0
  259. package/icons/pause.svg +1 -0
  260. package/icons/play.svg +1 -0
  261. package/icons/plug.svg +1 -0
  262. package/icons/plus.0c0c0d.svg +1 -0
  263. package/icons/plus.svg +1 -0
  264. package/icons/refresh.svg +1 -0
  265. package/icons/ruler.svg +1 -0
  266. package/icons/scroll-text.svg +1 -0
  267. package/icons/search-menu.svg +1 -0
  268. package/icons/search.svg +1 -0
  269. package/icons/send.0c0c0d.svg +1 -0
  270. package/icons/send.svg +1 -0
  271. package/icons/settings.0c0c0d.svg +1 -0
  272. package/icons/settings.svg +1 -0
  273. package/icons/shield.svg +1 -0
  274. package/icons/sidebar-left.svg +1 -0
  275. package/icons/sidebar-right.svg +1 -0
  276. package/icons/sliders.svg +1 -0
  277. package/icons/sparkles.32f08c.svg +1 -0
  278. package/icons/sparkles.svg +1 -0
  279. package/icons/square.svg +1 -0
  280. package/icons/star.svg +1 -0
  281. package/icons/sun.svg +1 -0
  282. package/icons/table.svg +1 -0
  283. package/icons/terminal.svg +1 -0
  284. package/icons/trash.svg +1 -0
  285. package/icons/trending-down.svg +1 -0
  286. package/icons/trending-up.svg +1 -0
  287. package/icons/type.svg +1 -0
  288. package/icons/upload.svg +1 -0
  289. package/icons/user-circle.svg +1 -0
  290. package/icons/user-plus.svg +6 -0
  291. package/icons/user.svg +1 -0
  292. package/icons/users.svg +1 -0
  293. package/icons/warning.svg +1 -0
  294. package/icons/wrench.svg +1 -0
  295. package/icons/x-circle.svg +1 -0
  296. package/icons/x.svg +1 -0
  297. package/icons/zap.0c0c0d.svg +1 -0
  298. package/icons/zap.svg +1 -0
  299. package/js/calendar.js +184 -0
  300. package/js/carousel.js +173 -0
  301. package/js/collapse.js +127 -0
  302. package/js/color-picker.js +144 -0
  303. package/js/component.js +276 -0
  304. package/js/composition-api.js +458 -0
  305. package/js/countdown.js +144 -0
  306. package/js/data-bind.js +1286 -0
  307. package/js/datepicker.js +270 -0
  308. package/js/dialog.js +131 -0
  309. package/js/drawer.js +118 -0
  310. package/js/dropdown.js +122 -0
  311. package/js/dynamic-tags.js +156 -0
  312. package/js/error-handler.js +181 -0
  313. package/js/fileupload.js +392 -0
  314. package/js/form.js +291 -0
  315. package/js/global-events.js +203 -0
  316. package/js/heatmap.js +405 -0
  317. package/js/http.js +419 -0
  318. package/js/i18n.js +228 -0
  319. package/js/icons.js +165 -0
  320. package/js/image-preview.js +218 -0
  321. package/js/initializer.js +162 -0
  322. package/js/kupola-core.js +66 -0
  323. package/js/kupola-devtools.js +598 -0
  324. package/js/kupola-lifecycle.js +510 -0
  325. package/js/message.js +60 -0
  326. package/js/modal.js +191 -0
  327. package/js/notification.js +73 -0
  328. package/js/numberinput.js +146 -0
  329. package/js/performance.js +250 -0
  330. package/js/registry.js +205 -0
  331. package/js/router.js +396 -0
  332. package/js/security.js +189 -0
  333. package/js/select.js +143 -0
  334. package/js/slide-captcha.js +504 -0
  335. package/js/slider.js +94 -0
  336. package/js/statcard.js +219 -0
  337. package/js/tag.js +83 -0
  338. package/js/test-utils.js +251 -0
  339. package/js/theme.js +363 -0
  340. package/js/timepicker.js +238 -0
  341. package/js/tooltip.js +122 -0
  342. package/js/utils.js +1458 -0
  343. package/js/validation.js +553 -0
  344. package/js/virtual-list.js +593 -0
  345. package/package.json +105 -0
  346. package/templates/base.html +30 -0
  347. package/templates/base_dashboard.html +99 -0
  348. package/types/kupola.d.ts +1359 -0
  349. package/utils/Kupola.cs +77 -0
  350. package/utils/Kupola.java +104 -0
  351. package/utils/kupola.go +120 -0
  352. package/utils/kupola.js +63 -0
  353. package/utils/kupola.py +1392 -0
  354. package/utils/kupola.rb +69 -0
package/js/utils.js ADDED
@@ -0,0 +1,1458 @@
1
+ const KupolaUtils = {
2
+ string: {
3
+ trim(str) {
4
+ return str ? str.trim() : '';
5
+ },
6
+ trimLeft(str) {
7
+ return str ? str.replace(/^\s+/, '') : '';
8
+ },
9
+ trimRight(str) {
10
+ return str ? str.replace(/\s+$/, '') : '';
11
+ },
12
+ toUpperCase(str) {
13
+ return str ? str.toUpperCase() : '';
14
+ },
15
+ toLowerCase(str) {
16
+ return str ? str.toLowerCase() : '';
17
+ },
18
+ capitalize(str) {
19
+ if (!str) return '';
20
+ return str.charAt(0).toUpperCase() + str.slice(1);
21
+ },
22
+ camelize(str) {
23
+ if (!str) return '';
24
+ return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '');
25
+ },
26
+ hyphenate(str) {
27
+ if (!str) return '';
28
+ return str.replace(/([A-Z])/g, '-$1').toLowerCase().replace(/^-/, '');
29
+ },
30
+ padStart(str, length, padChar = ' ') {
31
+ return (String(str) || '').padStart(length, padChar);
32
+ },
33
+ padEnd(str, length, padChar = ' ') {
34
+ return (String(str) || '').padEnd(length, padChar);
35
+ },
36
+ truncate(str, maxLength, suffix = '...') {
37
+ if (!str || str.length <= maxLength) return str || '';
38
+ return str.slice(0, maxLength) + suffix;
39
+ },
40
+ replaceAll(str, search, replacement) {
41
+ if (!str) return '';
42
+ return str.split(search).join(replacement);
43
+ },
44
+ format(template, data) {
45
+ if (!template) return '';
46
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => data[key] !== undefined ? data[key] : `{{${key}}}`);
47
+ },
48
+ startsWith(str, prefix) {
49
+ return (str || '').startsWith(prefix);
50
+ },
51
+ endsWith(str, suffix) {
52
+ return (str || '').endsWith(suffix);
53
+ },
54
+ includes(str, search) {
55
+ return (str || '').includes(search);
56
+ },
57
+ repeat(str, times) {
58
+ return (str || '').repeat(times);
59
+ },
60
+ reverse(str) {
61
+ return (str || '').split('').reverse().join('');
62
+ },
63
+ countOccurrences(str, search) {
64
+ if (!str || !search) return 0;
65
+ return str.split(search).length - 1;
66
+ },
67
+ escapeHtml(str) {
68
+ if (!str) return '';
69
+ const div = document.createElement('div');
70
+ div.textContent = str;
71
+ return div.innerHTML;
72
+ },
73
+ unescapeHtml(str) {
74
+ if (!str) return '';
75
+ const div = document.createElement('div');
76
+ div.innerHTML = str;
77
+ return div.textContent;
78
+ },
79
+ generateRandom(length = 8) {
80
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
81
+ let result = '';
82
+ for (let i = 0; i < length; i++) {
83
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
84
+ }
85
+ return result;
86
+ },
87
+ generateUUID() {
88
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
89
+ const r = Math.random() * 16 | 0;
90
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
91
+ return v.toString(16);
92
+ });
93
+ }
94
+ },
95
+
96
+ array: {
97
+ isArray(arr) {
98
+ return Array.isArray(arr);
99
+ },
100
+ isEmpty(arr) {
101
+ return !arr || arr.length === 0;
102
+ },
103
+ size(arr) {
104
+ return arr ? arr.length : 0;
105
+ },
106
+ first(arr, defaultValue) {
107
+ return arr && arr.length > 0 ? arr[0] : defaultValue;
108
+ },
109
+ last(arr, defaultValue) {
110
+ return arr && arr.length > 0 ? arr[arr.length - 1] : defaultValue;
111
+ },
112
+ get(arr, index, defaultValue) {
113
+ return arr && arr[index] !== undefined ? arr[index] : defaultValue;
114
+ },
115
+ slice(arr, start, end) {
116
+ return arr ? arr.slice(start, end) : [];
117
+ },
118
+ concat(...args) {
119
+ return args.reduce((result, arg) => result.concat(arg || []), []);
120
+ },
121
+ join(arr, separator = ',') {
122
+ return arr ? arr.join(separator) : '';
123
+ },
124
+ indexOf(arr, item, fromIndex = 0) {
125
+ return arr ? arr.indexOf(item, fromIndex) : -1;
126
+ },
127
+ lastIndexOf(arr, item, fromIndex) {
128
+ return arr ? arr.lastIndexOf(item, fromIndex) : -1;
129
+ },
130
+ includes(arr, item) {
131
+ return arr ? arr.includes(item) : false;
132
+ },
133
+ push(arr, ...items) {
134
+ if (arr) arr.push(...items);
135
+ return arr;
136
+ },
137
+ pop(arr) {
138
+ return arr ? arr.pop() : undefined;
139
+ },
140
+ shift(arr) {
141
+ return arr ? arr.shift() : undefined;
142
+ },
143
+ unshift(arr, ...items) {
144
+ if (arr) arr.unshift(...items);
145
+ return arr;
146
+ },
147
+ remove(arr, item) {
148
+ if (!arr) return arr;
149
+ const index = arr.indexOf(item);
150
+ if (index > -1) arr.splice(index, 1);
151
+ return arr;
152
+ },
153
+ removeAt(arr, index) {
154
+ if (!arr || index < 0 || index >= arr.length) return arr;
155
+ arr.splice(index, 1);
156
+ return arr;
157
+ },
158
+ insert(arr, index, item) {
159
+ if (!arr) return arr;
160
+ arr.splice(index, 0, item);
161
+ return arr;
162
+ },
163
+ reverse(arr) {
164
+ return arr ? arr.slice().reverse() : [];
165
+ },
166
+ sort(arr, compareFn) {
167
+ return arr ? arr.slice().sort(compareFn) : [];
168
+ },
169
+ sortBy(arr, key, order = 'asc') {
170
+ if (!arr) return [];
171
+ return arr.slice().sort((a, b) => {
172
+ const valA = typeof a === 'object' ? a[key] : a;
173
+ const valB = typeof b === 'object' ? b[key] : b;
174
+ if (valA < valB) return order === 'asc' ? -1 : 1;
175
+ if (valA > valB) return order === 'asc' ? 1 : -1;
176
+ return 0;
177
+ });
178
+ },
179
+ filter(arr, predicate) {
180
+ return arr ? arr.filter(predicate) : [];
181
+ },
182
+ map(arr, fn) {
183
+ return arr ? arr.map(fn) : [];
184
+ },
185
+ reduce(arr, fn, initialValue) {
186
+ return arr ? arr.reduce(fn, initialValue) : initialValue;
187
+ },
188
+ forEach(arr, fn) {
189
+ if (arr) arr.forEach(fn);
190
+ },
191
+ every(arr, predicate) {
192
+ return arr ? arr.every(predicate) : true;
193
+ },
194
+ some(arr, predicate) {
195
+ return arr ? arr.some(predicate) : false;
196
+ },
197
+ find(arr, predicate) {
198
+ return arr ? arr.find(predicate) : undefined;
199
+ },
200
+ findIndex(arr, predicate) {
201
+ return arr ? arr.findIndex(predicate) : -1;
202
+ },
203
+ flat(arr, depth = 1) {
204
+ return arr ? arr.flat(depth) : [];
205
+ },
206
+ flattenDeep(arr) {
207
+ return arr ? arr.reduce((acc, item) =>
208
+ Array.isArray(item) ? acc.concat(this.flattenDeep(item)) : acc.concat(item), []) : [];
209
+ },
210
+ unique(arr) {
211
+ return arr ? [...new Set(arr)] : [];
212
+ },
213
+ uniqueBy(arr, key) {
214
+ if (!arr) return [];
215
+ const seen = new Set();
216
+ return arr.filter(item => {
217
+ const value = typeof item === 'object' ? item[key] : item;
218
+ if (seen.has(value)) return false;
219
+ seen.add(value);
220
+ return true;
221
+ });
222
+ },
223
+ chunk(arr, size) {
224
+ if (!arr || size <= 0) return [];
225
+ const chunks = [];
226
+ for (let i = 0; i < arr.length; i += size) {
227
+ chunks.push(arr.slice(i, i + size));
228
+ }
229
+ return chunks;
230
+ },
231
+ shuffle(arr) {
232
+ if (!arr) return [];
233
+ const result = arr.slice();
234
+ for (let i = result.length - 1; i > 0; i--) {
235
+ const j = Math.floor(Math.random() * (i + 1));
236
+ [result[i], result[j]] = [result[j], result[i]];
237
+ }
238
+ return result;
239
+ },
240
+ sum(arr) {
241
+ return arr ? arr.reduce((acc, val) => acc + (Number(val) || 0), 0) : 0;
242
+ },
243
+ average(arr) {
244
+ if (!arr || arr.length === 0) return 0;
245
+ return this.sum(arr) / arr.length;
246
+ },
247
+ max(arr) {
248
+ return arr && arr.length > 0 ? Math.max(...arr) : -Infinity;
249
+ },
250
+ min(arr) {
251
+ return arr && arr.length > 0 ? Math.min(...arr) : Infinity;
252
+ },
253
+ intersection(...args) {
254
+ if (args.length === 0) return [];
255
+ return args.reduce((result, arr) =>
256
+ result.filter(item => arr && arr.includes(item)));
257
+ },
258
+ union(...args) {
259
+ return [...new Set(args.flat().filter(Boolean))];
260
+ },
261
+ difference(arr1, arr2) {
262
+ return arr1 ? arr1.filter(item => !arr2 || !arr2.includes(item)) : [];
263
+ },
264
+ zip(...args) {
265
+ if (args.length === 0) return [];
266
+ const maxLength = Math.max(...args.map(arr => arr ? arr.length : 0));
267
+ return Array.from({ length: maxLength }, (_, i) =>
268
+ args.map(arr => arr && arr[i]));
269
+ }
270
+ },
271
+
272
+ object: {
273
+ isObject(obj) {
274
+ return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
275
+ },
276
+ isEmpty(obj) {
277
+ if (!obj || typeof obj !== 'object') return true;
278
+ return Object.keys(obj).length === 0;
279
+ },
280
+ keys(obj) {
281
+ return obj ? Object.keys(obj) : [];
282
+ },
283
+ values(obj) {
284
+ return obj ? Object.values(obj) : [];
285
+ },
286
+ entries(obj) {
287
+ return obj ? Object.entries(obj) : [];
288
+ },
289
+ has(obj, key) {
290
+ return obj ? Object.prototype.hasOwnProperty.call(obj, key) : false;
291
+ },
292
+ get(obj, path, defaultValue) {
293
+ if (!obj) return defaultValue;
294
+ const keys = path.split('.');
295
+ return keys.reduce((current, key) => current && current[key], obj) || defaultValue;
296
+ },
297
+ set(obj, path, value) {
298
+ if (!obj || typeof obj !== 'object') return obj;
299
+ const keys = path.split('.');
300
+ const lastKey = keys.pop();
301
+ let current = obj;
302
+ keys.forEach(key => {
303
+ if (!current[key]) current[key] = {};
304
+ current = current[key];
305
+ });
306
+ current[lastKey] = value;
307
+ return obj;
308
+ },
309
+ pick(obj, keys) {
310
+ if (!obj) return {};
311
+ return keys.reduce((result, key) => {
312
+ if (obj[key] !== undefined) result[key] = obj[key];
313
+ return result;
314
+ }, {});
315
+ },
316
+ omit(obj, keys) {
317
+ if (!obj) return {};
318
+ return Object.keys(obj).reduce((result, key) => {
319
+ if (!keys.includes(key)) result[key] = obj[key];
320
+ return result;
321
+ }, {});
322
+ },
323
+ merge(...args) {
324
+ return args.reduce((result, obj) => {
325
+ if (obj && typeof obj === 'object') {
326
+ Object.keys(obj).forEach(key => {
327
+ if (this.isObject(obj[key]) && this.isObject(result[key])) {
328
+ result[key] = this.merge(result[key], obj[key]);
329
+ } else {
330
+ result[key] = obj[key];
331
+ }
332
+ });
333
+ }
334
+ return result;
335
+ }, {});
336
+ },
337
+ clone(obj) {
338
+ return obj ? JSON.parse(JSON.stringify(obj)) : obj;
339
+ },
340
+ deepClone(obj, hash = new WeakMap()) {
341
+ if (!obj || typeof obj !== 'object') return obj;
342
+ if (hash.has(obj)) return hash.get(obj);
343
+ if (obj instanceof Date) return new Date(obj);
344
+ if (obj instanceof RegExp) return new RegExp(obj);
345
+ if (obj instanceof Map) {
346
+ const map = new Map();
347
+ hash.set(obj, map);
348
+ obj.forEach((val, key) => map.set(key, this.deepClone(val, hash)));
349
+ return map;
350
+ }
351
+ if (obj instanceof Set) {
352
+ const set = new Set();
353
+ hash.set(obj, set);
354
+ obj.forEach(val => set.add(this.deepClone(val, hash)));
355
+ return set;
356
+ }
357
+ if (Array.isArray(obj)) {
358
+ const arr = [];
359
+ hash.set(obj, arr);
360
+ obj.forEach(item => arr.push(this.deepClone(item, hash)));
361
+ return arr;
362
+ }
363
+ const clone = {};
364
+ hash.set(obj, clone);
365
+ Object.keys(obj).forEach(key => {
366
+ clone[key] = this.deepClone(obj[key], hash);
367
+ });
368
+ return clone;
369
+ },
370
+ forEach(obj, fn) {
371
+ if (!obj) return;
372
+ Object.keys(obj).forEach(key => fn(obj[key], key, obj));
373
+ },
374
+ map(obj, fn) {
375
+ if (!obj) return {};
376
+ const result = {};
377
+ Object.keys(obj).forEach(key => {
378
+ result[key] = fn(obj[key], key, obj);
379
+ });
380
+ return result;
381
+ },
382
+ filter(obj, fn) {
383
+ if (!obj) return {};
384
+ const result = {};
385
+ Object.keys(obj).forEach(key => {
386
+ if (fn(obj[key], key, obj)) result[key] = obj[key];
387
+ });
388
+ return result;
389
+ },
390
+ reduce(obj, fn, initialValue) {
391
+ if (!obj) return initialValue;
392
+ return Object.keys(obj).reduce((acc, key) =>
393
+ fn(acc, obj[key], key, obj), initialValue);
394
+ },
395
+ toArray(obj) {
396
+ if (!obj) return [];
397
+ return Object.keys(obj).map(key => ({ key, value: obj[key] }));
398
+ },
399
+ fromArray(arr, keyField, valueField) {
400
+ if (!arr) return {};
401
+ return arr.reduce((result, item) => {
402
+ const key = typeof item === 'object' ? item[keyField] : item;
403
+ const value = valueField ? item[valueField] : item;
404
+ if (key !== undefined) result[key] = value;
405
+ return result;
406
+ }, {});
407
+ },
408
+ size(obj) {
409
+ return obj ? Object.keys(obj).length : 0;
410
+ },
411
+ invert(obj) {
412
+ if (!obj) return {};
413
+ const result = {};
414
+ Object.keys(obj).forEach(key => {
415
+ result[obj[key]] = key;
416
+ });
417
+ return result;
418
+ },
419
+ isEqual(obj1, obj2) {
420
+ if (obj1 === obj2) return true;
421
+ if (!obj1 || !obj2 || typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
422
+ const keys1 = Object.keys(obj1);
423
+ const keys2 = Object.keys(obj2);
424
+ if (keys1.length !== keys2.length) return false;
425
+ return keys1.every(key => this.isEqual(obj1[key], obj2[key]));
426
+ },
427
+ freeze(obj) {
428
+ if (!obj) return obj;
429
+ Object.freeze(obj);
430
+ Object.keys(obj).forEach(key => {
431
+ if (typeof obj[key] === 'object') this.freeze(obj[key]);
432
+ });
433
+ return obj;
434
+ },
435
+ seal(obj) {
436
+ return obj ? Object.seal(obj) : obj;
437
+ }
438
+ },
439
+
440
+ number: {
441
+ isNumber(val) {
442
+ return typeof val === 'number' && !isNaN(val);
443
+ },
444
+ isInteger(val) {
445
+ return Number.isInteger(val);
446
+ },
447
+ isFloat(val) {
448
+ return this.isNumber(val) && !Number.isInteger(val);
449
+ },
450
+ isPositive(val) {
451
+ return this.isNumber(val) && val > 0;
452
+ },
453
+ isNegative(val) {
454
+ return this.isNumber(val) && val < 0;
455
+ },
456
+ isZero(val) {
457
+ return this.isNumber(val) && val === 0;
458
+ },
459
+ clamp(val, min, max) {
460
+ if (!this.isNumber(val)) return val;
461
+ return Math.min(Math.max(val, min), max);
462
+ },
463
+ round(val, precision = 0) {
464
+ if (!this.isNumber(val)) return val;
465
+ const factor = Math.pow(10, precision);
466
+ return Math.round(val * factor) / factor;
467
+ },
468
+ floor(val) {
469
+ return this.isNumber(val) ? Math.floor(val) : val;
470
+ },
471
+ ceil(val) {
472
+ return this.isNumber(val) ? Math.ceil(val) : val;
473
+ },
474
+ abs(val) {
475
+ return this.isNumber(val) ? Math.abs(val) : val;
476
+ },
477
+ min(...args) {
478
+ const nums = args.filter(this.isNumber);
479
+ return nums.length > 0 ? Math.min(...nums) : undefined;
480
+ },
481
+ max(...args) {
482
+ const nums = args.filter(this.isNumber);
483
+ return nums.length > 0 ? Math.max(...nums) : undefined;
484
+ },
485
+ sum(...args) {
486
+ const nums = args.flat().filter(this.isNumber);
487
+ return nums.reduce((acc, val) => acc + val, 0);
488
+ },
489
+ average(...args) {
490
+ const nums = args.flat().filter(this.isNumber);
491
+ return nums.length > 0 ? this.sum(nums) / nums.length : 0;
492
+ },
493
+ random(min = 0, max = 1) {
494
+ return Math.random() * (max - min) + min;
495
+ },
496
+ randomInt(min, max) {
497
+ return Math.floor(this.random(min, max + 1));
498
+ },
499
+ format(val, decimals = 2) {
500
+ if (!this.isNumber(val)) return String(val);
501
+ return val.toFixed(decimals);
502
+ },
503
+ formatCurrency(val, currency = 'CNY', decimals = 2) {
504
+ if (!this.isNumber(val)) return String(val);
505
+ const formatter = new Intl.NumberFormat('zh-CN', {
506
+ style: 'currency',
507
+ currency,
508
+ minimumFractionDigits: decimals,
509
+ maximumFractionDigits: decimals
510
+ });
511
+ return formatter.format(val);
512
+ },
513
+ formatPercent(val, decimals = 0) {
514
+ if (!this.isNumber(val)) return String(val);
515
+ return `${(val * 100).toFixed(decimals)}%`;
516
+ },
517
+ toFixed(val, decimals = 0) {
518
+ if (!this.isNumber(val)) return String(val);
519
+ return val.toFixed(decimals);
520
+ },
521
+ toPrecision(val, precision = 6) {
522
+ if (!this.isNumber(val)) return String(val);
523
+ return val.toPrecision(precision);
524
+ },
525
+ isNaN(val) {
526
+ return Number.isNaN(val);
527
+ },
528
+ isFinite(val) {
529
+ return Number.isFinite(val);
530
+ },
531
+ parseInt(val, radix = 10) {
532
+ return Number.parseInt(val, radix);
533
+ },
534
+ parseFloat(val) {
535
+ return Number.parseFloat(val);
536
+ },
537
+ toNumber(val, defaultValue = 0) {
538
+ const num = Number(val);
539
+ return isNaN(num) ? defaultValue : num;
540
+ },
541
+ safeDivide(a, b, defaultValue = 0) {
542
+ if (!this.isNumber(a) || !this.isNumber(b) || b === 0) return defaultValue;
543
+ return a / b;
544
+ },
545
+ safeMultiply(...args) {
546
+ return args.reduce((result, val) => {
547
+ if (!this.isNumber(result) || !this.isNumber(val)) return 0;
548
+ return result * val;
549
+ }, 1);
550
+ }
551
+ },
552
+
553
+ date: {
554
+ now() {
555
+ return Date.now();
556
+ },
557
+ today() {
558
+ const now = new Date();
559
+ now.setHours(0, 0, 0, 0);
560
+ return now;
561
+ },
562
+ tomorrow() {
563
+ const date = this.today();
564
+ date.setDate(date.getDate() + 1);
565
+ return date;
566
+ },
567
+ yesterday() {
568
+ const date = this.today();
569
+ date.setDate(date.getDate() - 1);
570
+ return date;
571
+ },
572
+ isDate(val) {
573
+ return val instanceof Date && !isNaN(val.getTime());
574
+ },
575
+ isValid(date) {
576
+ return this.isDate(date);
577
+ },
578
+ parse(str) {
579
+ const date = new Date(str);
580
+ return this.isValid(date) ? date : null;
581
+ },
582
+ format(date, formatStr = 'YYYY-MM-DD HH:mm:ss') {
583
+ if (!this.isDate(date)) return '';
584
+ const year = date.getFullYear();
585
+ const month = String(date.getMonth() + 1).padStart(2, '0');
586
+ const day = String(date.getDate()).padStart(2, '0');
587
+ const hours = String(date.getHours()).padStart(2, '0');
588
+ const minutes = String(date.getMinutes()).padStart(2, '0');
589
+ const seconds = String(date.getSeconds()).padStart(2, '0');
590
+ const milliseconds = String(date.getMilliseconds()).padStart(3, '0');
591
+ const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
592
+ const weekDay = weekDays[date.getDay()];
593
+
594
+ return formatStr
595
+ .replace('YYYY', year)
596
+ .replace('MM', month)
597
+ .replace('DD', day)
598
+ .replace('HH', hours)
599
+ .replace('mm', minutes)
600
+ .replace('ss', seconds)
601
+ .replace('SSS', milliseconds)
602
+ .replace('D', date.getDate())
603
+ .replace('M', date.getMonth() + 1)
604
+ .replace('H', date.getHours())
605
+ .replace('m', date.getMinutes())
606
+ .replace('s', date.getSeconds())
607
+ .replace('W', weekDay);
608
+ },
609
+ toISO(date) {
610
+ return this.isDate(date) ? date.toISOString() : '';
611
+ },
612
+ toUTC(date) {
613
+ return this.isDate(date) ? new Date(date.toUTCString()) : null;
614
+ },
615
+ addDays(date, days) {
616
+ if (!this.isDate(date)) return date;
617
+ const newDate = new Date(date);
618
+ newDate.setDate(newDate.getDate() + days);
619
+ return newDate;
620
+ },
621
+ addHours(date, hours) {
622
+ if (!this.isDate(date)) return date;
623
+ const newDate = new Date(date);
624
+ newDate.setHours(newDate.getHours() + hours);
625
+ return newDate;
626
+ },
627
+ addMinutes(date, minutes) {
628
+ if (!this.isDate(date)) return date;
629
+ const newDate = new Date(date);
630
+ newDate.setMinutes(newDate.getMinutes() + minutes);
631
+ return newDate;
632
+ },
633
+ addSeconds(date, seconds) {
634
+ if (!this.isDate(date)) return date;
635
+ const newDate = new Date(date);
636
+ newDate.setSeconds(newDate.getSeconds() + seconds);
637
+ return newDate;
638
+ },
639
+ diffDays(date1, date2) {
640
+ if (!this.isDate(date1) || !this.isDate(date2)) return 0;
641
+ const d1 = this.today();
642
+ const d2 = this.today();
643
+ d1.setTime(date1.getTime());
644
+ d2.setTime(date2.getTime());
645
+ return Math.floor((d1.getTime() - d2.getTime()) / (1000 * 60 * 60 * 24));
646
+ },
647
+ diffHours(date1, date2) {
648
+ if (!this.isDate(date1) || !this.isDate(date2)) return 0;
649
+ return Math.floor((date1.getTime() - date2.getTime()) / (1000 * 60 * 60));
650
+ },
651
+ diffMinutes(date1, date2) {
652
+ if (!this.isDate(date1) || !this.isDate(date2)) return 0;
653
+ return Math.floor((date1.getTime() - date2.getTime()) / (1000 * 60));
654
+ },
655
+ diffSeconds(date1, date2) {
656
+ if (!this.isDate(date1) || !this.isDate(date2)) return 0;
657
+ return Math.floor((date1.getTime() - date2.getTime()) / 1000);
658
+ },
659
+ isToday(date) {
660
+ if (!this.isDate(date)) return false;
661
+ return this.diffDays(date, this.today()) === 0;
662
+ },
663
+ isYesterday(date) {
664
+ if (!this.isDate(date)) return false;
665
+ return this.diffDays(date, this.today()) === -1;
666
+ },
667
+ isTomorrow(date) {
668
+ if (!this.isDate(date)) return false;
669
+ return this.diffDays(date, this.today()) === 1;
670
+ },
671
+ isFuture(date) {
672
+ if (!this.isDate(date)) return false;
673
+ return date.getTime() > this.now();
674
+ },
675
+ isPast(date) {
676
+ if (!this.isDate(date)) return false;
677
+ return date.getTime() < this.now();
678
+ },
679
+ isLeapYear(date) {
680
+ if (!this.isDate(date)) return false;
681
+ const year = date.getFullYear();
682
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
683
+ },
684
+ getDaysInMonth(date) {
685
+ if (!this.isDate(date)) return 0;
686
+ const year = date.getFullYear();
687
+ const month = date.getMonth();
688
+ return new Date(year, month + 1, 0).getDate();
689
+ },
690
+ getWeekOfYear(date) {
691
+ if (!this.isDate(date)) return 0;
692
+ const startOfYear = new Date(date.getFullYear(), 0, 1);
693
+ const diff = date.getTime() - startOfYear.getTime();
694
+ return Math.ceil(diff / (1000 * 60 * 60 * 24 * 7));
695
+ },
696
+ getQuarter(date) {
697
+ if (!this.isDate(date)) return 0;
698
+ return Math.ceil((date.getMonth() + 1) / 3);
699
+ },
700
+ startOfDay(date) {
701
+ if (!this.isDate(date)) return date;
702
+ const newDate = new Date(date);
703
+ newDate.setHours(0, 0, 0, 0);
704
+ return newDate;
705
+ },
706
+ endOfDay(date) {
707
+ if (!this.isDate(date)) return date;
708
+ const newDate = new Date(date);
709
+ newDate.setHours(23, 59, 59, 999);
710
+ return newDate;
711
+ },
712
+ startOfMonth(date) {
713
+ if (!this.isDate(date)) return date;
714
+ return new Date(date.getFullYear(), date.getMonth(), 1);
715
+ },
716
+ endOfMonth(date) {
717
+ if (!this.isDate(date)) return date;
718
+ return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59, 999);
719
+ },
720
+ startOfWeek(date, startDay = 1) {
721
+ if (!this.isDate(date)) return date;
722
+ const newDate = new Date(date);
723
+ const day = newDate.getDay();
724
+ const diff = day >= startDay ? day - startDay : day + (7 - startDay);
725
+ newDate.setDate(newDate.getDate() - diff);
726
+ newDate.setHours(0, 0, 0, 0);
727
+ return newDate;
728
+ },
729
+ endOfWeek(date, startDay = 1) {
730
+ if (!this.isDate(date)) return date;
731
+ const start = this.startOfWeek(date, startDay);
732
+ const end = new Date(start);
733
+ end.setDate(end.getDate() + 6);
734
+ end.setHours(23, 59, 59, 999);
735
+ return end;
736
+ },
737
+ getAge(birthDate) {
738
+ if (!this.isDate(birthDate)) return 0;
739
+ const now = new Date();
740
+ let age = now.getFullYear() - birthDate.getFullYear();
741
+ if (now.getMonth() < birthDate.getMonth() ||
742
+ (now.getMonth() === birthDate.getMonth() && now.getDate() < birthDate.getDate())) {
743
+ age--;
744
+ }
745
+ return Math.max(0, age);
746
+ },
747
+ fromNow(date) {
748
+ if (!this.isDate(date)) return '';
749
+ const now = this.now();
750
+ const diff = now - date.getTime();
751
+ const minute = 60 * 1000;
752
+ const hour = 60 * minute;
753
+ const day = 24 * hour;
754
+ const week = 7 * day;
755
+ const month = 30 * day;
756
+ const year = 365 * day;
757
+
758
+ if (diff < minute) return '刚刚';
759
+ if (diff < hour) return `${Math.floor(diff / minute)}分钟前`;
760
+ if (diff < day) return `${Math.floor(diff / hour)}小时前`;
761
+ if (diff < week) return `${Math.floor(diff / day)}天前`;
762
+ if (diff < month) return `${Math.floor(diff / week)}周前`;
763
+ if (diff < year) return `${Math.floor(diff / month)}个月前`;
764
+ return `${Math.floor(diff / year)}年前`;
765
+ }
766
+ },
767
+
768
+ debounce(func, wait, options = {}) {
769
+ let timeout = null;
770
+ let lastArgs = null;
771
+ let lastThis = null;
772
+ let lastCallTime = 0;
773
+
774
+ const leading = options.leading || false;
775
+ const trailing = options.trailing !== false;
776
+
777
+ function invokeFunc() {
778
+ func.apply(lastThis, lastArgs);
779
+ }
780
+
781
+ function leadingEdge() {
782
+ lastCallTime = Date.now();
783
+ if (leading) {
784
+ timeout = setTimeout(trailingEdge, wait);
785
+ invokeFunc();
786
+ } else {
787
+ timeout = setTimeout(trailingEdge, wait);
788
+ }
789
+ }
790
+
791
+ function trailingEdge() {
792
+ timeout = null;
793
+ if (trailing && lastArgs) {
794
+ invokeFunc();
795
+ }
796
+ lastArgs = null;
797
+ lastThis = null;
798
+ }
799
+
800
+ function remainingWait() {
801
+ const timeSinceLastCall = Date.now() - lastCallTime;
802
+ return Math.max(0, wait - timeSinceLastCall);
803
+ }
804
+
805
+ return function(...args) {
806
+ lastArgs = args;
807
+ lastThis = this;
808
+ lastCallTime = Date.now();
809
+
810
+ if (!timeout) {
811
+ leadingEdge();
812
+ } else {
813
+ clearTimeout(timeout);
814
+ timeout = setTimeout(trailingEdge, remainingWait());
815
+ }
816
+ };
817
+ },
818
+
819
+ throttle(func, limit, options = {}) {
820
+ let inThrottle = false;
821
+ const trailing = options.trailing || false;
822
+ let lastArgs = null;
823
+ let lastThis = null;
824
+
825
+ function invokeFunc() {
826
+ func.apply(lastThis, lastArgs);
827
+ lastArgs = null;
828
+ lastThis = null;
829
+ }
830
+
831
+ return function(...args) {
832
+ if (!inThrottle) {
833
+ inThrottle = true;
834
+ lastArgs = args;
835
+ lastThis = this;
836
+ invokeFunc();
837
+ setTimeout(() => {
838
+ inThrottle = false;
839
+ if (trailing && lastArgs) {
840
+ invokeFunc();
841
+ }
842
+ }, limit);
843
+ } else if (trailing) {
844
+ lastArgs = args;
845
+ lastThis = this;
846
+ }
847
+ };
848
+ },
849
+
850
+ validator: {
851
+ isEmail(str) {
852
+ const regex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
853
+ return regex.test(str || '');
854
+ },
855
+ isPhone(str) {
856
+ const regex = /^1[3-9]\d{9}$/;
857
+ return regex.test(str || '');
858
+ },
859
+ isURL(str) {
860
+ const regex = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w.-]*)*\/?$/;
861
+ return regex.test(str || '');
862
+ },
863
+ isIPv4(str) {
864
+ const regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
865
+ return regex.test(str || '');
866
+ },
867
+ isIPv6(str) {
868
+ const regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
869
+ return regex.test(str || '');
870
+ },
871
+ isIP(str) {
872
+ return this.isIPv4(str) || this.isIPv6(str);
873
+ },
874
+ isIDCard(str) {
875
+ const regex = /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/;
876
+ return regex.test(str || '');
877
+ },
878
+ isPassport(str) {
879
+ const regex = /^[A-Z][0-9]{8}$|^[A-Z]{2}[0-9]{7}$/;
880
+ return regex.test(str || '');
881
+ },
882
+ isCreditCard(str) {
883
+ const regex = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9]{2})[0-9]{12}|3[47][0-9]{13})$/;
884
+ const cleanStr = str.replace(/\s/g, '');
885
+ if (!regex.test(cleanStr)) return false;
886
+ let sum = 0;
887
+ let alternate = false;
888
+ for (let i = cleanStr.length - 1; i >= 0; i--) {
889
+ let digit = parseInt(cleanStr[i], 10);
890
+ if (alternate) {
891
+ digit *= 2;
892
+ if (digit > 9) digit -= 9;
893
+ }
894
+ sum += digit;
895
+ alternate = !alternate;
896
+ }
897
+ return sum % 10 === 0;
898
+ },
899
+ isHexColor(str) {
900
+ const regex = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
901
+ return regex.test(str || '');
902
+ },
903
+ isRGB(str) {
904
+ const regex = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/;
905
+ const match = regex.exec(str || '');
906
+ if (!match) return false;
907
+ return match.slice(1).every(val => parseInt(val) >= 0 && parseInt(val) <= 255);
908
+ },
909
+ isRGBA(str) {
910
+ const regex = /^rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*([01]|0\.\d+)\)$/;
911
+ const match = regex.exec(str || '');
912
+ if (!match) return false;
913
+ const [, r, g, b, a] = match;
914
+ return parseInt(r) >= 0 && parseInt(r) <= 255 &&
915
+ parseInt(g) >= 0 && parseInt(g) <= 255 &&
916
+ parseInt(b) >= 0 && parseInt(b) <= 255 &&
917
+ parseFloat(a) >= 0 && parseFloat(a) <= 1;
918
+ },
919
+ isColor(str) {
920
+ return this.isHexColor(str) || this.isRGB(str) || this.isRGBA(str);
921
+ },
922
+ isDate(str) {
923
+ return !isNaN(new Date(str).getTime());
924
+ },
925
+ isJSON(str) {
926
+ try {
927
+ JSON.parse(str);
928
+ return true;
929
+ } catch {
930
+ return false;
931
+ }
932
+ },
933
+ isEmpty(str) {
934
+ return !str || str.trim() === '';
935
+ },
936
+ isWhitespace(str) {
937
+ return /^\s+$/.test(str || '');
938
+ },
939
+ isNumber(str) {
940
+ return !isNaN(parseFloat(str)) && isFinite(str);
941
+ },
942
+ isInteger(str) {
943
+ return /^-?\d+$/.test(str || '');
944
+ },
945
+ isFloat(str) {
946
+ return /^-?\d+\.\d+$/.test(str || '');
947
+ },
948
+ isPositive(str) {
949
+ const num = parseFloat(str);
950
+ return !isNaN(num) && num > 0;
951
+ },
952
+ isNegative(str) {
953
+ const num = parseFloat(str);
954
+ return !isNaN(num) && num < 0;
955
+ },
956
+ isAlpha(str) {
957
+ return /^[a-zA-Z]+$/.test(str || '');
958
+ },
959
+ isAlphaNumeric(str) {
960
+ return /^[a-zA-Z0-9]+$/.test(str || '');
961
+ },
962
+ isChinese(str) {
963
+ return /^[\u4e00-\u9fa5]+$/.test(str || '');
964
+ },
965
+ isLength(str, min, max) {
966
+ const len = (str || '').length;
967
+ return len >= min && (max === undefined || len <= max);
968
+ },
969
+ minLength(str, min) {
970
+ return (str || '').length >= min;
971
+ },
972
+ maxLength(str, max) {
973
+ return (str || '').length <= max;
974
+ },
975
+ matches(str, regex) {
976
+ return (regex instanceof RegExp) ? regex.test(str || '') : false;
977
+ },
978
+ equals(str1, str2) {
979
+ return String(str1) === String(str2);
980
+ },
981
+ contains(str, substring) {
982
+ return (str || '').includes(substring);
983
+ },
984
+ notContains(str, substring) {
985
+ return !this.contains(str, substring);
986
+ },
987
+ isArray(arr) {
988
+ return Array.isArray(arr);
989
+ },
990
+ arrayLength(arr, min, max) {
991
+ const len = arr ? arr.length : 0;
992
+ return len >= min && (max === undefined || len <= max);
993
+ },
994
+ arrayMinLength(arr, min) {
995
+ return arr ? arr.length >= min : false;
996
+ },
997
+ arrayMaxLength(arr, max) {
998
+ return arr ? arr.length <= max : false;
999
+ },
1000
+ isObject(obj) {
1001
+ return obj !== null && typeof obj === 'object' && !Array.isArray(obj);
1002
+ },
1003
+ hasKeys(obj, keys) {
1004
+ if (!obj || !keys || !Array.isArray(keys)) return false;
1005
+ return keys.every(key => Object.prototype.hasOwnProperty.call(obj, key));
1006
+ },
1007
+ validate(obj, rules) {
1008
+ const errors = {};
1009
+ Object.keys(rules).forEach(key => {
1010
+ const value = obj[key];
1011
+ const fieldRules = rules[key];
1012
+ const fieldErrors = [];
1013
+
1014
+ fieldRules.forEach(rule => {
1015
+ if (typeof rule === 'string') {
1016
+ const [name, ...params] = rule.split(':');
1017
+ if (!this[name](value, ...params)) {
1018
+ fieldErrors.push(name);
1019
+ }
1020
+ } else if (typeof rule === 'function') {
1021
+ const result = rule(value, obj);
1022
+ if (result !== true) {
1023
+ fieldErrors.push(result || 'validation_failed');
1024
+ }
1025
+ }
1026
+ });
1027
+
1028
+ if (fieldErrors.length > 0) {
1029
+ errors[key] = fieldErrors;
1030
+ }
1031
+ });
1032
+
1033
+ return {
1034
+ valid: Object.keys(errors).length === 0,
1035
+ errors
1036
+ };
1037
+ }
1038
+ },
1039
+
1040
+ crypto: {
1041
+ md5(str) {
1042
+ const message = str ? String(str) : '';
1043
+ const md5Array = [
1044
+ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476
1045
+ ];
1046
+
1047
+ const K = [
1048
+ 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee,
1049
+ 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501,
1050
+ 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
1051
+ 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821,
1052
+ 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa,
1053
+ 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
1054
+ 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed,
1055
+ 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a,
1056
+ 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
1057
+ 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70,
1058
+ 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05,
1059
+ 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
1060
+ 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039,
1061
+ 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1,
1062
+ 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
1063
+ 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
1064
+ ];
1065
+
1066
+ const S = [
1067
+ [7, 12, 17, 22], [5, 9, 14, 20], [4, 11, 16, 23], [6, 10, 15, 21]
1068
+ ];
1069
+
1070
+ function leftRotate(value, shift) {
1071
+ return (value << shift) | (value >>> (32 - shift));
1072
+ }
1073
+
1074
+ function addPadding(message) {
1075
+ const bitLength = message.length * 8;
1076
+ message += '\x80';
1077
+ while ((message.length % 64) !== 56) {
1078
+ message += '\x00';
1079
+ }
1080
+ const lowBytes = bitLength & 0xffffffff;
1081
+ const highBytes = (bitLength >>> 32) & 0xffffffff;
1082
+ for (let i = 0; i < 4; i++) {
1083
+ message += String.fromCharCode((lowBytes >>> (8 * i)) & 0xff);
1084
+ }
1085
+ for (let i = 0; i < 4; i++) {
1086
+ message += String.fromCharCode((highBytes >>> (8 * i)) & 0xff);
1087
+ }
1088
+ return message;
1089
+ }
1090
+
1091
+ function processBlock(block, state) {
1092
+ const [a, b, c, d] = state;
1093
+ const words = [];
1094
+ for (let i = 0; i < 16; i++) {
1095
+ words[i] = (block.charCodeAt(i * 4) & 0xff) |
1096
+ ((block.charCodeAt(i * 4 + 1) & 0xff) << 8) |
1097
+ ((block.charCodeAt(i * 4 + 2) & 0xff) << 16) |
1098
+ ((block.charCodeAt(i * 4 + 3) & 0xff) << 24);
1099
+ }
1100
+
1101
+ let AA = a, BB = b, CC = c, DD = d;
1102
+
1103
+ for (let i = 0; i < 64; i++) {
1104
+ let f, g;
1105
+ const round = Math.floor(i / 16);
1106
+ const wordIndex = i % 16;
1107
+
1108
+ if (round === 0) {
1109
+ f = (BB & CC) | (~BB & DD);
1110
+ g = wordIndex;
1111
+ } else if (round === 1) {
1112
+ f = (DD & BB) | (~DD & CC);
1113
+ g = (5 * wordIndex + 1) % 16;
1114
+ } else if (round === 2) {
1115
+ f = BB ^ CC ^ DD;
1116
+ g = (3 * wordIndex + 5) % 16;
1117
+ } else {
1118
+ f = CC ^ (BB | ~DD);
1119
+ g = (7 * wordIndex) % 16;
1120
+ }
1121
+
1122
+ const temp = DD;
1123
+ DD = CC;
1124
+ CC = BB;
1125
+ BB = BB + leftRotate((AA + f + K[i] + words[g]) & 0xffffffff, S[round][i % 4]);
1126
+ AA = temp;
1127
+ }
1128
+
1129
+ return [
1130
+ (a + AA) & 0xffffffff,
1131
+ (b + BB) & 0xffffffff,
1132
+ (c + CC) & 0xffffffff,
1133
+ (d + DD) & 0xffffffff
1134
+ ];
1135
+ }
1136
+
1137
+ const paddedMessage = addPadding(message);
1138
+ let state = [...md5Array];
1139
+
1140
+ for (let i = 0; i < paddedMessage.length; i += 64) {
1141
+ state = processBlock(paddedMessage.substring(i, i + 64), state);
1142
+ }
1143
+
1144
+ let result = '';
1145
+ state.forEach(word => {
1146
+ for (let i = 0; i < 4; i++) {
1147
+ result += ((word >>> (8 * i)) & 0xff).toString(16).padStart(2, '0');
1148
+ }
1149
+ });
1150
+
1151
+ return result;
1152
+ },
1153
+
1154
+ sha256(str) {
1155
+ const message = str ? String(str) : '';
1156
+ const K = [
1157
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
1158
+ 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
1159
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
1160
+ 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
1161
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
1162
+ 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
1163
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
1164
+ 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
1165
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
1166
+ 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
1167
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
1168
+ 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
1169
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
1170
+ 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
1171
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
1172
+ 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
1173
+ ];
1174
+
1175
+ function rightRotate(value, shift) {
1176
+ return (value >>> shift) | (value << (32 - shift));
1177
+ }
1178
+
1179
+ function addPadding(message) {
1180
+ const bitLength = message.length * 8;
1181
+ message += '\x80';
1182
+ while ((message.length % 64) !== 56) {
1183
+ message += '\x00';
1184
+ }
1185
+ const lowBytes = bitLength & 0xffffffff;
1186
+ const highBytes = (bitLength >>> 32) & 0xffffffff;
1187
+ for (let i = 0; i < 4; i++) {
1188
+ message += String.fromCharCode((highBytes >>> (8 * i)) & 0xff);
1189
+ }
1190
+ for (let i = 0; i < 4; i++) {
1191
+ message += String.fromCharCode((lowBytes >>> (8 * i)) & 0xff);
1192
+ }
1193
+ return message;
1194
+ }
1195
+
1196
+ function processBlock(block, state) {
1197
+ const words = [];
1198
+ for (let i = 0; i < 16; i++) {
1199
+ words[i] = (block.charCodeAt(i * 4) & 0xff) |
1200
+ ((block.charCodeAt(i * 4 + 1) & 0xff) << 8) |
1201
+ ((block.charCodeAt(i * 4 + 2) & 0xff) << 16) |
1202
+ ((block.charCodeAt(i * 4 + 3) & 0xff) << 24);
1203
+ }
1204
+
1205
+ for (let i = 16; i < 64; i++) {
1206
+ const s0 = rightRotate(words[i - 15], 7) ^ rightRotate(words[i - 15], 18) ^ (words[i - 15] >>> 3);
1207
+ const s1 = rightRotate(words[i - 2], 17) ^ rightRotate(words[i - 2], 19) ^ (words[i - 2] >>> 10);
1208
+ words[i] = (words[i - 16] + s0 + words[i - 7] + s1) & 0xffffffff;
1209
+ }
1210
+
1211
+ let [a, b, c, d, e, f, g, h] = state;
1212
+
1213
+ for (let i = 0; i < 64; i++) {
1214
+ const S1 = rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25);
1215
+ const ch = (e & f) ^ (~e & g);
1216
+ const temp1 = (h + S1 + ch + K[i] + words[i]) & 0xffffffff;
1217
+ const S0 = rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22);
1218
+ const maj = (a & b) ^ (a & c) ^ (b & c);
1219
+ const temp2 = (S0 + maj) & 0xffffffff;
1220
+
1221
+ h = g;
1222
+ g = f;
1223
+ f = e;
1224
+ e = (d + temp1) & 0xffffffff;
1225
+ d = c;
1226
+ c = b;
1227
+ b = a;
1228
+ a = (temp1 + temp2) & 0xffffffff;
1229
+ }
1230
+
1231
+ return [
1232
+ (state[0] + a) & 0xffffffff,
1233
+ (state[1] + b) & 0xffffffff,
1234
+ (state[2] + c) & 0xffffffff,
1235
+ (state[3] + d) & 0xffffffff,
1236
+ (state[4] + e) & 0xffffffff,
1237
+ (state[5] + f) & 0xffffffff,
1238
+ (state[6] + g) & 0xffffffff,
1239
+ (state[7] + h) & 0xffffffff
1240
+ ];
1241
+ }
1242
+
1243
+ const paddedMessage = addPadding(message);
1244
+ let state = [
1245
+ 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
1246
+ 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
1247
+ ];
1248
+
1249
+ for (let i = 0; i < paddedMessage.length; i += 64) {
1250
+ state = processBlock(paddedMessage.substring(i, i + 64), state);
1251
+ }
1252
+
1253
+ let result = '';
1254
+ state.forEach(word => {
1255
+ for (let i = 3; i >= 0; i--) {
1256
+ result += ((word >>> (8 * i)) & 0xff).toString(16).padStart(2, '0');
1257
+ }
1258
+ });
1259
+
1260
+ return result;
1261
+ },
1262
+
1263
+ base64Encode(str) {
1264
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
1265
+ let result = '';
1266
+ let i = 0;
1267
+ const bytes = str ? str.split('').map(c => c.charCodeAt(0)) : [];
1268
+
1269
+ while (i < bytes.length) {
1270
+ const byte1 = bytes[i++];
1271
+ const byte2 = bytes[i++] || 0;
1272
+ const byte3 = bytes[i++] || 0;
1273
+
1274
+ const enc1 = byte1 >> 2;
1275
+ const enc2 = ((byte1 & 3) << 4) | (byte2 >> 4);
1276
+ const enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
1277
+ const enc4 = byte3 & 63;
1278
+
1279
+ result += chars[enc1] + chars[enc2] +
1280
+ (i > bytes.length + 1 ? '=' : chars[enc3]) +
1281
+ (i > bytes.length ? '=' : chars[enc4]);
1282
+ }
1283
+
1284
+ return result;
1285
+ },
1286
+
1287
+ base64Decode(str) {
1288
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
1289
+ let result = '';
1290
+ let i = 0;
1291
+ str = str.replace(/[^A-Za-z0-9+/=]/g, '');
1292
+
1293
+ while (i < str.length) {
1294
+ const enc1 = chars.indexOf(str.charAt(i++));
1295
+ const enc2 = chars.indexOf(str.charAt(i++));
1296
+ const enc3 = chars.indexOf(str.charAt(i++));
1297
+ const enc4 = chars.indexOf(str.charAt(i++));
1298
+
1299
+ const byte1 = (enc1 << 2) | (enc2 >> 4);
1300
+ const byte2 = ((enc2 & 15) << 4) | (enc3 >> 2);
1301
+ const byte3 = ((enc3 & 3) << 6) | enc4;
1302
+
1303
+ result += String.fromCharCode(byte1);
1304
+ if (enc3 !== 64) result += String.fromCharCode(byte2);
1305
+ if (enc4 !== 64) result += String.fromCharCode(byte3);
1306
+ }
1307
+
1308
+ return result;
1309
+ },
1310
+
1311
+ uuid() {
1312
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
1313
+ const r = Math.random() * 16 | 0;
1314
+ const v = c === 'x' ? r : (r & 0x3 | 0x8);
1315
+ return v.toString(16);
1316
+ });
1317
+ }
1318
+ },
1319
+
1320
+ preload: {
1321
+ cache: new Map(),
1322
+
1323
+ async loadImage(url, options = {}) {
1324
+ const { crossOrigin = 'anonymous' } = options;
1325
+
1326
+ if (this.cache.has(url)) {
1327
+ return this.cache.get(url);
1328
+ }
1329
+
1330
+ return new Promise((resolve, reject) => {
1331
+ const img = new Image();
1332
+ img.crossOrigin = crossOrigin;
1333
+
1334
+ img.onload = () => {
1335
+ this.cache.set(url, img);
1336
+ resolve(img);
1337
+ };
1338
+
1339
+ img.onerror = () => {
1340
+ reject(new Error(`Failed to load image: ${url}`));
1341
+ };
1342
+
1343
+ img.src = url;
1344
+ });
1345
+ },
1346
+
1347
+ async loadImages(urls, options = {}) {
1348
+ const { parallel = true } = options;
1349
+
1350
+ if (parallel) {
1351
+ return Promise.all(urls.map(url => this.loadImage(url, options)));
1352
+ }
1353
+
1354
+ const results = [];
1355
+ for (const url of urls) {
1356
+ results.push(await this.loadImage(url, options));
1357
+ }
1358
+ return results;
1359
+ },
1360
+
1361
+ async loadScript(url, options = {}) {
1362
+ const { type = 'text/javascript', async = true, defer = false } = options;
1363
+
1364
+ if (this.cache.has(url)) {
1365
+ return this.cache.get(url);
1366
+ }
1367
+
1368
+ return new Promise((resolve, reject) => {
1369
+ const script = document.createElement('script');
1370
+ script.type = type;
1371
+ script.async = async;
1372
+ script.defer = defer;
1373
+
1374
+ script.onload = () => {
1375
+ this.cache.set(url, script);
1376
+ resolve(script);
1377
+ };
1378
+
1379
+ script.onerror = () => {
1380
+ script.remove();
1381
+ reject(new Error(`Failed to load script: ${url}`));
1382
+ };
1383
+
1384
+ script.src = url;
1385
+ document.head.appendChild(script);
1386
+ });
1387
+ },
1388
+
1389
+ async loadStylesheet(url, options = {}) {
1390
+ const { media = 'all' } = options;
1391
+
1392
+ if (this.cache.has(url)) {
1393
+ return this.cache.get(url);
1394
+ }
1395
+
1396
+ return new Promise((resolve, reject) => {
1397
+ const link = document.createElement('link');
1398
+ link.rel = 'stylesheet';
1399
+ link.href = url;
1400
+ link.media = media;
1401
+
1402
+ link.onload = () => {
1403
+ this.cache.set(url, link);
1404
+ resolve(link);
1405
+ };
1406
+
1407
+ link.onerror = () => {
1408
+ link.remove();
1409
+ reject(new Error(`Failed to load stylesheet: ${url}`));
1410
+ };
1411
+
1412
+ document.head.appendChild(link);
1413
+ });
1414
+ },
1415
+
1416
+ async loadFont(fontFamily, src, options = {}) {
1417
+ const { weight = 'normal', style = 'normal' } = options;
1418
+ const fontFace = new FontFace(fontFamily, `url(${src})`, { weight, style });
1419
+
1420
+ try {
1421
+ await fontFace.load();
1422
+ document.fonts.add(fontFace);
1423
+ return fontFace;
1424
+ } catch (e) {
1425
+ throw new Error(`Failed to load font: ${fontFamily}`);
1426
+ }
1427
+ },
1428
+
1429
+ async preload(url, type = 'image') {
1430
+ switch (type) {
1431
+ case 'image':
1432
+ return this.loadImage(url);
1433
+ case 'script':
1434
+ return this.loadScript(url);
1435
+ case 'stylesheet':
1436
+ case 'style':
1437
+ return this.loadStylesheet(url);
1438
+ default:
1439
+ throw new Error(`Unsupported preload type: ${type}`);
1440
+ }
1441
+ },
1442
+
1443
+ isLoaded(url) {
1444
+ return this.cache.has(url);
1445
+ },
1446
+
1447
+ clearCache() {
1448
+ this.cache.clear();
1449
+ },
1450
+
1451
+ clearCacheByUrl(url) {
1452
+ this.cache.delete(url);
1453
+ }
1454
+ }
1455
+ };
1456
+
1457
+ window.KupolaUtils = KupolaUtils;
1458
+ window.kupolaUtils = KupolaUtils;