@dodlhuat/basix 1.1.1 → 1.2.1

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 (352) hide show
  1. package/README.md +706 -482
  2. package/css/accordion.scss +86 -87
  3. package/css/alert.scss +137 -137
  4. package/css/badge.scss +104 -0
  5. package/css/bottom-sheet.scss +192 -0
  6. package/css/breadcrumb.scss +158 -0
  7. package/css/button.scss +48 -0
  8. package/css/calendar.scss +957 -0
  9. package/css/card.scss +65 -65
  10. package/css/chart.scss +270 -157
  11. package/css/chat-bubbles.scss +134 -68
  12. package/css/chips.scss +109 -19
  13. package/css/colors.scss +32 -32
  14. package/css/context-menu.scss +182 -0
  15. package/css/datepicker.scss +336 -336
  16. package/css/defaults.scss +90 -90
  17. package/css/docs.scss +529 -0
  18. package/css/editor.scss +664 -461
  19. package/css/file-uploader.scss +1 -1
  20. package/css/flyout-menu.scss +361 -361
  21. package/css/form.scss +124 -0
  22. package/css/gallery.scss +65 -6
  23. package/css/grid.scss +41 -40
  24. package/css/group-picker.scss +345 -0
  25. package/css/guitar-chords.css +250 -250
  26. package/css/icons.scss +330 -330
  27. package/css/parameters.scss +3 -3
  28. package/css/placeholder.scss +33 -33
  29. package/css/popover.scss +206 -0
  30. package/css/progress.scss +76 -32
  31. package/css/properties.scss +51 -36
  32. package/css/push-menu.scss +302 -174
  33. package/css/reset.scss +39 -39
  34. package/css/scrollbar.scss +62 -5
  35. package/css/sidebar-nav.scss +92 -0
  36. package/css/spinner.scss +65 -65
  37. package/css/stepper.scss +248 -0
  38. package/css/style.css +4603 -273
  39. package/css/style.css.map +1 -1
  40. package/css/style.min.css +1 -1
  41. package/css/style.scss +51 -39
  42. package/css/table.scss +199 -199
  43. package/css/tabs.scss +154 -123
  44. package/css/timeline.scss +83 -38
  45. package/css/timepicker.scss +100 -5
  46. package/css/toast.scss +81 -81
  47. package/css/typography.scss +194 -161
  48. package/css/virtual-dropdown.scss +35 -29
  49. package/js/bottom-sheet.js +173 -0
  50. package/js/bottom-sheet.ts +222 -0
  51. package/js/calendar.js +532 -0
  52. package/js/calendar.ts +706 -0
  53. package/js/carousel.js +26 -13
  54. package/js/chart.js +573 -257
  55. package/js/chart.ts +692 -0
  56. package/js/code-viewer.js +10 -10
  57. package/js/code-viewer.ts +188 -188
  58. package/js/context-menu.js +212 -0
  59. package/js/context-menu.ts +252 -0
  60. package/js/datepicker.ts +627 -627
  61. package/js/docs-nav.js +204 -0
  62. package/js/dropdown.ts +179 -179
  63. package/js/editor.js +96 -38
  64. package/js/editor.ts +483 -425
  65. package/js/file-uploader.js +1 -0
  66. package/js/file-uploader.ts +1 -0
  67. package/js/flyout-menu.js +14 -14
  68. package/js/flyout-menu.ts +249 -249
  69. package/js/form-builder.js +106 -106
  70. package/js/gallery.js +13 -6
  71. package/js/gallery.ts +245 -236
  72. package/js/group-picker.js +342 -0
  73. package/js/group-picker.ts +447 -0
  74. package/js/guitar-chords.js +268 -268
  75. package/js/lazy-loader.js +121 -121
  76. package/js/modal.ts +166 -166
  77. package/js/popover.js +163 -0
  78. package/js/popover.ts +219 -0
  79. package/js/position.js +108 -0
  80. package/js/position.ts +111 -0
  81. package/js/push-menu.js +226 -113
  82. package/js/push-menu.ts +284 -145
  83. package/js/request.js +50 -50
  84. package/js/scroll.ts +47 -47
  85. package/js/scrollbar.js +13 -0
  86. package/js/scrollbar.ts +324 -307
  87. package/js/select.ts +216 -216
  88. package/js/sidebar-nav.js +41 -0
  89. package/js/sidebar-nav.ts +66 -0
  90. package/js/stepper.js +80 -0
  91. package/js/stepper.ts +104 -0
  92. package/js/table.ts +452 -452
  93. package/js/tabs.ts +279 -279
  94. package/js/theme.js +17 -6
  95. package/js/theme.ts +234 -224
  96. package/js/timepicker.js +21 -8
  97. package/js/toast.ts +137 -137
  98. package/js/tooltip.js +6 -60
  99. package/js/tooltip.ts +184 -251
  100. package/js/tsconfig.json +18 -18
  101. package/js/utils.ts +83 -83
  102. package/js/virtual-dropdown.js +25 -25
  103. package/js/virtual-dropdown.ts +365 -365
  104. package/package.json +39 -39
  105. package/fonts/Outfit-VariableFont_wght.woff +0 -0
  106. package/fonts/material-icons.woff2 +0 -0
  107. package/icons/activity-outline.svg +0 -1
  108. package/icons/alert-circle-outline.svg +0 -1
  109. package/icons/alert-triangle-outline.svg +0 -1
  110. package/icons/archive-outline.svg +0 -1
  111. package/icons/arrow-back-outline.svg +0 -1
  112. package/icons/arrow-circle-down-outline.svg +0 -1
  113. package/icons/arrow-circle-left-outline.svg +0 -1
  114. package/icons/arrow-circle-right-outline.svg +0 -1
  115. package/icons/arrow-circle-up-outline.svg +0 -1
  116. package/icons/arrow-down-outline.svg +0 -1
  117. package/icons/arrow-downward-outline.svg +0 -1
  118. package/icons/arrow-forward-outline.svg +0 -1
  119. package/icons/arrow-ios-back-outline.svg +0 -1
  120. package/icons/arrow-ios-downward-outline.svg +0 -1
  121. package/icons/arrow-ios-forward-outline.svg +0 -1
  122. package/icons/arrow-ios-upward-outline.svg +0 -1
  123. package/icons/arrow-left-outline.svg +0 -1
  124. package/icons/arrow-right-outline.svg +0 -1
  125. package/icons/arrow-up-outline.svg +0 -1
  126. package/icons/arrow-upward-outline.svg +0 -1
  127. package/icons/arrowhead-down-outline.svg +0 -1
  128. package/icons/arrowhead-left-outline.svg +0 -1
  129. package/icons/arrowhead-right-outline.svg +0 -1
  130. package/icons/arrowhead-up-outline.svg +0 -1
  131. package/icons/at-outline.svg +0 -1
  132. package/icons/attach-2-outline.svg +0 -1
  133. package/icons/attach-outline.svg +0 -1
  134. package/icons/award-outline.svg +0 -1
  135. package/icons/backspace-outline.svg +0 -1
  136. package/icons/bar-chart-2-outline.svg +0 -1
  137. package/icons/bar-chart-outline.svg +0 -1
  138. package/icons/battery-outline.svg +0 -1
  139. package/icons/behance-outline.svg +0 -1
  140. package/icons/bell-off-outline.svg +0 -1
  141. package/icons/bell-outline.svg +0 -1
  142. package/icons/bluetooth-outline.svg +0 -1
  143. package/icons/book-open-outline.svg +0 -1
  144. package/icons/book-outline.svg +0 -1
  145. package/icons/bookmark-outline.svg +0 -1
  146. package/icons/briefcase-outline.svg +0 -1
  147. package/icons/browser-outline.svg +0 -1
  148. package/icons/brush-outline.svg +0 -1
  149. package/icons/bulb-outline.svg +0 -1
  150. package/icons/calendar-outline.svg +0 -1
  151. package/icons/camera-outline.svg +0 -1
  152. package/icons/car-outline.svg +0 -1
  153. package/icons/cast-outline.svg +0 -1
  154. package/icons/charging-outline.svg +0 -1
  155. package/icons/checkmark-circle-2-outline.svg +0 -1
  156. package/icons/checkmark-circle-outline.svg +0 -1
  157. package/icons/checkmark-outline.svg +0 -1
  158. package/icons/checkmark-square-2-outline.svg +0 -1
  159. package/icons/checkmark-square-outline.svg +0 -1
  160. package/icons/chevron-down-outline.svg +0 -1
  161. package/icons/chevron-left-outline.svg +0 -1
  162. package/icons/chevron-right-outline.svg +0 -1
  163. package/icons/chevron-up-outline.svg +0 -1
  164. package/icons/clipboard-outline.svg +0 -1
  165. package/icons/clock-outline.svg +0 -1
  166. package/icons/close-circle-outline.svg +0 -1
  167. package/icons/close-outline.svg +0 -1
  168. package/icons/close-square-outline.svg +0 -1
  169. package/icons/cloud-download-outline.svg +0 -1
  170. package/icons/cloud-upload-outline.svg +0 -1
  171. package/icons/code-download-outline.svg +0 -1
  172. package/icons/code-outline.svg +0 -1
  173. package/icons/collapse-outline.svg +0 -1
  174. package/icons/color-palette-outline.svg +0 -1
  175. package/icons/color-picker-outline.svg +0 -1
  176. package/icons/compass-outline.svg +0 -1
  177. package/icons/copy-outline.svg +0 -1
  178. package/icons/corner-down-left-outline.svg +0 -1
  179. package/icons/corner-down-right-outline.svg +0 -1
  180. package/icons/corner-left-down-outline.svg +0 -1
  181. package/icons/corner-left-up-outline.svg +0 -1
  182. package/icons/corner-right-down-outline.svg +0 -1
  183. package/icons/corner-right-up-outline.svg +0 -1
  184. package/icons/corner-up-left-outline.svg +0 -1
  185. package/icons/corner-up-right-outline.svg +0 -1
  186. package/icons/credit-card-outline.svg +0 -1
  187. package/icons/crop-outline.svg +0 -1
  188. package/icons/cube-outline.svg +0 -1
  189. package/icons/diagonal-arrow-left-down-outline.svg +0 -1
  190. package/icons/diagonal-arrow-left-up-outline.svg +0 -1
  191. package/icons/diagonal-arrow-right-down-outline.svg +0 -1
  192. package/icons/diagonal-arrow-right-up-outline.svg +0 -1
  193. package/icons/done-all-outline.svg +0 -1
  194. package/icons/download-outline.svg +0 -1
  195. package/icons/droplet-off-outline.svg +0 -1
  196. package/icons/droplet-outline.svg +0 -1
  197. package/icons/edit-2-outline.svg +0 -1
  198. package/icons/edit-outline.svg +0 -1
  199. package/icons/email-outline.svg +0 -1
  200. package/icons/expand-outline.svg +0 -1
  201. package/icons/external-link-outline.svg +0 -1
  202. package/icons/eye-off-2-outline.svg +0 -1
  203. package/icons/eye-off-outline.svg +0 -1
  204. package/icons/eye-outline.svg +0 -1
  205. package/icons/facebook-outline.svg +0 -1
  206. package/icons/file-add-outline.svg +0 -1
  207. package/icons/file-outline.svg +0 -1
  208. package/icons/file-remove-outline.svg +0 -1
  209. package/icons/file-text-outline.svg +0 -1
  210. package/icons/film-outline.svg +0 -1
  211. package/icons/flag-outline.svg +0 -1
  212. package/icons/flash-off-outline.svg +0 -1
  213. package/icons/flash-outline.svg +0 -1
  214. package/icons/flip-2-outline.svg +0 -1
  215. package/icons/flip-outline.svg +0 -1
  216. package/icons/folder-add-outline.svg +0 -1
  217. package/icons/folder-outline.svg +0 -1
  218. package/icons/folder-remove-outline.svg +0 -1
  219. package/icons/funnel-outline.svg +0 -1
  220. package/icons/gift-outline.svg +0 -1
  221. package/icons/github-outline.svg +0 -1
  222. package/icons/globe-2-outline.svg +0 -1
  223. package/icons/globe-outline.svg +0 -1
  224. package/icons/google-outline.svg +0 -1
  225. package/icons/grid-outline.svg +0 -1
  226. package/icons/hard-drive-outline.svg +0 -1
  227. package/icons/hash-outline.svg +0 -1
  228. package/icons/headphones-outline.svg +0 -1
  229. package/icons/heart-outline.svg +0 -1
  230. package/icons/home-outline.svg +0 -1
  231. package/icons/image-outline.svg +0 -1
  232. package/icons/inbox-outline.svg +0 -1
  233. package/icons/info-outline.svg +0 -1
  234. package/icons/keypad-outline.svg +0 -1
  235. package/icons/layers-outline.svg +0 -1
  236. package/icons/layout-outline.svg +0 -1
  237. package/icons/link-2-outline.svg +0 -1
  238. package/icons/link-outline.svg +0 -1
  239. package/icons/linkedin-outline.svg +0 -1
  240. package/icons/list-outline.svg +0 -1
  241. package/icons/loader-outline.svg +0 -1
  242. package/icons/lock-outline.svg +0 -1
  243. package/icons/log-in-outline.svg +0 -1
  244. package/icons/log-out-outline.svg +0 -1
  245. package/icons/map-outline.svg +0 -1
  246. package/icons/maximize-outline.svg +0 -1
  247. package/icons/menu-2-outline.svg +0 -1
  248. package/icons/menu-arrow-outline.svg +0 -1
  249. package/icons/menu-outline.svg +0 -1
  250. package/icons/message-circle-outline.svg +0 -1
  251. package/icons/message-square-outline.svg +0 -1
  252. package/icons/mic-off-outline.svg +0 -1
  253. package/icons/mic-outline.svg +0 -1
  254. package/icons/minimize-outline.svg +0 -1
  255. package/icons/minus-circle-outline.svg +0 -1
  256. package/icons/minus-outline.svg +0 -1
  257. package/icons/minus-square-outline.svg +0 -1
  258. package/icons/monitor-outline.svg +0 -1
  259. package/icons/moon-outline.svg +0 -1
  260. package/icons/more-horizontal-outline.svg +0 -1
  261. package/icons/more-vertical-outline.svg +0 -1
  262. package/icons/move-outline.svg +0 -1
  263. package/icons/music-outline.svg +0 -1
  264. package/icons/navigation-2-outline.svg +0 -1
  265. package/icons/navigation-outline.svg +0 -1
  266. package/icons/npm-outline.svg +0 -1
  267. package/icons/options-2-outline.svg +0 -1
  268. package/icons/options-outline.svg +0 -1
  269. package/icons/pantone-outline.svg +0 -1
  270. package/icons/paper-plane-outline.svg +0 -1
  271. package/icons/pause-circle-outline.svg +0 -1
  272. package/icons/people-outline.svg +0 -1
  273. package/icons/percent-outline.svg +0 -1
  274. package/icons/person-add-outline.svg +0 -1
  275. package/icons/person-delete-outline.svg +0 -1
  276. package/icons/person-done-outline.svg +0 -1
  277. package/icons/person-outline.svg +0 -1
  278. package/icons/person-remove-outline.svg +0 -1
  279. package/icons/phone-call-outline.svg +0 -1
  280. package/icons/phone-missed-outline.svg +0 -1
  281. package/icons/phone-off-outline.svg +0 -1
  282. package/icons/phone-outline.svg +0 -1
  283. package/icons/pie-chart-outline.svg +0 -1
  284. package/icons/pin-outline.svg +0 -1
  285. package/icons/play-circle-outline.svg +0 -1
  286. package/icons/plus-circle-outline.svg +0 -1
  287. package/icons/plus-outline.svg +0 -1
  288. package/icons/plus-square-outline.svg +0 -1
  289. package/icons/power-outline.svg +0 -1
  290. package/icons/pricetags-outline.svg +0 -1
  291. package/icons/printer-outline.svg +0 -1
  292. package/icons/question-mark-circle-outline.svg +0 -1
  293. package/icons/question-mark-outline.svg +0 -1
  294. package/icons/radio-button-off-outline.svg +0 -1
  295. package/icons/radio-button-on-outline.svg +0 -1
  296. package/icons/radio-outline.svg +0 -1
  297. package/icons/recording-outline.svg +0 -1
  298. package/icons/refresh-outline.svg +0 -1
  299. package/icons/repeat-outline.svg +0 -1
  300. package/icons/rewind-left-outline.svg +0 -1
  301. package/icons/rewind-right-outline.svg +0 -1
  302. package/icons/save-outline.svg +0 -1
  303. package/icons/scissors-outline.svg +0 -1
  304. package/icons/search-outline.svg +0 -1
  305. package/icons/settings-2-outline.svg +0 -1
  306. package/icons/settings-outline.svg +0 -1
  307. package/icons/shake-outline.svg +0 -1
  308. package/icons/share-outline.svg +0 -1
  309. package/icons/shield-off-outline.svg +0 -1
  310. package/icons/shield-outline.svg +0 -1
  311. package/icons/shopping-bag-outline.svg +0 -1
  312. package/icons/shopping-cart-outline.svg +0 -1
  313. package/icons/shuffle-2-outline.svg +0 -1
  314. package/icons/shuffle-outline.svg +0 -1
  315. package/icons/skip-back-outline.svg +0 -1
  316. package/icons/skip-forward-outline.svg +0 -1
  317. package/icons/slash-outline.svg +0 -1
  318. package/icons/smartphone-outline.svg +0 -1
  319. package/icons/smiling-face-outline.svg +0 -1
  320. package/icons/speaker-outline.svg +0 -1
  321. package/icons/square-outline.svg +0 -1
  322. package/icons/star-outline.svg +0 -1
  323. package/icons/stop-circle-outline.svg +0 -1
  324. package/icons/sun-outline.svg +0 -1
  325. package/icons/swap-outline.svg +0 -1
  326. package/icons/sync-outline.svg +0 -1
  327. package/icons/text-outline.svg +0 -1
  328. package/icons/thermometer-minus-outline.svg +0 -1
  329. package/icons/thermometer-outline.svg +0 -1
  330. package/icons/thermometer-plus-outline.svg +0 -1
  331. package/icons/toggle-left-outline.svg +0 -1
  332. package/icons/toggle-right-outline.svg +0 -1
  333. package/icons/trash-2-outline.svg +0 -1
  334. package/icons/trash-outline.svg +0 -1
  335. package/icons/trending-down-outline.svg +0 -1
  336. package/icons/trending-up-outline.svg +0 -1
  337. package/icons/tv-outline.svg +0 -1
  338. package/icons/twitter-outline.svg +0 -1
  339. package/icons/umbrella-outline.svg +0 -1
  340. package/icons/undo-outline.svg +0 -1
  341. package/icons/unlock-outline.svg +0 -1
  342. package/icons/upload-outline.svg +0 -1
  343. package/icons/video-off-outline.svg +0 -1
  344. package/icons/video-outline.svg +0 -1
  345. package/icons/volume-down-outline.svg +0 -1
  346. package/icons/volume-mute-outline.svg +0 -1
  347. package/icons/volume-off-outline.svg +0 -1
  348. package/icons/volume-up-outline.svg +0 -1
  349. package/icons/wifi-off-outline.svg +0 -1
  350. package/icons/wifi-outline.svg +0 -1
  351. package/js/index.js +0 -718
  352. package/js/index.ts +0 -873
package/js/theme.ts CHANGED
@@ -1,225 +1,235 @@
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
-
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 — SVG sprite via <use> or font icon via class
109
+ const useEl = icon.querySelector('use');
110
+ if (useEl) {
111
+ const iconName = isDark ? icon.dataset.iconDark : icon.dataset.iconLight;
112
+ if (iconName) {
113
+ const existingHref = useEl.getAttribute('href') ?? '';
114
+ const basePath = existingHref.includes('#') ? existingHref.split('#')[0] : '';
115
+ useEl.setAttribute('href', `${basePath}#${iconName}`);
116
+ }
117
+ } else {
118
+ if (isDark) {
119
+ icon.classList.remove('icon-light');
120
+ icon.classList.add('icon-dark');
121
+ } else {
122
+ icon.classList.remove('icon-dark');
123
+ icon.classList.add('icon-light');
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Toggles between light and dark theme
130
+ */
131
+ private static toggleTheme(): void {
132
+ if (!this.elements) return;
133
+
134
+ try {
135
+ const current = this.getCurrentTheme();
136
+ const next: ThemeMode = current === 'dark' ? 'light' : 'dark';
137
+
138
+ this.saveTheme(next);
139
+ this.applyTheme(next);
140
+ } catch (err) {
141
+ console.error('Error toggling theme', err);
142
+ if (this.elements.status) {
143
+ this.elements.status.textContent = 'Error toggling theme (see console).';
144
+ }
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Binds click event to toggle button
150
+ */
151
+ private static bindToggleClick(): void {
152
+ if (!this.elements) return;
153
+
154
+ this.elements.toggleBtn.addEventListener('click', () => {
155
+ this.toggleTheme();
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Binds keyboard shortcut (Ctrl/Cmd+J) for theme toggle
161
+ */
162
+ private static bindKeyboardShortcut(): void {
163
+ window.addEventListener('keydown', (ev: KeyboardEvent) => {
164
+ const isMac = /Mac|iPhone|iPod|iPad/i.test(navigator.platform);
165
+ const modifierPressed = isMac ? ev.metaKey : ev.ctrlKey;
166
+
167
+ if (modifierPressed && ev.key.toLowerCase() === 'j') {
168
+ ev.preventDefault();
169
+ this.toggleTheme();
170
+ }
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Binds listener for system theme changes
176
+ * Only applies if user hasn't explicitly saved a preference
177
+ */
178
+ private static bindSystemThemeChange(): void {
179
+ if (!this.mediaQuery) return;
180
+
181
+ const handler = (e: MediaQueryListEvent | MediaQueryList): void => {
182
+ // Only apply system theme if user hasn't saved a preference
183
+ if (!this.getSavedTheme()) {
184
+ const matches = 'matches' in e ? e.matches : (e as MediaQueryList).matches;
185
+ this.applyTheme(matches ? 'dark' : 'light');
186
+ }
187
+ };
188
+
189
+ // Modern API
190
+ if ('addEventListener' in this.mediaQuery) {
191
+ this.mediaQuery.addEventListener('change', handler as (e: MediaQueryListEvent) => void);
192
+ }
193
+ // Legacy API (deprecated but still supported in older browsers)
194
+ else if ('addListener' in this.mediaQuery) {
195
+ (this.mediaQuery as any).addListener(handler);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Public API: Get the current theme
201
+ */
202
+ public static getTheme(): ThemeMode {
203
+ return this.getCurrentTheme();
204
+ }
205
+
206
+ /**
207
+ * Public API: Set the theme programmatically
208
+ */
209
+ public static setTheme(theme: ThemeMode): void {
210
+ this.saveTheme(theme);
211
+ this.applyTheme(theme);
212
+ }
213
+
214
+ /**
215
+ * Public API: Reset to system preference
216
+ */
217
+ public static resetToSystem(): void {
218
+ try {
219
+ localStorage.removeItem(this.STORAGE_KEY);
220
+ const systemTheme = this.getSystemTheme();
221
+ this.applyTheme(systemTheme);
222
+ } catch (e) {
223
+ console.warn('Failed to reset theme', e);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Public API: Check if user has a saved preference
229
+ */
230
+ public static hasSavedPreference(): boolean {
231
+ return this.getSavedTheme() !== null;
232
+ }
233
+ }
234
+
225
235
  export { Theme };
package/js/timepicker.js CHANGED
@@ -16,6 +16,7 @@ class TimeSpanPicker {
16
16
  this.endTimeInput.value = options.defaultEnd;
17
17
  }
18
18
  this.attachEventListeners();
19
+ // Render initial state if defaults provided
19
20
  if (options?.defaultStart || options?.defaultEnd) {
20
21
  this.updateUI();
21
22
  }
@@ -69,8 +70,10 @@ class TimeSpanPicker {
69
70
  formatDuration(minutes) {
70
71
  const h = Math.floor(minutes / 60);
71
72
  const m = minutes % 60;
72
- if (h && m) return `${h}h ${m}m`;
73
- if (h) return `${h}h`;
73
+ if (h && m)
74
+ return `${h}h ${m}m`;
75
+ if (h)
76
+ return `${h}h`;
74
77
  return `${m}m`;
75
78
  }
76
79
  updateUI() {
@@ -83,7 +86,8 @@ class TimeSpanPicker {
83
86
  picker?.classList.toggle('is-error', isError);
84
87
  if (isError) {
85
88
  this.endTimeInput.setCustomValidity('End time must be after start time');
86
- if (durationEl) durationEl.textContent = '!';
89
+ if (durationEl)
90
+ durationEl.textContent = '!';
87
91
  return;
88
92
  }
89
93
  this.endTimeInput.setCustomValidity('');
@@ -96,9 +100,14 @@ class TimeSpanPicker {
96
100
  const widthPct = ((duration / 1440) * 100).toFixed(2);
97
101
  barFill.style.left = `${startPct}%`;
98
102
  barFill.style.width = `${widthPct}%`;
99
- } else {
100
- if (durationEl) durationEl.textContent = '';
101
- if (barFill) { barFill.style.left = '0'; barFill.style.width = '0'; }
103
+ }
104
+ else {
105
+ if (durationEl)
106
+ durationEl.textContent = '';
107
+ if (barFill) {
108
+ barFill.style.left = '0';
109
+ barFill.style.width = '0';
110
+ }
102
111
  }
103
112
  }
104
113
  handleChange() {
@@ -127,8 +136,12 @@ class TimeSpanPicker {
127
136
  const durationEl = this.container.querySelector('.timespan-duration');
128
137
  const barFill = this.container.querySelector('.timespan-bar-fill');
129
138
  picker?.classList.remove('is-error');
130
- if (durationEl) durationEl.textContent = '';
131
- if (barFill) { barFill.style.left = '0'; barFill.style.width = '0'; }
139
+ if (durationEl)
140
+ durationEl.textContent = '';
141
+ if (barFill) {
142
+ barFill.style.left = '0';
143
+ barFill.style.width = '0';
144
+ }
132
145
  }
133
146
  isValid() {
134
147
  const { start, end } = this.getValue();