@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/scrollbar.ts CHANGED
@@ -1,308 +1,325 @@
1
- interface ScrollbarElements {
2
- viewport: HTMLElement;
3
- content: HTMLElement;
4
- track: HTMLElement;
5
- thumb: HTMLElement;
6
- }
7
-
8
- class Scrollbar {
9
- private static readonly instances = new WeakMap<HTMLElement, Scrollbar>();
10
- private static activeInstance: Scrollbar | null = null;
11
- private static globalListenersInstalled = false;
12
-
13
- private readonly container!: HTMLElement;
14
- private readonly viewport!: HTMLElement;
15
- private readonly content!: HTMLElement;
16
- private readonly track!: HTMLElement;
17
- private readonly thumb!: HTMLElement;
18
- private readonly MIN_THUMB_HEIGHT!: number;
19
- private readonly ro!: ResizeObserver;
20
-
21
- private dragging = false;
22
- private activePointerId: number | null = null;
23
- private startPointerY = 0;
24
- private startThumbTop = 0;
25
-
26
- // Bound methods for event handling
27
- private readonly boundPointerMove!: (e: PointerEvent) => void;
28
- private readonly boundPointerUp!: (e: PointerEvent) => void;
29
- private readonly boundThumbPointerDown!: (e: PointerEvent) => void;
30
- private readonly boundTrackClick!: (e: MouseEvent) => void;
31
- private readonly boundViewportScroll!: () => void;
32
- private readonly boundUpdateThumb!: () => void;
33
-
34
- constructor(elementOrSelector: string | HTMLElement) {
35
- const container = typeof elementOrSelector === 'string'
36
- ? document.querySelector<HTMLElement>(elementOrSelector)
37
- : elementOrSelector;
38
-
39
- if (!container) {
40
- throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
41
- }
42
-
43
- // Return existing instance if already initialized
44
- const existingInstance = Scrollbar.instances.get(container);
45
- if (existingInstance) {
46
- return existingInstance as any;
47
- }
48
-
49
- this.container = container;
50
-
51
- // Query and validate required elements
52
- const elements = this.getRequiredElements(container);
53
- this.viewport = elements.viewport;
54
- this.content = elements.content;
55
- this.track = elements.track;
56
- this.thumb = elements.thumb;
57
-
58
- // Get minimum thumb height from CSS variable or use default
59
- this.MIN_THUMB_HEIGHT = this.getMinThumbHeight();
60
-
61
- // Bind all event handlers once
62
- this.boundPointerMove = this.handlePointerMove.bind(this);
63
- this.boundPointerUp = this.handlePointerUp.bind(this);
64
- this.boundThumbPointerDown = this.handleThumbPointerDown.bind(this);
65
- this.boundTrackClick = this.handleTrackClick.bind(this);
66
- this.boundViewportScroll = this.updateThumb.bind(this);
67
- this.boundUpdateThumb = this.updateThumb.bind(this);
68
-
69
- // Setup ResizeObserver
70
- this.ro = new ResizeObserver(this.boundUpdateThumb);
71
-
72
- // Initialize
73
- this.attachEventListeners();
74
- Scrollbar.instances.set(container, this);
75
-
76
- // Install global listeners once for all instances
77
- if (!Scrollbar.globalListenersInstalled) {
78
- Scrollbar.installGlobalListeners();
79
- }
80
-
81
- // Initial thumb update
82
- requestAnimationFrame(this.boundUpdateThumb);
83
- }
84
-
85
- private getRequiredElements(container: HTMLElement): ScrollbarElements {
86
- const viewport = container.querySelector<HTMLElement>('.viewport');
87
- const content = container.querySelector<HTMLElement>('.content');
88
- const track = container.querySelector<HTMLElement>('.track');
89
- const thumb = container.querySelector<HTMLElement>('.thumb');
90
-
91
- if (!viewport || !content || !track || !thumb) {
92
- throw new Error(
93
- 'Required scrollbar elements not found. Expected: .viewport, .content, .track, .thumb'
94
- );
95
- }
96
-
97
- return { viewport, content, track, thumb };
98
- }
99
-
100
- private getMinThumbHeight(): number {
101
- const cssValue = getComputedStyle(document.documentElement)
102
- .getPropertyValue('--thumb-min')
103
- .trim();
104
-
105
- const parsed = parseInt(cssValue, 10);
106
- const defaultMin = 28;
107
- const absoluteMin = 16;
108
-
109
- return Math.max(absoluteMin, parsed || defaultMin);
110
- }
111
-
112
- private static installGlobalListeners(): void {
113
- // Route pointer events to the active scrollbar instance
114
- document.addEventListener('pointermove', (e: PointerEvent) => {
115
- Scrollbar.activeInstance?.boundPointerMove(e);
116
- }, { passive: false });
117
-
118
- document.addEventListener('pointerup', (e: PointerEvent) => {
119
- Scrollbar.activeInstance?.boundPointerUp(e);
120
- });
121
-
122
- document.addEventListener('pointercancel', (e: PointerEvent) => {
123
- Scrollbar.activeInstance?.boundPointerUp(e);
124
- });
125
-
126
- Scrollbar.globalListenersInstalled = true;
127
- }
128
-
129
- private attachEventListeners(): void {
130
- // Instance-specific events
131
- this.viewport.addEventListener('scroll', this.boundViewportScroll, { passive: true });
132
- this.thumb.addEventListener('pointerdown', this.boundThumbPointerDown);
133
- this.track.addEventListener('click', this.boundTrackClick);
134
-
135
- // Observe size changes
136
- this.ro.observe(this.viewport);
137
- this.ro.observe(this.content);
138
- window.addEventListener('resize', this.boundUpdateThumb);
139
- }
140
-
141
- private updateThumb(): void {
142
- const viewportHeight = this.viewport.clientHeight;
143
- const contentHeight = this.content.scrollHeight;
144
- const trackHeight = this.track.clientHeight;
145
-
146
- // Hide thumb if content fits in viewport
147
- if (contentHeight <= viewportHeight + 1) {
148
- this.thumb.style.display = 'none';
149
- return;
150
- }
151
-
152
- this.thumb.style.display = '';
153
-
154
- // Calculate thumb size
155
- const ratio = viewportHeight / contentHeight;
156
- const thumbHeight = Math.max(
157
- Math.floor(ratio * trackHeight),
158
- this.MIN_THUMB_HEIGHT
159
- );
160
- this.thumb.style.height = `${thumbHeight}px`;
161
-
162
- // Calculate thumb position
163
- const maxScroll = contentHeight - viewportHeight;
164
- const maxThumbTop = trackHeight - thumbHeight;
165
- const scrollRatio = this.viewport.scrollTop / (maxScroll || 1);
166
- const thumbTop = scrollRatio * (maxThumbTop || 0);
167
-
168
- this.thumb.style.top = `${thumbTop}px`;
169
- }
170
-
171
- private handleThumbPointerDown(e: PointerEvent): void {
172
- e.preventDefault();
173
-
174
- this.dragging = true;
175
- this.activePointerId = e.pointerId;
176
- Scrollbar.activeInstance = this;
177
-
178
- // Capture pointer for reliable tracking
179
- try {
180
- this.thumb.setPointerCapture(e.pointerId);
181
- } catch (err) {
182
- console.warn('Failed to capture pointer:', err);
183
- }
184
-
185
- this.startPointerY = e.clientY;
186
-
187
- const thumbRect = this.thumb.getBoundingClientRect();
188
- const trackRect = this.track.getBoundingClientRect();
189
- this.startThumbTop = thumbRect.top - trackRect.top;
190
-
191
- // Prevent text selection during drag
192
- document.body.style.userSelect = 'none';
193
- }
194
-
195
- private handlePointerMove(e: PointerEvent): void {
196
- // Only handle events for the active pointer
197
- if (!this.dragging || this.activePointerId !== e.pointerId) {
198
- return;
199
- }
200
-
201
- e.preventDefault();
202
-
203
- const pointerDelta = e.clientY - this.startPointerY;
204
- const trackHeight = this.track.clientHeight;
205
- const thumbHeight = this.thumb.clientHeight;
206
- const maxThumbTop = trackHeight - thumbHeight;
207
-
208
- // Calculate new thumb position
209
- const newThumbTop = Math.max(
210
- 0,
211
- Math.min(maxThumbTop, this.startThumbTop + pointerDelta)
212
- );
213
- this.thumb.style.top = `${newThumbTop}px`;
214
-
215
- // Update viewport scroll position
216
- const contentHeight = this.content.scrollHeight;
217
- const viewportHeight = this.viewport.clientHeight;
218
- const maxScroll = contentHeight - viewportHeight;
219
- const scrollRatio = newThumbTop / (maxThumbTop || 1);
220
-
221
- this.viewport.scrollTop = scrollRatio * (maxScroll || 0);
222
- }
223
-
224
- private handlePointerUp(e: PointerEvent): void {
225
- if (!this.dragging || this.activePointerId !== e.pointerId) {
226
- return;
227
- }
228
-
229
- this.dragging = false;
230
-
231
- // Release pointer capture
232
- try {
233
- this.thumb.releasePointerCapture(e.pointerId);
234
- } catch (err) {
235
- console.warn('Failed to release pointer:', err);
236
- }
237
-
238
- this.activePointerId = null;
239
- Scrollbar.activeInstance = null;
240
- document.body.style.userSelect = '';
241
- }
242
-
243
- private handleTrackClick(e: MouseEvent): void {
244
- // Ignore clicks directly on the thumb
245
- if (e.target === this.thumb) {
246
- return;
247
- }
248
-
249
- const trackRect = this.track.getBoundingClientRect();
250
- const clickY = e.clientY - trackRect.top;
251
- const thumbHeight = this.thumb.clientHeight;
252
- const trackHeight = this.track.clientHeight;
253
-
254
- // Center thumb on click position
255
- const targetThumbTop = clickY - thumbHeight / 2;
256
- const maxThumbTop = trackHeight - thumbHeight;
257
- const clampedThumbTop = Math.max(0, Math.min(maxThumbTop, targetThumbTop));
258
-
259
- // Calculate corresponding scroll position
260
- const contentHeight = this.content.scrollHeight;
261
- const viewportHeight = this.viewport.clientHeight;
262
- const maxScroll = contentHeight - viewportHeight;
263
- const scrollRatio = clampedThumbTop / (maxThumbTop || 1);
264
- const scrollTop = scrollRatio * (maxScroll || 0);
265
-
266
- this.viewport.scrollTo({ top: scrollTop, behavior: 'smooth' });
267
- }
268
-
269
- public destroy(): void {
270
- // Remove event listeners
271
- this.viewport.removeEventListener('scroll', this.boundViewportScroll);
272
- this.thumb.removeEventListener('pointerdown', this.boundThumbPointerDown);
273
- this.track.removeEventListener('click', this.boundTrackClick);
274
- window.removeEventListener('resize', this.boundUpdateThumb);
275
-
276
- // Disconnect observer
277
- this.ro.disconnect();
278
-
279
- // Clear from instances map
280
- Scrollbar.instances.delete(this.container);
281
-
282
- // Clear active instance if this was it
283
- if (Scrollbar.activeInstance === this) {
284
- Scrollbar.activeInstance = null;
285
- }
286
- }
287
-
288
- // Static factory methods
289
- public static initAll(selector: string): Scrollbar[] {
290
- const containers = document.querySelectorAll<HTMLElement>(selector);
291
- return Array.from(containers).map(container => new Scrollbar(container));
292
- }
293
-
294
- public static initOne(elementOrSelector: string | HTMLElement): Scrollbar {
295
- return new Scrollbar(elementOrSelector);
296
- }
297
-
298
- public static getInstance(container: HTMLElement): Scrollbar | undefined {
299
- return Scrollbar.instances.get(container);
300
- }
301
-
302
- public static destroyAll(): void {
303
- // Note: WeakMap doesn't support iteration, so this is a no-op
304
- // Individual instances should be destroyed by calling destroy()
305
- }
306
- }
307
-
1
+ interface ScrollbarElements {
2
+ viewport: HTMLElement;
3
+ content: HTMLElement;
4
+ track: HTMLElement;
5
+ thumb: HTMLElement;
6
+ }
7
+
8
+ class Scrollbar {
9
+ private static readonly instances = new WeakMap<HTMLElement, Scrollbar>();
10
+ private static activeInstance: Scrollbar | null = null;
11
+ private static globalListenersInstalled = false;
12
+
13
+ private readonly container!: HTMLElement;
14
+ private readonly viewport!: HTMLElement;
15
+ private readonly content!: HTMLElement;
16
+ private readonly track!: HTMLElement;
17
+ private readonly thumb!: HTMLElement;
18
+ private readonly MIN_THUMB_HEIGHT!: number;
19
+ private readonly ro!: ResizeObserver;
20
+
21
+ private dragging = false;
22
+ private activePointerId: number | null = null;
23
+ private startPointerY = 0;
24
+ private startThumbTop = 0;
25
+
26
+ // Bound methods for event handling
27
+ private readonly boundPointerMove!: (e: PointerEvent) => void;
28
+ private readonly boundPointerUp!: (e: PointerEvent) => void;
29
+ private readonly boundThumbPointerDown!: (e: PointerEvent) => void;
30
+ private readonly boundTrackClick!: (e: MouseEvent) => void;
31
+ private readonly boundViewportScroll!: () => void;
32
+ private readonly boundUpdateThumb!: () => void;
33
+ private readonly boundContainerWheel!: (e: WheelEvent) => void;
34
+
35
+ constructor(elementOrSelector: string | HTMLElement) {
36
+ const container = typeof elementOrSelector === 'string'
37
+ ? document.querySelector<HTMLElement>(elementOrSelector)
38
+ : elementOrSelector;
39
+
40
+ if (!container) {
41
+ throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
42
+ }
43
+
44
+ // Return existing instance if already initialized
45
+ const existingInstance = Scrollbar.instances.get(container);
46
+ if (existingInstance) {
47
+ return existingInstance as any;
48
+ }
49
+
50
+ this.container = container;
51
+
52
+ // Query and validate required elements
53
+ const elements = this.getRequiredElements(container);
54
+ this.viewport = elements.viewport;
55
+ this.content = elements.content;
56
+ this.track = elements.track;
57
+ this.thumb = elements.thumb;
58
+
59
+ // Get minimum thumb height from CSS variable or use default
60
+ this.MIN_THUMB_HEIGHT = this.getMinThumbHeight();
61
+
62
+ // Bind all event handlers once
63
+ this.boundPointerMove = this.handlePointerMove.bind(this);
64
+ this.boundPointerUp = this.handlePointerUp.bind(this);
65
+ this.boundThumbPointerDown = this.handleThumbPointerDown.bind(this);
66
+ this.boundTrackClick = this.handleTrackClick.bind(this);
67
+ this.boundViewportScroll = this.updateThumb.bind(this);
68
+ this.boundUpdateThumb = this.updateThumb.bind(this);
69
+ this.boundContainerWheel = this.handleContainerWheel.bind(this);
70
+
71
+ // Setup ResizeObserver
72
+ this.ro = new ResizeObserver(this.boundUpdateThumb);
73
+
74
+ // Initialize
75
+ this.attachEventListeners();
76
+ Scrollbar.instances.set(container, this);
77
+
78
+ // Install global listeners once for all instances
79
+ if (!Scrollbar.globalListenersInstalled) {
80
+ Scrollbar.installGlobalListeners();
81
+ }
82
+
83
+ // Initial thumb update
84
+ requestAnimationFrame(this.boundUpdateThumb);
85
+ }
86
+
87
+ private getRequiredElements(container: HTMLElement): ScrollbarElements {
88
+ const viewport = container.querySelector<HTMLElement>('.viewport');
89
+ const content = container.querySelector<HTMLElement>('.content');
90
+ const track = container.querySelector<HTMLElement>('.track');
91
+ const thumb = container.querySelector<HTMLElement>('.thumb');
92
+
93
+ if (!viewport || !content || !track || !thumb) {
94
+ throw new Error(
95
+ 'Required scrollbar elements not found. Expected: .viewport, .content, .track, .thumb'
96
+ );
97
+ }
98
+
99
+ return { viewport, content, track, thumb };
100
+ }
101
+
102
+ private getMinThumbHeight(): number {
103
+ const cssValue = getComputedStyle(document.documentElement)
104
+ .getPropertyValue('--thumb-min')
105
+ .trim();
106
+
107
+ const parsed = parseInt(cssValue, 10);
108
+ const defaultMin = 28;
109
+ const absoluteMin = 16;
110
+
111
+ return Math.max(absoluteMin, parsed || defaultMin);
112
+ }
113
+
114
+ private static installGlobalListeners(): void {
115
+ // Route pointer events to the active scrollbar instance
116
+ document.addEventListener('pointermove', (e: PointerEvent) => {
117
+ Scrollbar.activeInstance?.boundPointerMove(e);
118
+ }, { passive: false });
119
+
120
+ document.addEventListener('pointerup', (e: PointerEvent) => {
121
+ Scrollbar.activeInstance?.boundPointerUp(e);
122
+ });
123
+
124
+ document.addEventListener('pointercancel', (e: PointerEvent) => {
125
+ Scrollbar.activeInstance?.boundPointerUp(e);
126
+ });
127
+
128
+ Scrollbar.globalListenersInstalled = true;
129
+ }
130
+
131
+ private attachEventListeners(): void {
132
+ // Instance-specific events
133
+ this.viewport.addEventListener('scroll', this.boundViewportScroll, { passive: true });
134
+ this.thumb.addEventListener('pointerdown', this.boundThumbPointerDown);
135
+ this.track.addEventListener('click', this.boundTrackClick);
136
+ this.container.addEventListener('wheel', this.boundContainerWheel, { passive: false });
137
+
138
+ // Observe size changes
139
+ this.ro.observe(this.viewport);
140
+ this.ro.observe(this.content);
141
+ window.addEventListener('resize', this.boundUpdateThumb);
142
+ }
143
+
144
+ private updateThumb(): void {
145
+ const viewportHeight = this.viewport.clientHeight;
146
+ const contentHeight = this.content.scrollHeight;
147
+ const trackHeight = this.track.clientHeight;
148
+
149
+ // Hide thumb if content fits in viewport
150
+ if (contentHeight <= viewportHeight + 1) {
151
+ this.thumb.style.display = 'none';
152
+ return;
153
+ }
154
+
155
+ this.thumb.style.display = '';
156
+
157
+ // Calculate thumb size
158
+ const ratio = viewportHeight / contentHeight;
159
+ const thumbHeight = Math.max(
160
+ Math.floor(ratio * trackHeight),
161
+ this.MIN_THUMB_HEIGHT
162
+ );
163
+ this.thumb.style.height = `${thumbHeight}px`;
164
+
165
+ // Calculate thumb position
166
+ const maxScroll = contentHeight - viewportHeight;
167
+ const maxThumbTop = trackHeight - thumbHeight;
168
+ const scrollRatio = this.viewport.scrollTop / (maxScroll || 1);
169
+ const thumbTop = scrollRatio * (maxThumbTop || 0);
170
+
171
+ this.thumb.style.top = `${thumbTop}px`;
172
+ }
173
+
174
+ private handleThumbPointerDown(e: PointerEvent): void {
175
+ e.preventDefault();
176
+
177
+ this.dragging = true;
178
+ this.activePointerId = e.pointerId;
179
+ Scrollbar.activeInstance = this;
180
+
181
+ // Capture pointer for reliable tracking
182
+ try {
183
+ this.thumb.setPointerCapture(e.pointerId);
184
+ } catch (err) {
185
+ console.warn('Failed to capture pointer:', err);
186
+ }
187
+
188
+ this.startPointerY = e.clientY;
189
+
190
+ const thumbRect = this.thumb.getBoundingClientRect();
191
+ const trackRect = this.track.getBoundingClientRect();
192
+ this.startThumbTop = thumbRect.top - trackRect.top;
193
+
194
+ // Prevent text selection during drag
195
+ document.body.style.userSelect = 'none';
196
+ }
197
+
198
+ private handlePointerMove(e: PointerEvent): void {
199
+ // Only handle events for the active pointer
200
+ if (!this.dragging || this.activePointerId !== e.pointerId) {
201
+ return;
202
+ }
203
+
204
+ e.preventDefault();
205
+
206
+ const pointerDelta = e.clientY - this.startPointerY;
207
+ const trackHeight = this.track.clientHeight;
208
+ const thumbHeight = this.thumb.clientHeight;
209
+ const maxThumbTop = trackHeight - thumbHeight;
210
+
211
+ // Calculate new thumb position
212
+ const newThumbTop = Math.max(
213
+ 0,
214
+ Math.min(maxThumbTop, this.startThumbTop + pointerDelta)
215
+ );
216
+ this.thumb.style.top = `${newThumbTop}px`;
217
+
218
+ // Update viewport scroll position
219
+ const contentHeight = this.content.scrollHeight;
220
+ const viewportHeight = this.viewport.clientHeight;
221
+ const maxScroll = contentHeight - viewportHeight;
222
+ const scrollRatio = newThumbTop / (maxThumbTop || 1);
223
+
224
+ this.viewport.scrollTop = scrollRatio * (maxScroll || 0);
225
+ }
226
+
227
+ private handlePointerUp(e: PointerEvent): void {
228
+ if (!this.dragging || this.activePointerId !== e.pointerId) {
229
+ return;
230
+ }
231
+
232
+ this.dragging = false;
233
+
234
+ // Release pointer capture
235
+ try {
236
+ this.thumb.releasePointerCapture(e.pointerId);
237
+ } catch (err) {
238
+ console.warn('Failed to release pointer:', err);
239
+ }
240
+
241
+ this.activePointerId = null;
242
+ Scrollbar.activeInstance = null;
243
+ document.body.style.userSelect = '';
244
+ }
245
+
246
+ private handleTrackClick(e: MouseEvent): void {
247
+ // Ignore clicks directly on the thumb
248
+ if (e.target === this.thumb) {
249
+ return;
250
+ }
251
+
252
+ const trackRect = this.track.getBoundingClientRect();
253
+ const clickY = e.clientY - trackRect.top;
254
+ const thumbHeight = this.thumb.clientHeight;
255
+ const trackHeight = this.track.clientHeight;
256
+
257
+ // Center thumb on click position
258
+ const targetThumbTop = clickY - thumbHeight / 2;
259
+ const maxThumbTop = trackHeight - thumbHeight;
260
+ const clampedThumbTop = Math.max(0, Math.min(maxThumbTop, targetThumbTop));
261
+
262
+ // Calculate corresponding scroll position
263
+ const contentHeight = this.content.scrollHeight;
264
+ const viewportHeight = this.viewport.clientHeight;
265
+ const maxScroll = contentHeight - viewportHeight;
266
+ const scrollRatio = clampedThumbTop / (maxThumbTop || 1);
267
+ const scrollTop = scrollRatio * (maxScroll || 0);
268
+
269
+ this.viewport.scrollTo({ top: scrollTop, behavior: 'smooth' });
270
+ }
271
+
272
+ private handleContainerWheel(e: WheelEvent): void {
273
+ const { scrollTop, scrollHeight, clientHeight } = this.viewport;
274
+ const scrollable = scrollHeight > clientHeight;
275
+ const atTop = scrollTop === 0 && e.deltaY < 0;
276
+ const atBottom = scrollTop + clientHeight >= scrollHeight - 1 && e.deltaY > 0;
277
+
278
+ if (scrollable && !atTop && !atBottom) {
279
+ e.preventDefault();
280
+ }
281
+
282
+ this.viewport.scrollTop += e.deltaY;
283
+ }
284
+
285
+ public destroy(): void {
286
+ // Remove event listeners
287
+ this.viewport.removeEventListener('scroll', this.boundViewportScroll);
288
+ this.thumb.removeEventListener('pointerdown', this.boundThumbPointerDown);
289
+ this.track.removeEventListener('click', this.boundTrackClick);
290
+ this.container.removeEventListener('wheel', this.boundContainerWheel);
291
+ window.removeEventListener('resize', this.boundUpdateThumb);
292
+
293
+ // Disconnect observer
294
+ this.ro.disconnect();
295
+
296
+ // Clear from instances map
297
+ Scrollbar.instances.delete(this.container);
298
+
299
+ // Clear active instance if this was it
300
+ if (Scrollbar.activeInstance === this) {
301
+ Scrollbar.activeInstance = null;
302
+ }
303
+ }
304
+
305
+ // Static factory methods
306
+ public static initAll(selector: string): Scrollbar[] {
307
+ const containers = document.querySelectorAll<HTMLElement>(selector);
308
+ return Array.from(containers).map(container => new Scrollbar(container));
309
+ }
310
+
311
+ public static initOne(elementOrSelector: string | HTMLElement): Scrollbar {
312
+ return new Scrollbar(elementOrSelector);
313
+ }
314
+
315
+ public static getInstance(container: HTMLElement): Scrollbar | undefined {
316
+ return Scrollbar.instances.get(container);
317
+ }
318
+
319
+ public static destroyAll(): void {
320
+ // Note: WeakMap doesn't support iteration, so this is a no-op
321
+ // Individual instances should be destroyed by calling destroy()
322
+ }
323
+ }
324
+
308
325
  export { Scrollbar, ScrollbarElements };