@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/tabs.ts ADDED
@@ -0,0 +1,280 @@
1
+ type TabLayout = 'horizontal' | 'vertical';
2
+ type MenuPosition = 'top' | 'bottom' | 'left' | 'right';
3
+
4
+ interface TabsOptions {
5
+ layout?: TabLayout;
6
+ defaultTab?: number;
7
+ menuPos?: MenuPosition;
8
+ onChange?: (index: number) => void;
9
+ }
10
+
11
+ class Tabs {
12
+ private container: HTMLElement;
13
+ private options: Required<Omit<TabsOptions, 'onChange'>> & Pick<TabsOptions, 'onChange'>;
14
+ private tabItems: NodeListOf<Element>;
15
+ private tabPanels: NodeListOf<Element>;
16
+ private currentTab: number;
17
+
18
+ constructor(elementOrSelector: string | HTMLElement, options: TabsOptions = {}) {
19
+ const element = typeof elementOrSelector === 'string'
20
+ ? document.querySelector<HTMLElement>(elementOrSelector)
21
+ : elementOrSelector;
22
+
23
+ if (!element) {
24
+ throw new Error(`Tabs: Element not found for selector "${elementOrSelector}"`);
25
+ }
26
+
27
+ this.container = element;
28
+
29
+ // Set default options
30
+ const layout = options.layout || 'horizontal';
31
+ this.options = {
32
+ layout,
33
+ defaultTab: options.defaultTab ?? 0,
34
+ menuPos: options.menuPos || (layout === 'vertical' ? 'left' : 'top'),
35
+ onChange: options.onChange
36
+ };
37
+
38
+ this.currentTab = this.options.defaultTab;
39
+ this.tabItems = document.querySelectorAll('.tab-item'); // Will be set in init
40
+ this.tabPanels = document.querySelectorAll('.tab-panel'); // Will be set in init
41
+
42
+ this.init();
43
+ }
44
+
45
+ /**
46
+ * Initializes the tabs component
47
+ */
48
+ private init(): void {
49
+ // Apply layout class
50
+ if (this.options.layout === 'vertical') {
51
+ this.container.classList.add('tabs-vertical');
52
+ }
53
+
54
+ this.tabItems = this.container.querySelectorAll('.tab-item');
55
+ this.tabPanels = this.container.querySelectorAll('.tab-panel');
56
+
57
+ // Validate that we have tabs and panels
58
+ if (this.tabItems.length === 0) {
59
+ console.warn('No tab items found in container');
60
+ return;
61
+ }
62
+
63
+ if (this.tabPanels.length === 0) {
64
+ console.warn('No tab panels found in container');
65
+ return;
66
+ }
67
+
68
+ if (this.tabItems.length !== this.tabPanels.length) {
69
+ console.warn('Number of tab items does not match number of tab panels');
70
+ }
71
+
72
+ this.bindEvents();
73
+ this.activateTab(this.options.defaultTab);
74
+ }
75
+
76
+ /**
77
+ * Binds click events to tab items
78
+ */
79
+ private bindEvents(): void {
80
+ this.tabItems.forEach((item, index) => {
81
+ item.addEventListener('click', (e: Event) => {
82
+ e.preventDefault();
83
+ this.activateTab(index);
84
+ });
85
+
86
+ // Add keyboard navigation for accessibility
87
+ item.addEventListener('keydown', (e: Event) => {
88
+ const keyEvent = e as KeyboardEvent;
89
+ this.handleKeyboardNavigation(keyEvent, index);
90
+ });
91
+
92
+ // Set ARIA attributes
93
+ item.setAttribute('role', 'tab');
94
+ item.setAttribute('tabindex', index === this.options.defaultTab ? '0' : '-1');
95
+ item.setAttribute('aria-selected', index === this.options.defaultTab ? 'true' : 'false');
96
+ });
97
+
98
+ // Set ARIA attributes for panels
99
+ this.tabPanels.forEach((panel, index) => {
100
+ panel.setAttribute('role', 'tabpanel');
101
+ panel.setAttribute('aria-hidden', index === this.options.defaultTab ? 'false' : 'true');
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Handles keyboard navigation (Arrow keys, Home, End)
107
+ */
108
+ private handleKeyboardNavigation(e: KeyboardEvent, currentIndex: number): void {
109
+ let newIndex = currentIndex;
110
+ const isVertical = this.options.layout === 'vertical';
111
+
112
+ switch (e.key) {
113
+ case 'ArrowLeft':
114
+ if (!isVertical) {
115
+ newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabItems.length - 1;
116
+ e.preventDefault();
117
+ }
118
+ break;
119
+ case 'ArrowRight':
120
+ if (!isVertical) {
121
+ newIndex = currentIndex < this.tabItems.length - 1 ? currentIndex + 1 : 0;
122
+ e.preventDefault();
123
+ }
124
+ break;
125
+ case 'ArrowUp':
126
+ if (isVertical) {
127
+ newIndex = currentIndex > 0 ? currentIndex - 1 : this.tabItems.length - 1;
128
+ e.preventDefault();
129
+ }
130
+ break;
131
+ case 'ArrowDown':
132
+ if (isVertical) {
133
+ newIndex = currentIndex < this.tabItems.length - 1 ? currentIndex + 1 : 0;
134
+ e.preventDefault();
135
+ }
136
+ break;
137
+ case 'Home':
138
+ newIndex = 0;
139
+ e.preventDefault();
140
+ break;
141
+ case 'End':
142
+ newIndex = this.tabItems.length - 1;
143
+ e.preventDefault();
144
+ break;
145
+ default:
146
+ return;
147
+ }
148
+
149
+ if (newIndex !== currentIndex) {
150
+ this.activateTab(newIndex);
151
+ (this.tabItems[newIndex] as HTMLElement).focus();
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Activates a tab by index
157
+ */
158
+ private activateTab(index: number): void {
159
+ if (index < 0 || index >= this.tabItems.length) {
160
+ console.warn(`Invalid tab index: ${index}`);
161
+ return;
162
+ }
163
+
164
+ // Remove active class from all
165
+ this.tabItems.forEach((item, i) => {
166
+ item.classList.remove('active');
167
+ item.setAttribute('tabindex', '-1');
168
+ item.setAttribute('aria-selected', 'false');
169
+ });
170
+
171
+ this.tabPanels.forEach((panel) => {
172
+ panel.classList.remove('active');
173
+ panel.setAttribute('aria-hidden', 'true');
174
+ });
175
+
176
+ // Add active class to selected
177
+ this.tabItems[index].classList.add('active');
178
+ this.tabItems[index].setAttribute('tabindex', '0');
179
+ this.tabItems[index].setAttribute('aria-selected', 'true');
180
+
181
+ this.tabPanels[index].classList.add('active');
182
+ this.tabPanels[index].setAttribute('aria-hidden', 'false');
183
+
184
+ const previousTab = this.currentTab;
185
+ this.currentTab = index;
186
+
187
+ // Call onChange callback if provided
188
+ if (this.options.onChange && previousTab !== index) {
189
+ this.options.onChange(index);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Public API: Programmatically activate a tab
195
+ */
196
+ public goToTab(index: number): void {
197
+ this.activateTab(index);
198
+
199
+ // Focus the tab for keyboard users
200
+ if (this.tabItems[index]) {
201
+ (this.tabItems[index] as HTMLElement).focus();
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Public API: Get the currently active tab index
207
+ */
208
+ public getCurrentTab(): number {
209
+ return this.currentTab;
210
+ }
211
+
212
+ /**
213
+ * Public API: Get the total number of tabs
214
+ */
215
+ public getTabCount(): number {
216
+ return this.tabItems.length;
217
+ }
218
+
219
+ /**
220
+ * Public API: Enable a tab
221
+ */
222
+ public enableTab(index: number): void {
223
+ if (index < 0 || index >= this.tabItems.length) return;
224
+
225
+ const tab = this.tabItems[index] as HTMLElement;
226
+ tab.classList.remove('disabled');
227
+ tab.removeAttribute('aria-disabled');
228
+ tab.style.pointerEvents = '';
229
+ }
230
+
231
+ /**
232
+ * Public API: Disable a tab
233
+ */
234
+ public disableTab(index: number): void {
235
+ if (index < 0 || index >= this.tabItems.length) return;
236
+
237
+ const tab = this.tabItems[index] as HTMLElement;
238
+ tab.classList.add('disabled');
239
+ tab.setAttribute('aria-disabled', 'true');
240
+ tab.style.pointerEvents = 'none';
241
+
242
+ // If disabling the current tab, switch to the first enabled tab
243
+ if (index === this.currentTab) {
244
+ const firstEnabled = Array.from(this.tabItems).findIndex(
245
+ (item) => !item.classList.contains('disabled')
246
+ );
247
+ if (firstEnabled !== -1) {
248
+ this.activateTab(firstEnabled);
249
+ }
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Public API: Destroy the tabs instance and clean up
255
+ */
256
+ public destroy(): void {
257
+ // Remove event listeners by cloning and replacing nodes
258
+ this.tabItems.forEach((item) => {
259
+ const newItem = item.cloneNode(true);
260
+ item.parentNode?.replaceChild(newItem, item);
261
+ });
262
+
263
+ // Remove classes
264
+ this.container.classList.remove('tabs-vertical');
265
+
266
+ // Remove ARIA attributes
267
+ this.tabItems.forEach((item) => {
268
+ item.removeAttribute('role');
269
+ item.removeAttribute('tabindex');
270
+ item.removeAttribute('aria-selected');
271
+ });
272
+
273
+ this.tabPanels.forEach((panel) => {
274
+ panel.removeAttribute('role');
275
+ panel.removeAttribute('aria-hidden');
276
+ });
277
+ }
278
+ }
279
+
280
+ export { Tabs };
package/js/theme.js ADDED
@@ -0,0 +1,194 @@
1
+ class Theme {
2
+ /**
3
+ * Initializes the theme system with toggle functionality and system preference detection
4
+ */
5
+ static init() {
6
+ this.root = document.documentElement;
7
+ // Get DOM elements
8
+ const toggleBtn = document.getElementById('theme-toggle');
9
+ const icon = document.getElementById('theme-icon');
10
+ const status = document.getElementById('status');
11
+ // Validate required elements
12
+ if (!toggleBtn || !icon) {
13
+ console.error('Theme toggle: missing DOM elements', { toggleBtn, icon });
14
+ if (status) {
15
+ status.textContent = 'Error: missing toggle elements (check IDs).';
16
+ }
17
+ return;
18
+ }
19
+ this.elements = { toggleBtn, icon, status };
20
+ // Initialize media query
21
+ if (window.matchMedia) {
22
+ this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
23
+ }
24
+ // Apply initial theme
25
+ const savedTheme = this.getSavedTheme();
26
+ const systemTheme = this.getSystemTheme();
27
+ const initialTheme = savedTheme || systemTheme;
28
+ this.applyTheme(initialTheme);
29
+ // Bind event listeners
30
+ this.bindToggleClick();
31
+ this.bindKeyboardShortcut();
32
+ this.bindSystemThemeChange();
33
+ }
34
+ /**
35
+ * Safely retrieves the saved theme from localStorage
36
+ */
37
+ static getSavedTheme() {
38
+ try {
39
+ const saved = localStorage.getItem(this.STORAGE_KEY);
40
+ return saved === 'dark' || saved === 'light' ? saved : null;
41
+ }
42
+ catch (e) {
43
+ console.warn('localStorage.getItem failed', e);
44
+ return null;
45
+ }
46
+ }
47
+ /**
48
+ * Safely saves the theme to localStorage
49
+ */
50
+ static saveTheme(theme) {
51
+ try {
52
+ localStorage.setItem(this.STORAGE_KEY, theme);
53
+ }
54
+ catch (e) {
55
+ console.warn('localStorage.setItem failed', e);
56
+ }
57
+ }
58
+ /**
59
+ * Gets the system-preferred theme
60
+ */
61
+ static getSystemTheme() {
62
+ return this.mediaQuery?.matches ? 'dark' : 'light';
63
+ }
64
+ /**
65
+ * Gets the current active theme
66
+ */
67
+ static getCurrentTheme() {
68
+ const current = this.root.getAttribute('data-theme');
69
+ return current === 'dark' ? 'dark' : 'light';
70
+ }
71
+ /**
72
+ * Applies a theme to the document
73
+ */
74
+ static applyTheme(theme) {
75
+ if (!this.elements)
76
+ return;
77
+ this.root.setAttribute('data-theme', theme);
78
+ const isDark = theme === 'dark';
79
+ const { toggleBtn, icon } = this.elements;
80
+ // Update button state
81
+ toggleBtn.setAttribute('aria-pressed', String(isDark));
82
+ toggleBtn.setAttribute('aria-label', `Switch to ${isDark ? 'light' : 'dark'} mode`);
83
+ // Update icon classes
84
+ if (isDark) {
85
+ icon.classList.remove('icon-light');
86
+ icon.classList.add('icon-dark');
87
+ }
88
+ else {
89
+ icon.classList.remove('icon-dark');
90
+ icon.classList.add('icon-light');
91
+ }
92
+ }
93
+ /**
94
+ * Toggles between light and dark theme
95
+ */
96
+ static toggleTheme() {
97
+ if (!this.elements)
98
+ return;
99
+ try {
100
+ const current = this.getCurrentTheme();
101
+ const next = current === 'dark' ? 'light' : 'dark';
102
+ this.saveTheme(next);
103
+ this.applyTheme(next);
104
+ }
105
+ catch (err) {
106
+ console.error('Error toggling theme', err);
107
+ if (this.elements.status) {
108
+ this.elements.status.textContent = 'Error toggling theme (see console).';
109
+ }
110
+ }
111
+ }
112
+ /**
113
+ * Binds click event to toggle button
114
+ */
115
+ static bindToggleClick() {
116
+ if (!this.elements)
117
+ return;
118
+ this.elements.toggleBtn.addEventListener('click', () => {
119
+ this.toggleTheme();
120
+ });
121
+ }
122
+ /**
123
+ * Binds keyboard shortcut (Ctrl/Cmd+J) for theme toggle
124
+ */
125
+ static bindKeyboardShortcut() {
126
+ window.addEventListener('keydown', (ev) => {
127
+ const isMac = /Mac|iPhone|iPod|iPad/i.test(navigator.platform);
128
+ const modifierPressed = isMac ? ev.metaKey : ev.ctrlKey;
129
+ if (modifierPressed && ev.key.toLowerCase() === 'j') {
130
+ ev.preventDefault();
131
+ this.toggleTheme();
132
+ }
133
+ });
134
+ }
135
+ /**
136
+ * Binds listener for system theme changes
137
+ * Only applies if user hasn't explicitly saved a preference
138
+ */
139
+ static bindSystemThemeChange() {
140
+ if (!this.mediaQuery)
141
+ return;
142
+ const handler = (e) => {
143
+ // Only apply system theme if user hasn't saved a preference
144
+ if (!this.getSavedTheme()) {
145
+ const matches = 'matches' in e ? e.matches : e.matches;
146
+ this.applyTheme(matches ? 'dark' : 'light');
147
+ }
148
+ };
149
+ // Modern API
150
+ if ('addEventListener' in this.mediaQuery) {
151
+ this.mediaQuery.addEventListener('change', handler);
152
+ }
153
+ // Legacy API (deprecated but still supported in older browsers)
154
+ else if ('addListener' in this.mediaQuery) {
155
+ this.mediaQuery.addListener(handler);
156
+ }
157
+ }
158
+ /**
159
+ * Public API: Get the current theme
160
+ */
161
+ static getTheme() {
162
+ return this.getCurrentTheme();
163
+ }
164
+ /**
165
+ * Public API: Set the theme programmatically
166
+ */
167
+ static setTheme(theme) {
168
+ this.saveTheme(theme);
169
+ this.applyTheme(theme);
170
+ }
171
+ /**
172
+ * Public API: Reset to system preference
173
+ */
174
+ static resetToSystem() {
175
+ try {
176
+ localStorage.removeItem(this.STORAGE_KEY);
177
+ const systemTheme = this.getSystemTheme();
178
+ this.applyTheme(systemTheme);
179
+ }
180
+ catch (e) {
181
+ console.warn('Failed to reset theme', e);
182
+ }
183
+ }
184
+ /**
185
+ * Public API: Check if user has a saved preference
186
+ */
187
+ static hasSavedPreference() {
188
+ return this.getSavedTheme() !== null;
189
+ }
190
+ }
191
+ Theme.STORAGE_KEY = 'theme';
192
+ Theme.elements = null;
193
+ Theme.mediaQuery = null;
194
+ export { Theme };
package/js/theme.ts ADDED
@@ -0,0 +1,225 @@
1
+ type ThemeMode = 'light' | 'dark';
2
+
3
+ interface ThemeElements {
4
+ toggleBtn: HTMLElement;
5
+ icon: HTMLElement;
6
+ status: HTMLElement | null;
7
+ }
8
+
9
+ class Theme {
10
+ private static readonly STORAGE_KEY = 'theme';
11
+ private static root: HTMLElement;
12
+ private static elements: ThemeElements | null = null;
13
+ private static mediaQuery: MediaQueryList | null = null;
14
+
15
+ /**
16
+ * Initializes the theme system with toggle functionality and system preference detection
17
+ */
18
+ public static init(): void {
19
+ this.root = document.documentElement;
20
+
21
+ // Get DOM elements
22
+ const toggleBtn = document.getElementById('theme-toggle');
23
+ const icon = document.getElementById('theme-icon');
24
+ const status = document.getElementById('status');
25
+
26
+ // Validate required elements
27
+ if (!toggleBtn || !icon) {
28
+ console.error('Theme toggle: missing DOM elements', { toggleBtn, icon });
29
+ if (status) {
30
+ status.textContent = 'Error: missing toggle elements (check IDs).';
31
+ }
32
+ return;
33
+ }
34
+
35
+ this.elements = { toggleBtn, icon, status };
36
+
37
+ // Initialize media query
38
+ if (window.matchMedia) {
39
+ this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
40
+ }
41
+
42
+ // Apply initial theme
43
+ const savedTheme = this.getSavedTheme();
44
+ const systemTheme = this.getSystemTheme();
45
+ const initialTheme = savedTheme || systemTheme;
46
+ this.applyTheme(initialTheme);
47
+
48
+ // Bind event listeners
49
+ this.bindToggleClick();
50
+ this.bindKeyboardShortcut();
51
+ this.bindSystemThemeChange();
52
+ }
53
+
54
+ /**
55
+ * Safely retrieves the saved theme from localStorage
56
+ */
57
+ private static getSavedTheme(): ThemeMode | null {
58
+ try {
59
+ const saved = localStorage.getItem(this.STORAGE_KEY);
60
+ return saved === 'dark' || saved === 'light' ? saved : null;
61
+ } catch (e) {
62
+ console.warn('localStorage.getItem failed', e);
63
+ return null;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Safely saves the theme to localStorage
69
+ */
70
+ private static saveTheme(theme: ThemeMode): void {
71
+ try {
72
+ localStorage.setItem(this.STORAGE_KEY, theme);
73
+ } catch (e) {
74
+ console.warn('localStorage.setItem failed', e);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Gets the system-preferred theme
80
+ */
81
+ private static getSystemTheme(): ThemeMode {
82
+ return this.mediaQuery?.matches ? 'dark' : 'light';
83
+ }
84
+
85
+ /**
86
+ * Gets the current active theme
87
+ */
88
+ private static getCurrentTheme(): ThemeMode {
89
+ const current = this.root.getAttribute('data-theme');
90
+ return current === 'dark' ? 'dark' : 'light';
91
+ }
92
+
93
+ /**
94
+ * Applies a theme to the document
95
+ */
96
+ private static applyTheme(theme: ThemeMode): void {
97
+ if (!this.elements) return;
98
+
99
+ this.root.setAttribute('data-theme', theme);
100
+
101
+ const isDark = theme === 'dark';
102
+ const { toggleBtn, icon } = this.elements;
103
+
104
+ // Update button state
105
+ toggleBtn.setAttribute('aria-pressed', String(isDark));
106
+ toggleBtn.setAttribute('aria-label', `Switch to ${isDark ? 'light' : 'dark'} mode`);
107
+
108
+ // Update icon classes
109
+ if (isDark) {
110
+ icon.classList.remove('icon-light');
111
+ icon.classList.add('icon-dark');
112
+ } else {
113
+ icon.classList.remove('icon-dark');
114
+ icon.classList.add('icon-light');
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Toggles between light and dark theme
120
+ */
121
+ private static toggleTheme(): void {
122
+ if (!this.elements) return;
123
+
124
+ try {
125
+ const current = this.getCurrentTheme();
126
+ const next: ThemeMode = current === 'dark' ? 'light' : 'dark';
127
+
128
+ this.saveTheme(next);
129
+ this.applyTheme(next);
130
+ } catch (err) {
131
+ console.error('Error toggling theme', err);
132
+ if (this.elements.status) {
133
+ this.elements.status.textContent = 'Error toggling theme (see console).';
134
+ }
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Binds click event to toggle button
140
+ */
141
+ private static bindToggleClick(): void {
142
+ if (!this.elements) return;
143
+
144
+ this.elements.toggleBtn.addEventListener('click', () => {
145
+ this.toggleTheme();
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Binds keyboard shortcut (Ctrl/Cmd+J) for theme toggle
151
+ */
152
+ private static bindKeyboardShortcut(): void {
153
+ window.addEventListener('keydown', (ev: KeyboardEvent) => {
154
+ const isMac = /Mac|iPhone|iPod|iPad/i.test(navigator.platform);
155
+ const modifierPressed = isMac ? ev.metaKey : ev.ctrlKey;
156
+
157
+ if (modifierPressed && ev.key.toLowerCase() === 'j') {
158
+ ev.preventDefault();
159
+ this.toggleTheme();
160
+ }
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Binds listener for system theme changes
166
+ * Only applies if user hasn't explicitly saved a preference
167
+ */
168
+ private static bindSystemThemeChange(): void {
169
+ if (!this.mediaQuery) return;
170
+
171
+ const handler = (e: MediaQueryListEvent | MediaQueryList): void => {
172
+ // Only apply system theme if user hasn't saved a preference
173
+ if (!this.getSavedTheme()) {
174
+ const matches = 'matches' in e ? e.matches : (e as MediaQueryList).matches;
175
+ this.applyTheme(matches ? 'dark' : 'light');
176
+ }
177
+ };
178
+
179
+ // Modern API
180
+ if ('addEventListener' in this.mediaQuery) {
181
+ this.mediaQuery.addEventListener('change', handler as (e: MediaQueryListEvent) => void);
182
+ }
183
+ // Legacy API (deprecated but still supported in older browsers)
184
+ else if ('addListener' in this.mediaQuery) {
185
+ (this.mediaQuery as any).addListener(handler);
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Public API: Get the current theme
191
+ */
192
+ public static getTheme(): ThemeMode {
193
+ return this.getCurrentTheme();
194
+ }
195
+
196
+ /**
197
+ * Public API: Set the theme programmatically
198
+ */
199
+ public static setTheme(theme: ThemeMode): void {
200
+ this.saveTheme(theme);
201
+ this.applyTheme(theme);
202
+ }
203
+
204
+ /**
205
+ * Public API: Reset to system preference
206
+ */
207
+ public static resetToSystem(): void {
208
+ try {
209
+ localStorage.removeItem(this.STORAGE_KEY);
210
+ const systemTheme = this.getSystemTheme();
211
+ this.applyTheme(systemTheme);
212
+ } catch (e) {
213
+ console.warn('Failed to reset theme', e);
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Public API: Check if user has a saved preference
219
+ */
220
+ public static hasSavedPreference(): boolean {
221
+ return this.getSavedTheme() !== null;
222
+ }
223
+ }
224
+
225
+ export { Theme };