@dodlhuat/basix 1.0.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 (430) hide show
  1. package/README.md +482 -0
  2. package/css/accordion.css +109 -0
  3. package/css/accordion.css.map +1 -0
  4. package/css/accordion.scss +78 -0
  5. package/css/alert.css +57 -0
  6. package/css/alert.css.map +1 -0
  7. package/css/alert.scss +86 -0
  8. package/css/button.css +69 -0
  9. package/css/button.css.map +1 -0
  10. package/css/button.scss +102 -0
  11. package/css/card.css +144 -0
  12. package/css/card.css.map +1 -0
  13. package/css/card.scss +66 -0
  14. package/css/carousel.css +118 -0
  15. package/css/carousel.css.map +1 -0
  16. package/css/carousel.scss +87 -0
  17. package/css/chart.css +159 -0
  18. package/css/chart.css.map +1 -0
  19. package/css/chart.scss +159 -0
  20. package/css/chat-bubbles.css +97 -0
  21. package/css/chat-bubbles.css.map +1 -0
  22. package/css/chat-bubbles.scss +68 -0
  23. package/css/checkbox.css +77 -0
  24. package/css/checkbox.css.map +1 -0
  25. package/css/checkbox.scss +55 -0
  26. package/css/chips.css +72 -0
  27. package/css/chips.css.map +1 -0
  28. package/css/chips.scss +52 -0
  29. package/css/code-viewer.css +97 -0
  30. package/css/code-viewer.css.map +1 -0
  31. package/css/code-viewer.scss +98 -0
  32. package/css/colors.css +63 -0
  33. package/css/colors.css.map +1 -0
  34. package/css/colors.scss +33 -0
  35. package/css/datepicker.css +264 -0
  36. package/css/datepicker.css.map +1 -0
  37. package/css/datepicker.scss +317 -0
  38. package/css/defaults.css +118 -0
  39. package/css/defaults.css.map +1 -0
  40. package/css/defaults.scss +91 -0
  41. package/css/dropdown.css +146 -0
  42. package/css/dropdown.css.map +1 -0
  43. package/css/dropdown.scss +137 -0
  44. package/css/editor.css +413 -0
  45. package/css/editor.scss +458 -0
  46. package/css/file-uploader.css +194 -0
  47. package/css/file-uploader.css.map +1 -0
  48. package/css/file-uploader.scss +195 -0
  49. package/css/flyout-menu.css +345 -0
  50. package/css/flyout-menu.css.map +1 -0
  51. package/css/flyout-menu.scss +355 -0
  52. package/css/form-builder.css +9 -0
  53. package/css/form-builder.css.map +1 -0
  54. package/css/form-builder.scss +11 -0
  55. package/css/form.css +130 -0
  56. package/css/form.css.map +1 -0
  57. package/css/form.scss +115 -0
  58. package/css/gallery.css +91 -0
  59. package/css/gallery.css.map +1 -0
  60. package/css/gallery.scss +63 -0
  61. package/css/grid.css +44 -0
  62. package/css/grid.css.map +1 -0
  63. package/css/grid.scss +41 -0
  64. package/css/guitar-chords.css +251 -0
  65. package/css/icons.css +327 -0
  66. package/css/icons.css.map +1 -0
  67. package/css/icons.scss +331 -0
  68. package/css/modal.css +97 -0
  69. package/css/modal.css.map +1 -0
  70. package/css/modal.scss +72 -0
  71. package/css/parameters.css +1 -0
  72. package/css/parameters.css.map +1 -0
  73. package/css/parameters.scss +4 -0
  74. package/css/placeholder.css +50 -0
  75. package/css/placeholder.css.map +1 -0
  76. package/css/placeholder.scss +28 -0
  77. package/css/progress.css +51 -0
  78. package/css/progress.css.map +1 -0
  79. package/css/progress.scss +32 -0
  80. package/css/properties.css +31 -0
  81. package/css/properties.css.map +1 -0
  82. package/css/properties.scss +31 -0
  83. package/css/push-menu.css +145 -0
  84. package/css/push-menu.css.map +1 -0
  85. package/css/push-menu.scss +127 -0
  86. package/css/radiobutton.css +91 -0
  87. package/css/radiobutton.css.map +1 -0
  88. package/css/radiobutton.scss +64 -0
  89. package/css/reset.css +46 -0
  90. package/css/reset.css.map +1 -0
  91. package/css/reset.scss +40 -0
  92. package/css/scrollbar.css +91 -0
  93. package/css/scrollbar.css.map +1 -0
  94. package/css/scrollbar.scss +69 -0
  95. package/css/spinner.css +118 -0
  96. package/css/spinner.css.map +1 -0
  97. package/css/spinner.scss +91 -0
  98. package/css/style.css +3735 -0
  99. package/css/style.css.map +1 -0
  100. package/css/style.min.css +1 -0
  101. package/css/style.scss +38 -0
  102. package/css/switch.css +66 -0
  103. package/css/switch.css.map +1 -0
  104. package/css/switch.scss +42 -0
  105. package/css/table.css +201 -0
  106. package/css/table.css.map +1 -0
  107. package/css/table.scss +178 -0
  108. package/css/tabs.css +135 -0
  109. package/css/tabs.css.map +1 -0
  110. package/css/tabs.scss +118 -0
  111. package/css/timeline.css +69 -0
  112. package/css/timeline.css.map +1 -0
  113. package/css/timeline.scss +69 -0
  114. package/css/timepicker.scss +72 -0
  115. package/css/toast.css +98 -0
  116. package/css/toast.css.map +1 -0
  117. package/css/toast.scss +81 -0
  118. package/css/tooltip.css +151 -0
  119. package/css/tooltip.css.map +1 -0
  120. package/css/tooltip.scss +122 -0
  121. package/css/tree.css +199 -0
  122. package/css/tree.css.map +1 -0
  123. package/css/tree.scss +192 -0
  124. package/css/typography.css +137 -0
  125. package/css/typography.css.map +1 -0
  126. package/css/typography.scss +100 -0
  127. package/css/virtual-dropdown.css +149 -0
  128. package/css/virtual-dropdown.css.map +1 -0
  129. package/css/virtual-dropdown.scss +142 -0
  130. package/fonts/MaterialSymbolsOutlined.woff2 +0 -0
  131. package/fonts/Outfit-VariableFont_wght.woff +0 -0
  132. package/fonts/Outfit-VariableFont_wght.woff2 +0 -0
  133. package/fonts/material-icons.woff2 +0 -0
  134. package/icons/activity-outline.svg +1 -0
  135. package/icons/alert-circle-outline.svg +1 -0
  136. package/icons/alert-triangle-outline.svg +1 -0
  137. package/icons/archive-outline.svg +1 -0
  138. package/icons/arrow-back-outline.svg +1 -0
  139. package/icons/arrow-circle-down-outline.svg +1 -0
  140. package/icons/arrow-circle-left-outline.svg +1 -0
  141. package/icons/arrow-circle-right-outline.svg +1 -0
  142. package/icons/arrow-circle-up-outline.svg +1 -0
  143. package/icons/arrow-down-outline.svg +1 -0
  144. package/icons/arrow-downward-outline.svg +1 -0
  145. package/icons/arrow-forward-outline.svg +1 -0
  146. package/icons/arrow-ios-back-outline.svg +1 -0
  147. package/icons/arrow-ios-downward-outline.svg +1 -0
  148. package/icons/arrow-ios-forward-outline.svg +1 -0
  149. package/icons/arrow-ios-upward-outline.svg +1 -0
  150. package/icons/arrow-left-outline.svg +1 -0
  151. package/icons/arrow-right-outline.svg +1 -0
  152. package/icons/arrow-up-outline.svg +1 -0
  153. package/icons/arrow-upward-outline.svg +1 -0
  154. package/icons/arrowhead-down-outline.svg +1 -0
  155. package/icons/arrowhead-left-outline.svg +1 -0
  156. package/icons/arrowhead-right-outline.svg +1 -0
  157. package/icons/arrowhead-up-outline.svg +1 -0
  158. package/icons/at-outline.svg +1 -0
  159. package/icons/attach-2-outline.svg +1 -0
  160. package/icons/attach-outline.svg +1 -0
  161. package/icons/award-outline.svg +1 -0
  162. package/icons/backspace-outline.svg +1 -0
  163. package/icons/bar-chart-2-outline.svg +1 -0
  164. package/icons/bar-chart-outline.svg +1 -0
  165. package/icons/battery-outline.svg +1 -0
  166. package/icons/behance-outline.svg +1 -0
  167. package/icons/bell-off-outline.svg +1 -0
  168. package/icons/bell-outline.svg +1 -0
  169. package/icons/bluetooth-outline.svg +1 -0
  170. package/icons/book-open-outline.svg +1 -0
  171. package/icons/book-outline.svg +1 -0
  172. package/icons/bookmark-outline.svg +1 -0
  173. package/icons/briefcase-outline.svg +1 -0
  174. package/icons/browser-outline.svg +1 -0
  175. package/icons/brush-outline.svg +1 -0
  176. package/icons/bulb-outline.svg +1 -0
  177. package/icons/calendar-outline.svg +1 -0
  178. package/icons/camera-outline.svg +1 -0
  179. package/icons/car-outline.svg +1 -0
  180. package/icons/cast-outline.svg +1 -0
  181. package/icons/charging-outline.svg +1 -0
  182. package/icons/checkmark-circle-2-outline.svg +1 -0
  183. package/icons/checkmark-circle-outline.svg +1 -0
  184. package/icons/checkmark-outline.svg +1 -0
  185. package/icons/checkmark-square-2-outline.svg +1 -0
  186. package/icons/checkmark-square-outline.svg +1 -0
  187. package/icons/chevron-down-outline.svg +1 -0
  188. package/icons/chevron-left-outline.svg +1 -0
  189. package/icons/chevron-right-outline.svg +1 -0
  190. package/icons/chevron-up-outline.svg +1 -0
  191. package/icons/clipboard-outline.svg +1 -0
  192. package/icons/clock-outline.svg +1 -0
  193. package/icons/close-circle-outline.svg +1 -0
  194. package/icons/close-outline.svg +1 -0
  195. package/icons/close-square-outline.svg +1 -0
  196. package/icons/cloud-download-outline.svg +1 -0
  197. package/icons/cloud-upload-outline.svg +1 -0
  198. package/icons/code-download-outline.svg +1 -0
  199. package/icons/code-outline.svg +1 -0
  200. package/icons/collapse-outline.svg +1 -0
  201. package/icons/color-palette-outline.svg +1 -0
  202. package/icons/color-picker-outline.svg +1 -0
  203. package/icons/compass-outline.svg +1 -0
  204. package/icons/copy-outline.svg +1 -0
  205. package/icons/corner-down-left-outline.svg +1 -0
  206. package/icons/corner-down-right-outline.svg +1 -0
  207. package/icons/corner-left-down-outline.svg +1 -0
  208. package/icons/corner-left-up-outline.svg +1 -0
  209. package/icons/corner-right-down-outline.svg +1 -0
  210. package/icons/corner-right-up-outline.svg +1 -0
  211. package/icons/corner-up-left-outline.svg +1 -0
  212. package/icons/corner-up-right-outline.svg +1 -0
  213. package/icons/credit-card-outline.svg +1 -0
  214. package/icons/crop-outline.svg +1 -0
  215. package/icons/cube-outline.svg +1 -0
  216. package/icons/diagonal-arrow-left-down-outline.svg +1 -0
  217. package/icons/diagonal-arrow-left-up-outline.svg +1 -0
  218. package/icons/diagonal-arrow-right-down-outline.svg +1 -0
  219. package/icons/diagonal-arrow-right-up-outline.svg +1 -0
  220. package/icons/done-all-outline.svg +1 -0
  221. package/icons/download-outline.svg +1 -0
  222. package/icons/droplet-off-outline.svg +1 -0
  223. package/icons/droplet-outline.svg +1 -0
  224. package/icons/edit-2-outline.svg +1 -0
  225. package/icons/edit-outline.svg +1 -0
  226. package/icons/email-outline.svg +1 -0
  227. package/icons/expand-outline.svg +1 -0
  228. package/icons/external-link-outline.svg +1 -0
  229. package/icons/eye-off-2-outline.svg +1 -0
  230. package/icons/eye-off-outline.svg +1 -0
  231. package/icons/eye-outline.svg +1 -0
  232. package/icons/facebook-outline.svg +1 -0
  233. package/icons/file-add-outline.svg +1 -0
  234. package/icons/file-outline.svg +1 -0
  235. package/icons/file-remove-outline.svg +1 -0
  236. package/icons/file-text-outline.svg +1 -0
  237. package/icons/film-outline.svg +1 -0
  238. package/icons/flag-outline.svg +1 -0
  239. package/icons/flash-off-outline.svg +1 -0
  240. package/icons/flash-outline.svg +1 -0
  241. package/icons/flip-2-outline.svg +1 -0
  242. package/icons/flip-outline.svg +1 -0
  243. package/icons/folder-add-outline.svg +1 -0
  244. package/icons/folder-outline.svg +1 -0
  245. package/icons/folder-remove-outline.svg +1 -0
  246. package/icons/funnel-outline.svg +1 -0
  247. package/icons/gift-outline.svg +1 -0
  248. package/icons/github-outline.svg +1 -0
  249. package/icons/globe-2-outline.svg +1 -0
  250. package/icons/globe-outline.svg +1 -0
  251. package/icons/google-outline.svg +1 -0
  252. package/icons/grid-outline.svg +1 -0
  253. package/icons/hard-drive-outline.svg +1 -0
  254. package/icons/hash-outline.svg +1 -0
  255. package/icons/headphones-outline.svg +1 -0
  256. package/icons/heart-outline.svg +1 -0
  257. package/icons/home-outline.svg +1 -0
  258. package/icons/image-outline.svg +1 -0
  259. package/icons/inbox-outline.svg +1 -0
  260. package/icons/info-outline.svg +1 -0
  261. package/icons/keypad-outline.svg +1 -0
  262. package/icons/layers-outline.svg +1 -0
  263. package/icons/layout-outline.svg +1 -0
  264. package/icons/link-2-outline.svg +1 -0
  265. package/icons/link-outline.svg +1 -0
  266. package/icons/linkedin-outline.svg +1 -0
  267. package/icons/list-outline.svg +1 -0
  268. package/icons/loader-outline.svg +1 -0
  269. package/icons/lock-outline.svg +1 -0
  270. package/icons/log-in-outline.svg +1 -0
  271. package/icons/log-out-outline.svg +1 -0
  272. package/icons/map-outline.svg +1 -0
  273. package/icons/maximize-outline.svg +1 -0
  274. package/icons/menu-2-outline.svg +1 -0
  275. package/icons/menu-arrow-outline.svg +1 -0
  276. package/icons/menu-outline.svg +1 -0
  277. package/icons/message-circle-outline.svg +1 -0
  278. package/icons/message-square-outline.svg +1 -0
  279. package/icons/mic-off-outline.svg +1 -0
  280. package/icons/mic-outline.svg +1 -0
  281. package/icons/minimize-outline.svg +1 -0
  282. package/icons/minus-circle-outline.svg +1 -0
  283. package/icons/minus-outline.svg +1 -0
  284. package/icons/minus-square-outline.svg +1 -0
  285. package/icons/monitor-outline.svg +1 -0
  286. package/icons/moon-outline.svg +1 -0
  287. package/icons/more-horizontal-outline.svg +1 -0
  288. package/icons/more-vertical-outline.svg +1 -0
  289. package/icons/move-outline.svg +1 -0
  290. package/icons/music-outline.svg +1 -0
  291. package/icons/navigation-2-outline.svg +1 -0
  292. package/icons/navigation-outline.svg +1 -0
  293. package/icons/npm-outline.svg +1 -0
  294. package/icons/options-2-outline.svg +1 -0
  295. package/icons/options-outline.svg +1 -0
  296. package/icons/pantone-outline.svg +1 -0
  297. package/icons/paper-plane-outline.svg +1 -0
  298. package/icons/pause-circle-outline.svg +1 -0
  299. package/icons/people-outline.svg +1 -0
  300. package/icons/percent-outline.svg +1 -0
  301. package/icons/person-add-outline.svg +1 -0
  302. package/icons/person-delete-outline.svg +1 -0
  303. package/icons/person-done-outline.svg +1 -0
  304. package/icons/person-outline.svg +1 -0
  305. package/icons/person-remove-outline.svg +1 -0
  306. package/icons/phone-call-outline.svg +1 -0
  307. package/icons/phone-missed-outline.svg +1 -0
  308. package/icons/phone-off-outline.svg +1 -0
  309. package/icons/phone-outline.svg +1 -0
  310. package/icons/pie-chart-outline.svg +1 -0
  311. package/icons/pin-outline.svg +1 -0
  312. package/icons/play-circle-outline.svg +1 -0
  313. package/icons/plus-circle-outline.svg +1 -0
  314. package/icons/plus-outline.svg +1 -0
  315. package/icons/plus-square-outline.svg +1 -0
  316. package/icons/power-outline.svg +1 -0
  317. package/icons/pricetags-outline.svg +1 -0
  318. package/icons/printer-outline.svg +1 -0
  319. package/icons/question-mark-circle-outline.svg +1 -0
  320. package/icons/question-mark-outline.svg +1 -0
  321. package/icons/radio-button-off-outline.svg +1 -0
  322. package/icons/radio-button-on-outline.svg +1 -0
  323. package/icons/radio-outline.svg +1 -0
  324. package/icons/recording-outline.svg +1 -0
  325. package/icons/refresh-outline.svg +1 -0
  326. package/icons/repeat-outline.svg +1 -0
  327. package/icons/rewind-left-outline.svg +1 -0
  328. package/icons/rewind-right-outline.svg +1 -0
  329. package/icons/save-outline.svg +1 -0
  330. package/icons/scissors-outline.svg +1 -0
  331. package/icons/search-outline.svg +1 -0
  332. package/icons/settings-2-outline.svg +1 -0
  333. package/icons/settings-outline.svg +1 -0
  334. package/icons/shake-outline.svg +1 -0
  335. package/icons/share-outline.svg +1 -0
  336. package/icons/shield-off-outline.svg +1 -0
  337. package/icons/shield-outline.svg +1 -0
  338. package/icons/shopping-bag-outline.svg +1 -0
  339. package/icons/shopping-cart-outline.svg +1 -0
  340. package/icons/shuffle-2-outline.svg +1 -0
  341. package/icons/shuffle-outline.svg +1 -0
  342. package/icons/skip-back-outline.svg +1 -0
  343. package/icons/skip-forward-outline.svg +1 -0
  344. package/icons/slash-outline.svg +1 -0
  345. package/icons/smartphone-outline.svg +1 -0
  346. package/icons/smiling-face-outline.svg +1 -0
  347. package/icons/speaker-outline.svg +1 -0
  348. package/icons/square-outline.svg +1 -0
  349. package/icons/star-outline.svg +1 -0
  350. package/icons/stop-circle-outline.svg +1 -0
  351. package/icons/sun-outline.svg +1 -0
  352. package/icons/swap-outline.svg +1 -0
  353. package/icons/sync-outline.svg +1 -0
  354. package/icons/text-outline.svg +1 -0
  355. package/icons/thermometer-minus-outline.svg +1 -0
  356. package/icons/thermometer-outline.svg +1 -0
  357. package/icons/thermometer-plus-outline.svg +1 -0
  358. package/icons/toggle-left-outline.svg +1 -0
  359. package/icons/toggle-right-outline.svg +1 -0
  360. package/icons/trash-2-outline.svg +1 -0
  361. package/icons/trash-outline.svg +1 -0
  362. package/icons/trending-down-outline.svg +1 -0
  363. package/icons/trending-up-outline.svg +1 -0
  364. package/icons/tv-outline.svg +1 -0
  365. package/icons/twitter-outline.svg +1 -0
  366. package/icons/umbrella-outline.svg +1 -0
  367. package/icons/undo-outline.svg +1 -0
  368. package/icons/unlock-outline.svg +1 -0
  369. package/icons/upload-outline.svg +1 -0
  370. package/icons/video-off-outline.svg +1 -0
  371. package/icons/video-outline.svg +1 -0
  372. package/icons/volume-down-outline.svg +1 -0
  373. package/icons/volume-mute-outline.svg +1 -0
  374. package/icons/volume-off-outline.svg +1 -0
  375. package/icons/volume-up-outline.svg +1 -0
  376. package/icons/wifi-off-outline.svg +1 -0
  377. package/icons/wifi-outline.svg +1 -0
  378. package/js/carousel.js +133 -0
  379. package/js/carousel.ts +173 -0
  380. package/js/chart.js +257 -0
  381. package/js/code-viewer.js +148 -0
  382. package/js/code-viewer.ts +188 -0
  383. package/js/datepicker.js +497 -0
  384. package/js/datepicker.ts +619 -0
  385. package/js/dropdown.js +122 -0
  386. package/js/dropdown.ts +180 -0
  387. package/js/editor.js +421 -0
  388. package/js/editor.ts +426 -0
  389. package/js/file-uploader.js +268 -0
  390. package/js/file-uploader.ts +350 -0
  391. package/js/flyout-menu.js +195 -0
  392. package/js/flyout-menu.ts +250 -0
  393. package/js/form-builder.js +107 -0
  394. package/js/gallery.js +177 -0
  395. package/js/gallery.ts +231 -0
  396. package/js/guitar-chords.js +268 -0
  397. package/js/index.js +720 -0
  398. package/js/index.ts +874 -0
  399. package/js/lazy-loader.js +121 -0
  400. package/js/modal.js +113 -0
  401. package/js/modal.ts +167 -0
  402. package/js/push-menu.js +101 -0
  403. package/js/push-menu.ts +130 -0
  404. package/js/request.js +51 -0
  405. package/js/scroll.js +27 -0
  406. package/js/scroll.ts +47 -0
  407. package/js/scrollbar.js +219 -0
  408. package/js/scrollbar.ts +308 -0
  409. package/js/select.js +158 -0
  410. package/js/select.ts +217 -0
  411. package/js/table.js +359 -0
  412. package/js/table.ts +453 -0
  413. package/js/tabs.js +225 -0
  414. package/js/tabs.ts +280 -0
  415. package/js/theme.js +194 -0
  416. package/js/theme.ts +225 -0
  417. package/js/timepicker.js +98 -0
  418. package/js/timepicker.ts +131 -0
  419. package/js/toast.js +93 -0
  420. package/js/toast.ts +138 -0
  421. package/js/tooltip.js +193 -0
  422. package/js/tooltip.ts +252 -0
  423. package/js/tree.js +162 -0
  424. package/js/tree.ts +218 -0
  425. package/js/tsconfig.json +18 -0
  426. package/js/utils.js +69 -0
  427. package/js/utils.ts +84 -0
  428. package/js/virtual-dropdown.js +277 -0
  429. package/js/virtual-dropdown.ts +366 -0
  430. package/package.json +38 -0
package/js/editor.ts ADDED
@@ -0,0 +1,426 @@
1
+ class Editor {
2
+ private readonly editable: HTMLElement;
3
+ private readonly code: HTMLTextAreaElement;
4
+ private readonly preview: HTMLElement;
5
+ private readonly sidePanel: HTMLElement;
6
+ private readonly wordCount: HTMLElement | null;
7
+ private undoStack: string[] = [];
8
+ private redoStack: string[] = [];
9
+
10
+ constructor() {
11
+ const editable = document.getElementById('editable');
12
+ const code = document.getElementById('code') as HTMLTextAreaElement;
13
+ const preview = document.getElementById('preview');
14
+ const sidePanel = document.getElementById('sidePanel');
15
+ const wordCount = document.getElementById('wordCount');
16
+
17
+ if (!editable || !code || !preview || !sidePanel) {
18
+ throw new Error('Editor: Required elements not found');
19
+ }
20
+
21
+ this.editable = editable;
22
+ this.code = code;
23
+ this.preview = preview;
24
+ this.sidePanel = sidePanel;
25
+ this.wordCount = wordCount;
26
+
27
+ this.bindToolbar();
28
+ this.bindActions();
29
+ this.bindKeyboard();
30
+ this.bindEditable();
31
+ this.bindTabs();
32
+ this.syncViews();
33
+ this.saveState();
34
+
35
+ // Start with side panel hidden
36
+ this.sidePanel.classList.add('hidden');
37
+ }
38
+
39
+ private bindToolbar(): void {
40
+ document.querySelectorAll<HTMLElement>('[data-cmd]').forEach(btn => {
41
+ btn.addEventListener('click', () => {
42
+ const cmd = btn.dataset.cmd!;
43
+ const val = btn.dataset.value ?? null;
44
+ this.exec(cmd, val);
45
+ this.editable.focus();
46
+ });
47
+ });
48
+ }
49
+
50
+ private bindActions(): void {
51
+ document.getElementById('linkBtn')?.addEventListener('click', () => {
52
+ const url = prompt('Enter URL:', 'https://');
53
+ if (url) this.exec('createLink', url);
54
+ });
55
+
56
+ const imageFile = document.getElementById('imageFile') as HTMLInputElement;
57
+ document.getElementById('imageBtn')?.addEventListener('click', () => imageFile.click());
58
+ imageFile?.addEventListener('change', () => {
59
+ const file = imageFile.files?.[0];
60
+ if (!file) return;
61
+ const reader = new FileReader();
62
+ reader.onload = () => {
63
+ if (typeof reader.result === 'string') {
64
+ this.insertImage(reader.result);
65
+ }
66
+ };
67
+ reader.readAsDataURL(file);
68
+ imageFile.value = '';
69
+ });
70
+
71
+ document.getElementById('cleanBtn')?.addEventListener('click', () => {
72
+ const sel = window.getSelection();
73
+ if (!sel || sel.rangeCount === 0) return;
74
+ const range = sel.getRangeAt(0);
75
+ const text = range.toString();
76
+ range.deleteContents();
77
+ range.insertNode(document.createTextNode(text));
78
+ this.onContentChange();
79
+ });
80
+
81
+ document.getElementById('undoBtn')?.addEventListener('click', () => this.undo());
82
+ document.getElementById('redoBtn')?.addEventListener('click', () => this.redo());
83
+
84
+ document.getElementById('toggleCodeBtn')?.addEventListener('click', () => {
85
+ this.sidePanel.classList.toggle('hidden');
86
+ this.syncViews();
87
+ });
88
+
89
+ // Code action buttons — matched by position within .code-actions
90
+ const codeActions = document.querySelectorAll<HTMLButtonElement>('.code-actions button');
91
+ codeActions[0]?.addEventListener('click', () => {
92
+ this.editable.innerHTML = this.sanitizeHTML(this.code.value);
93
+ this.onContentChange();
94
+ });
95
+ codeActions[1]?.addEventListener('click', () => {
96
+ this.code.value = this.sanitizeHTML(this.code.value);
97
+ this.editable.innerHTML = this.code.value;
98
+ this.onContentChange();
99
+ });
100
+ codeActions[2]?.addEventListener('click', () => {
101
+ this.code.value = this.code.value
102
+ .replace(/\n/g, '')
103
+ .replace(/>\s+</g, '><')
104
+ .trim();
105
+ });
106
+
107
+ const saveBtn = document.getElementById('saveBtn');
108
+ saveBtn?.addEventListener('click', () => this.downloadHTML());
109
+
110
+ document.getElementById('clearBtn')?.addEventListener('click', () => {
111
+ if (confirm('Clear all content?')) {
112
+ this.editable.innerHTML = '';
113
+ this.onContentChange();
114
+ }
115
+ });
116
+ }
117
+
118
+ private bindKeyboard(): void {
119
+ const saveBtn = document.getElementById('saveBtn');
120
+
121
+ window.addEventListener('keydown', (e: KeyboardEvent) => {
122
+ const mod = e.ctrlKey || e.metaKey;
123
+ if (!mod) return;
124
+
125
+ const key = e.key.toLowerCase();
126
+
127
+ if (key === 'b') { e.preventDefault(); this.exec('bold'); }
128
+ else if (key === 'i') { e.preventDefault(); this.exec('italic'); }
129
+ else if (key === 'u') { e.preventDefault(); this.exec('underline'); }
130
+ else if (key === 'k') {
131
+ e.preventDefault();
132
+ const url = prompt('Enter URL:', 'https://');
133
+ if (url) this.exec('createLink', url);
134
+ }
135
+ else if (key === 's') { e.preventDefault(); saveBtn?.click(); }
136
+ else if (key === 'z' && !e.shiftKey) { e.preventDefault(); this.undo(); }
137
+ else if (key === 'y' || (key === 'z' && e.shiftKey)) { e.preventDefault(); this.redo(); }
138
+ });
139
+ }
140
+
141
+ private bindEditable(): void {
142
+ this.editable.addEventListener('input', () => this.onContentChange());
143
+
144
+ this.editable.addEventListener('paste', (e: ClipboardEvent) => {
145
+ e.preventDefault();
146
+ const text = e.clipboardData?.getData('text/plain') ?? '';
147
+ this.insertText(text);
148
+ });
149
+
150
+ this.editable.addEventListener('keyup', () => this.refreshActiveState());
151
+ this.editable.addEventListener('mouseup', () => this.refreshActiveState());
152
+ }
153
+
154
+ private bindTabs(): void {
155
+ document.querySelectorAll<HTMLElement>('.side-tab[data-tab]').forEach(tab => {
156
+ tab.addEventListener('click', () => {
157
+ const targetId = tab.dataset.tab!;
158
+
159
+ document.querySelectorAll('.side-tab').forEach(t => t.classList.remove('active'));
160
+ document.querySelectorAll('.side-panel').forEach(p => p.classList.remove('active'));
161
+
162
+ tab.classList.add('active');
163
+ document.getElementById(targetId)?.classList.add('active');
164
+ });
165
+ });
166
+ }
167
+
168
+ private onContentChange(): void {
169
+ this.saveState();
170
+ this.syncViews();
171
+ }
172
+
173
+ private syncViews(): void {
174
+ this.code.value = this.editable.innerHTML.trim();
175
+ this.preview.innerHTML = this.editable.innerHTML;
176
+ this.updateWordCount();
177
+ }
178
+
179
+ private updateWordCount(): void {
180
+ if (!this.wordCount) return;
181
+ const text = this.editable.innerText || '';
182
+ const words = text.trim().split(/\s+/).filter(w => w.length > 0);
183
+ const count = words.length;
184
+ this.wordCount.textContent = `${count} word${count !== 1 ? 's' : ''}`;
185
+ }
186
+
187
+ private saveState(): void {
188
+ this.undoStack.push(this.editable.innerHTML);
189
+ if (this.undoStack.length > 100) this.undoStack.shift();
190
+ this.redoStack = [];
191
+ }
192
+
193
+ private undo(): void {
194
+ if (this.undoStack.length <= 1) return;
195
+ this.redoStack.push(this.undoStack.pop()!);
196
+ this.editable.innerHTML = this.undoStack[this.undoStack.length - 1];
197
+ this.syncViews();
198
+ }
199
+
200
+ private redo(): void {
201
+ if (this.redoStack.length === 0) return;
202
+ const state = this.redoStack.pop()!;
203
+ this.undoStack.push(state);
204
+ this.editable.innerHTML = state;
205
+ this.syncViews();
206
+ }
207
+
208
+ private exec(command: string, value: string | null = null): void {
209
+ switch (command) {
210
+ case 'bold': this.toggleInlineStyle('strong'); break;
211
+ case 'italic': this.toggleInlineStyle('em'); break;
212
+ case 'underline': this.toggleInlineStyle('u'); break;
213
+ case 'strikeThrough': this.toggleInlineStyle('s'); break;
214
+ case 'createLink': if (value) this.createLink(value); break;
215
+ case 'formatBlock': if (value) this.formatBlock(value); break;
216
+ case 'insertUnorderedList': this.insertList('ul'); break;
217
+ case 'insertOrderedList': this.insertList('ol'); break;
218
+ }
219
+ }
220
+
221
+ private insertText(text: string): void {
222
+ const sel = window.getSelection();
223
+ if (!sel || sel.rangeCount === 0) return;
224
+
225
+ const range = sel.getRangeAt(0);
226
+ range.deleteContents();
227
+ range.insertNode(document.createTextNode(text));
228
+ range.collapse(false);
229
+ sel.removeAllRanges();
230
+ sel.addRange(range);
231
+
232
+ this.onContentChange();
233
+ }
234
+
235
+ private insertImage(dataUrl: string): void {
236
+ const sel = window.getSelection();
237
+ if (!sel || sel.rangeCount === 0) return;
238
+
239
+ const range = sel.getRangeAt(0);
240
+ const img = document.createElement('img');
241
+ img.src = dataUrl;
242
+ img.style.maxWidth = '100%';
243
+ range.deleteContents();
244
+ range.insertNode(img);
245
+
246
+ range.setStartAfter(img);
247
+ range.collapse(true);
248
+ sel.removeAllRanges();
249
+ sel.addRange(range);
250
+
251
+ this.onContentChange();
252
+ }
253
+
254
+ private toggleInlineStyle(tagName: string): void {
255
+ const sel = window.getSelection();
256
+ if (!sel || sel.rangeCount === 0) return;
257
+
258
+ const range = sel.getRangeAt(0);
259
+ const container = range.commonAncestorContainer;
260
+ let current: HTMLElement | null = container.nodeType === Node.TEXT_NODE
261
+ ? container.parentElement
262
+ : container as HTMLElement;
263
+
264
+ let wrapper: HTMLElement | null = null;
265
+ while (current && current !== this.editable) {
266
+ if (current.tagName === tagName.toUpperCase()) {
267
+ wrapper = current;
268
+ break;
269
+ }
270
+ current = current.parentElement;
271
+ }
272
+
273
+ if (wrapper) {
274
+ const parent = wrapper.parentNode;
275
+ while (wrapper.firstChild) {
276
+ parent?.insertBefore(wrapper.firstChild, wrapper);
277
+ }
278
+ parent?.removeChild(wrapper);
279
+ } else {
280
+ const contents = range.extractContents();
281
+ const el = document.createElement(tagName);
282
+ el.appendChild(contents);
283
+ range.insertNode(el);
284
+ range.selectNodeContents(el);
285
+ sel.removeAllRanges();
286
+ sel.addRange(range);
287
+ }
288
+
289
+ this.onContentChange();
290
+ }
291
+
292
+ private createLink(url: string): void {
293
+ const sel = window.getSelection();
294
+ if (!sel || sel.rangeCount === 0) return;
295
+
296
+ const range = sel.getRangeAt(0);
297
+ const contents = range.extractContents();
298
+ const link = document.createElement('a');
299
+ link.href = url;
300
+ link.appendChild(contents);
301
+ range.insertNode(link);
302
+
303
+ this.onContentChange();
304
+ }
305
+
306
+ private formatBlock(tag: string): void {
307
+ const sel = window.getSelection();
308
+ if (!sel || sel.rangeCount === 0) return;
309
+
310
+ const range = sel.getRangeAt(0);
311
+ const container = range.commonAncestorContainer;
312
+ let blockElement: HTMLElement | null = container.nodeType === Node.TEXT_NODE
313
+ ? container.parentElement
314
+ : container as HTMLElement;
315
+
316
+ while (blockElement && blockElement !== this.editable && blockElement.parentElement !== this.editable) {
317
+ blockElement = blockElement.parentElement;
318
+ }
319
+
320
+ if (blockElement && blockElement !== this.editable) {
321
+ const newBlock = document.createElement(tag);
322
+ newBlock.innerHTML = blockElement.innerHTML;
323
+ blockElement.parentNode?.replaceChild(newBlock, blockElement);
324
+ this.onContentChange();
325
+ }
326
+ }
327
+
328
+ private insertList(listTag: string): void {
329
+ const sel = window.getSelection();
330
+ if (!sel || sel.rangeCount === 0) return;
331
+
332
+ const range = sel.getRangeAt(0);
333
+ const text = range.toString();
334
+
335
+ const list = document.createElement(listTag);
336
+ const lines = text ? text.split('\n').filter(l => l.trim()) : [''];
337
+
338
+ for (const line of lines) {
339
+ const li = document.createElement('li');
340
+ li.textContent = line.trim() || '\u200B';
341
+ list.appendChild(li);
342
+ }
343
+
344
+ range.deleteContents();
345
+ range.insertNode(list);
346
+
347
+ const lastLi = list.lastElementChild;
348
+ if (lastLi) {
349
+ range.setStart(lastLi, lastLi.childNodes.length);
350
+ range.collapse(true);
351
+ sel.removeAllRanges();
352
+ sel.addRange(range);
353
+ }
354
+
355
+ this.onContentChange();
356
+ }
357
+
358
+ private sanitizeHTML(html: string): string {
359
+ const parser = new DOMParser();
360
+ const doc = parser.parseFromString(html, 'text/html');
361
+
362
+ doc.querySelectorAll('script, style, iframe, object, embed').forEach(el => el.remove());
363
+
364
+ doc.querySelectorAll('*').forEach(el => {
365
+ for (const attr of Array.from(el.attributes)) {
366
+ if (attr.name.startsWith('on') || attr.value.trim().toLowerCase().startsWith('javascript:')) {
367
+ el.removeAttribute(attr.name);
368
+ }
369
+ }
370
+ });
371
+
372
+ return doc.body.innerHTML;
373
+ }
374
+
375
+ private downloadHTML(): void {
376
+ const content = this.sanitizeHTML(this.editable.innerHTML);
377
+ const html = `<!doctype html>
378
+ <html lang="en">
379
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Export</title></head>
380
+ <body>
381
+ ${content}
382
+ </body>
383
+ </html>`;
384
+ const blob = new Blob([html], { type: 'text/html' });
385
+ const a = document.createElement('a');
386
+ a.href = URL.createObjectURL(blob);
387
+ a.download = 'document.html';
388
+ a.click();
389
+ URL.revokeObjectURL(a.href);
390
+ }
391
+
392
+ private refreshActiveState(): void {
393
+ const sel = window.getSelection();
394
+ if (!sel || sel.rangeCount === 0) return;
395
+
396
+ const range = sel.getRangeAt(0);
397
+ const container = range.commonAncestorContainer;
398
+ const element = container.nodeType === Node.TEXT_NODE
399
+ ? container.parentElement
400
+ : container as HTMLElement;
401
+
402
+ document.querySelectorAll<HTMLElement>('[data-cmd]').forEach(btn => {
403
+ const cmd = btn.dataset.cmd;
404
+ let active = false;
405
+
406
+ let current: HTMLElement | null = element;
407
+ while (current && current !== this.editable) {
408
+ const tag = current.tagName?.toLowerCase();
409
+ if (
410
+ (cmd === 'bold' && (tag === 'strong' || tag === 'b')) ||
411
+ (cmd === 'italic' && (tag === 'em' || tag === 'i')) ||
412
+ (cmd === 'underline' && tag === 'u') ||
413
+ (cmd === 'strikeThrough' && tag === 's')
414
+ ) {
415
+ active = true;
416
+ break;
417
+ }
418
+ current = current.parentElement;
419
+ }
420
+
421
+ btn.classList.toggle('active', active);
422
+ });
423
+ }
424
+ }
425
+
426
+ export { Editor };
@@ -0,0 +1,268 @@
1
+ class FileUploader {
2
+ constructor(elementOrSelector, config = {}) {
3
+ this.files = new Map();
4
+ this.abortControllers = new Map();
5
+ this.preventDefaults = (e) => {
6
+ e.preventDefault();
7
+ e.stopPropagation();
8
+ };
9
+ this.handleDragEnter = () => {
10
+ this.dropZone.classList.add('drag-over');
11
+ };
12
+ this.handleDragLeave = () => {
13
+ this.dropZone.classList.remove('drag-over');
14
+ };
15
+ this.handleDrop = (e) => {
16
+ const droppedFiles = e.dataTransfer?.files;
17
+ if (droppedFiles) {
18
+ this.handleFiles(droppedFiles);
19
+ }
20
+ };
21
+ this.handleDropZoneClick = () => {
22
+ this.fileInput.click();
23
+ };
24
+ this.handleFileInputChange = (e) => {
25
+ const target = e.target;
26
+ if (target.files) {
27
+ this.handleFiles(target.files);
28
+ target.value = ''; // Reset input so same file can be selected again
29
+ }
30
+ };
31
+ this.handleUploadClick = async () => {
32
+ if (this.files.size === 0)
33
+ return;
34
+ this.uploadBtn.disabled = true;
35
+ this.uploadBtn.textContent = 'Uploading...';
36
+ const uploadPromises = Array.from(this.files.values()).map(({ file, element }) => this.uploadFile(file, element));
37
+ const results = await Promise.allSettled(uploadPromises);
38
+ this.uploadBtn.textContent = 'Upload Complete';
39
+ setTimeout(() => {
40
+ this.dispatchUploadCompletedEvent(results);
41
+ this.cleanupAfterUpload();
42
+ this.resetUploadState();
43
+ }, 1000);
44
+ };
45
+ const container = typeof elementOrSelector === 'string'
46
+ ? document.querySelector(elementOrSelector)
47
+ : elementOrSelector;
48
+ if (!container) {
49
+ throw new Error(`FileUploader: Element not found for selector "${elementOrSelector}"`);
50
+ }
51
+ this.container = container;
52
+ const dropZone = container.querySelector('#drop-zone');
53
+ const fileInput = container.querySelector('#file-input');
54
+ const fileList = container.querySelector('#file-list');
55
+ const uploadBtn = container.querySelector('#upload-btn');
56
+ if (!dropZone || !fileInput || !fileList || !uploadBtn) {
57
+ throw new Error('Required elements not found in container');
58
+ }
59
+ this.dropZone = dropZone;
60
+ this.fileInput = fileInput;
61
+ this.fileList = fileList;
62
+ this.uploadBtn = uploadBtn;
63
+ this.uploadUrl = config.uploadUrl ?? 'https://httpbin.org/post';
64
+ this.maxFileSize = config.maxFileSize;
65
+ this.allowedTypes = config.allowedTypes;
66
+ this.init();
67
+ }
68
+ init() {
69
+ this.setupEventListeners();
70
+ }
71
+ setupEventListeners() {
72
+ // Drag & Drop - prevent default browser behavior
73
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
74
+ this.dropZone.addEventListener(eventName, this.preventDefaults);
75
+ });
76
+ // Drag over effects
77
+ ['dragenter', 'dragover'].forEach(eventName => {
78
+ this.dropZone.addEventListener(eventName, this.handleDragEnter);
79
+ });
80
+ ['dragleave', 'drop'].forEach(eventName => {
81
+ this.dropZone.addEventListener(eventName, this.handleDragLeave);
82
+ });
83
+ this.dropZone.addEventListener('drop', this.handleDrop);
84
+ this.dropZone.addEventListener('click', this.handleDropZoneClick);
85
+ this.fileInput.addEventListener('change', this.handleFileInputChange);
86
+ this.uploadBtn.addEventListener('click', this.handleUploadClick);
87
+ }
88
+ handleFiles(fileList) {
89
+ Array.from(fileList).forEach(file => {
90
+ if (this.validateFile(file) && !this.files.has(file.name)) {
91
+ const element = this.addFileToUI(file);
92
+ this.files.set(file.name, { file, element });
93
+ }
94
+ });
95
+ this.updateUploadButton();
96
+ }
97
+ validateFile(file) {
98
+ if (this.maxFileSize && file.size > this.maxFileSize) {
99
+ console.warn(`File ${file.name} exceeds maximum size`);
100
+ return false;
101
+ }
102
+ if (this.allowedTypes && !this.allowedTypes.includes(file.type)) {
103
+ console.warn(`File type ${file.type} is not allowed`);
104
+ return false;
105
+ }
106
+ return true;
107
+ }
108
+ addFileToUI(file) {
109
+ const item = document.createElement('div');
110
+ item.className = 'file-item';
111
+ const escapedFileName = this.escapeHtml(file.name);
112
+ item.innerHTML = `
113
+ <div class="file-item-header">
114
+ <div class="file-info">
115
+ <div class="file-icon">
116
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
117
+ <path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path>
118
+ <polyline points="13 2 13 9 20 9"></polyline>
119
+ </svg>
120
+ </div>
121
+ <div class="file-details">
122
+ <span class="file-name" title="${escapedFileName}">${escapedFileName}</span>
123
+ <span class="file-size">${this.formatSize(file.size)}</span>
124
+ </div>
125
+ </div>
126
+ <button class="remove-btn" type="button" aria-label="Remove file">
127
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
128
+ <line x1="18" y1="6" x2="6" y2="18"></line>
129
+ <line x1="6" y1="6" x2="18" y2="18"></line>
130
+ </svg>
131
+ </button>
132
+ </div>
133
+ <div class="progress-container" style="display: none;">
134
+ <div class="progress-bar"></div>
135
+ </div>
136
+ <div class="status-text" style="display: none;">Waiting...</div>
137
+ `;
138
+ const removeBtn = item.querySelector('.remove-btn');
139
+ if (removeBtn) {
140
+ removeBtn.addEventListener('click', (e) => {
141
+ e.stopPropagation();
142
+ this.removeFile(file.name);
143
+ });
144
+ }
145
+ this.fileList.appendChild(item);
146
+ return item;
147
+ }
148
+ async uploadFile(file, element) {
149
+ const progressContainer = element.querySelector('.progress-container');
150
+ const progressBar = element.querySelector('.progress-bar');
151
+ const statusText = element.querySelector('.status-text');
152
+ const removeBtn = element.querySelector('.remove-btn');
153
+ if (!progressContainer || !progressBar || !statusText || !removeBtn) {
154
+ throw new Error('Required UI elements not found');
155
+ }
156
+ // Show progress elements
157
+ progressContainer.style.display = 'block';
158
+ statusText.style.display = 'block';
159
+ removeBtn.style.display = 'none';
160
+ const abortController = new AbortController();
161
+ this.abortControllers.set(file.name, abortController);
162
+ try {
163
+ const formData = new FormData();
164
+ formData.append('file', file);
165
+ const response = await fetch(this.uploadUrl, {
166
+ method: 'POST',
167
+ body: formData,
168
+ signal: abortController.signal,
169
+ });
170
+ // Note: Fetch API doesn't support upload progress natively
171
+ // For progress tracking, you'd need to use XMLHttpRequest or a library
172
+ progressBar.style.width = '100%';
173
+ statusText.textContent = '100%';
174
+ if (response.ok) {
175
+ statusText.textContent = 'Completed';
176
+ statusText.classList.add('success');
177
+ progressBar.style.backgroundColor = 'var(--success-color)';
178
+ return await response.json();
179
+ }
180
+ else {
181
+ throw new Error(`Upload failed: ${response.statusText}`);
182
+ }
183
+ }
184
+ catch (error) {
185
+ if (error instanceof Error && error.name === 'AbortError') {
186
+ statusText.textContent = 'Cancelled';
187
+ }
188
+ else {
189
+ statusText.textContent = error instanceof Error ? 'Error' : 'Network Error';
190
+ }
191
+ statusText.classList.add('error');
192
+ progressBar.style.backgroundColor = 'var(--error-color)';
193
+ removeBtn.style.display = 'flex';
194
+ throw error;
195
+ }
196
+ finally {
197
+ this.abortControllers.delete(file.name);
198
+ }
199
+ }
200
+ removeFile(fileName) {
201
+ // Cancel upload if in progress
202
+ const abortController = this.abortControllers.get(fileName);
203
+ if (abortController) {
204
+ abortController.abort();
205
+ }
206
+ const fileData = this.files.get(fileName);
207
+ if (fileData) {
208
+ fileData.element.remove();
209
+ this.files.delete(fileName);
210
+ this.updateUploadButton();
211
+ }
212
+ }
213
+ updateUploadButton() {
214
+ this.uploadBtn.disabled = this.files.size === 0;
215
+ this.uploadBtn.textContent =
216
+ this.files.size > 0
217
+ ? `Upload ${this.files.size} File${this.files.size === 1 ? '' : 's'}`
218
+ : 'Upload Files';
219
+ }
220
+ dispatchUploadCompletedEvent(results) {
221
+ const files = Array.from(this.files.values()).map(({ file }) => file);
222
+ const event = new CustomEvent('upload-completed', {
223
+ detail: {
224
+ fileCount: this.files.size,
225
+ files,
226
+ results,
227
+ },
228
+ bubbles: true,
229
+ });
230
+ this.container.dispatchEvent(event);
231
+ }
232
+ cleanupAfterUpload() {
233
+ const progressContainers = this.fileList.querySelectorAll('.progress-container');
234
+ progressContainers.forEach(el => el.remove());
235
+ const statusTexts = this.fileList.querySelectorAll('.status-text');
236
+ statusTexts.forEach(el => el.remove());
237
+ const removeBtns = this.fileList.querySelectorAll('.remove-btn');
238
+ removeBtns.forEach(btn => (btn.style.display = 'flex'));
239
+ }
240
+ resetUploadState() {
241
+ this.files.clear();
242
+ this.updateUploadButton();
243
+ }
244
+ formatSize(bytes) {
245
+ if (bytes === 0)
246
+ return '0 Bytes';
247
+ const k = 1024;
248
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
249
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
250
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
251
+ }
252
+ escapeHtml(text) {
253
+ const div = document.createElement('div');
254
+ div.textContent = text;
255
+ return div.innerHTML;
256
+ }
257
+ destroy() {
258
+ // Cancel all ongoing uploads
259
+ this.abortControllers.forEach(controller => controller.abort());
260
+ this.abortControllers.clear();
261
+ // Clear files
262
+ this.files.clear();
263
+ // Remove event listeners would require storing bound handlers
264
+ // For now, removing elements will clean up
265
+ this.fileList.innerHTML = '';
266
+ }
267
+ }
268
+ export { FileUploader };