@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.
- package/README.md +482 -0
- package/css/accordion.css +109 -0
- package/css/accordion.css.map +1 -0
- package/css/accordion.scss +78 -0
- package/css/alert.css +57 -0
- package/css/alert.css.map +1 -0
- package/css/alert.scss +86 -0
- package/css/button.css +69 -0
- package/css/button.css.map +1 -0
- package/css/button.scss +102 -0
- package/css/card.css +144 -0
- package/css/card.css.map +1 -0
- package/css/card.scss +66 -0
- package/css/carousel.css +118 -0
- package/css/carousel.css.map +1 -0
- package/css/carousel.scss +87 -0
- package/css/chart.css +159 -0
- package/css/chart.css.map +1 -0
- package/css/chart.scss +159 -0
- package/css/chat-bubbles.css +97 -0
- package/css/chat-bubbles.css.map +1 -0
- package/css/chat-bubbles.scss +68 -0
- package/css/checkbox.css +77 -0
- package/css/checkbox.css.map +1 -0
- package/css/checkbox.scss +55 -0
- package/css/chips.css +72 -0
- package/css/chips.css.map +1 -0
- package/css/chips.scss +52 -0
- package/css/code-viewer.css +97 -0
- package/css/code-viewer.css.map +1 -0
- package/css/code-viewer.scss +98 -0
- package/css/colors.css +63 -0
- package/css/colors.css.map +1 -0
- package/css/colors.scss +33 -0
- package/css/datepicker.css +264 -0
- package/css/datepicker.css.map +1 -0
- package/css/datepicker.scss +317 -0
- package/css/defaults.css +118 -0
- package/css/defaults.css.map +1 -0
- package/css/defaults.scss +91 -0
- package/css/dropdown.css +146 -0
- package/css/dropdown.css.map +1 -0
- package/css/dropdown.scss +137 -0
- package/css/editor.css +413 -0
- package/css/editor.scss +458 -0
- package/css/file-uploader.css +194 -0
- package/css/file-uploader.css.map +1 -0
- package/css/file-uploader.scss +195 -0
- package/css/flyout-menu.css +345 -0
- package/css/flyout-menu.css.map +1 -0
- package/css/flyout-menu.scss +355 -0
- package/css/form-builder.css +9 -0
- package/css/form-builder.css.map +1 -0
- package/css/form-builder.scss +11 -0
- package/css/form.css +130 -0
- package/css/form.css.map +1 -0
- package/css/form.scss +115 -0
- package/css/gallery.css +91 -0
- package/css/gallery.css.map +1 -0
- package/css/gallery.scss +63 -0
- package/css/grid.css +44 -0
- package/css/grid.css.map +1 -0
- package/css/grid.scss +41 -0
- package/css/guitar-chords.css +251 -0
- package/css/icons.css +327 -0
- package/css/icons.css.map +1 -0
- package/css/icons.scss +331 -0
- package/css/modal.css +97 -0
- package/css/modal.css.map +1 -0
- package/css/modal.scss +72 -0
- package/css/parameters.css +1 -0
- package/css/parameters.css.map +1 -0
- package/css/parameters.scss +4 -0
- package/css/placeholder.css +50 -0
- package/css/placeholder.css.map +1 -0
- package/css/placeholder.scss +28 -0
- package/css/progress.css +51 -0
- package/css/progress.css.map +1 -0
- package/css/progress.scss +32 -0
- package/css/properties.css +31 -0
- package/css/properties.css.map +1 -0
- package/css/properties.scss +31 -0
- package/css/push-menu.css +145 -0
- package/css/push-menu.css.map +1 -0
- package/css/push-menu.scss +127 -0
- package/css/radiobutton.css +91 -0
- package/css/radiobutton.css.map +1 -0
- package/css/radiobutton.scss +64 -0
- package/css/reset.css +46 -0
- package/css/reset.css.map +1 -0
- package/css/reset.scss +40 -0
- package/css/scrollbar.css +91 -0
- package/css/scrollbar.css.map +1 -0
- package/css/scrollbar.scss +69 -0
- package/css/spinner.css +118 -0
- package/css/spinner.css.map +1 -0
- package/css/spinner.scss +91 -0
- package/css/style.css +3735 -0
- package/css/style.css.map +1 -0
- package/css/style.min.css +1 -0
- package/css/style.scss +38 -0
- package/css/switch.css +66 -0
- package/css/switch.css.map +1 -0
- package/css/switch.scss +42 -0
- package/css/table.css +201 -0
- package/css/table.css.map +1 -0
- package/css/table.scss +178 -0
- package/css/tabs.css +135 -0
- package/css/tabs.css.map +1 -0
- package/css/tabs.scss +118 -0
- package/css/timeline.css +69 -0
- package/css/timeline.css.map +1 -0
- package/css/timeline.scss +69 -0
- package/css/timepicker.scss +72 -0
- package/css/toast.css +98 -0
- package/css/toast.css.map +1 -0
- package/css/toast.scss +81 -0
- package/css/tooltip.css +151 -0
- package/css/tooltip.css.map +1 -0
- package/css/tooltip.scss +122 -0
- package/css/tree.css +199 -0
- package/css/tree.css.map +1 -0
- package/css/tree.scss +192 -0
- package/css/typography.css +137 -0
- package/css/typography.css.map +1 -0
- package/css/typography.scss +100 -0
- package/css/virtual-dropdown.css +149 -0
- package/css/virtual-dropdown.css.map +1 -0
- package/css/virtual-dropdown.scss +142 -0
- package/fonts/MaterialSymbolsOutlined.woff2 +0 -0
- package/fonts/Outfit-VariableFont_wght.woff +0 -0
- package/fonts/Outfit-VariableFont_wght.woff2 +0 -0
- package/fonts/material-icons.woff2 +0 -0
- package/icons/activity-outline.svg +1 -0
- package/icons/alert-circle-outline.svg +1 -0
- package/icons/alert-triangle-outline.svg +1 -0
- package/icons/archive-outline.svg +1 -0
- package/icons/arrow-back-outline.svg +1 -0
- package/icons/arrow-circle-down-outline.svg +1 -0
- package/icons/arrow-circle-left-outline.svg +1 -0
- package/icons/arrow-circle-right-outline.svg +1 -0
- package/icons/arrow-circle-up-outline.svg +1 -0
- package/icons/arrow-down-outline.svg +1 -0
- package/icons/arrow-downward-outline.svg +1 -0
- package/icons/arrow-forward-outline.svg +1 -0
- package/icons/arrow-ios-back-outline.svg +1 -0
- package/icons/arrow-ios-downward-outline.svg +1 -0
- package/icons/arrow-ios-forward-outline.svg +1 -0
- package/icons/arrow-ios-upward-outline.svg +1 -0
- package/icons/arrow-left-outline.svg +1 -0
- package/icons/arrow-right-outline.svg +1 -0
- package/icons/arrow-up-outline.svg +1 -0
- package/icons/arrow-upward-outline.svg +1 -0
- package/icons/arrowhead-down-outline.svg +1 -0
- package/icons/arrowhead-left-outline.svg +1 -0
- package/icons/arrowhead-right-outline.svg +1 -0
- package/icons/arrowhead-up-outline.svg +1 -0
- package/icons/at-outline.svg +1 -0
- package/icons/attach-2-outline.svg +1 -0
- package/icons/attach-outline.svg +1 -0
- package/icons/award-outline.svg +1 -0
- package/icons/backspace-outline.svg +1 -0
- package/icons/bar-chart-2-outline.svg +1 -0
- package/icons/bar-chart-outline.svg +1 -0
- package/icons/battery-outline.svg +1 -0
- package/icons/behance-outline.svg +1 -0
- package/icons/bell-off-outline.svg +1 -0
- package/icons/bell-outline.svg +1 -0
- package/icons/bluetooth-outline.svg +1 -0
- package/icons/book-open-outline.svg +1 -0
- package/icons/book-outline.svg +1 -0
- package/icons/bookmark-outline.svg +1 -0
- package/icons/briefcase-outline.svg +1 -0
- package/icons/browser-outline.svg +1 -0
- package/icons/brush-outline.svg +1 -0
- package/icons/bulb-outline.svg +1 -0
- package/icons/calendar-outline.svg +1 -0
- package/icons/camera-outline.svg +1 -0
- package/icons/car-outline.svg +1 -0
- package/icons/cast-outline.svg +1 -0
- package/icons/charging-outline.svg +1 -0
- package/icons/checkmark-circle-2-outline.svg +1 -0
- package/icons/checkmark-circle-outline.svg +1 -0
- package/icons/checkmark-outline.svg +1 -0
- package/icons/checkmark-square-2-outline.svg +1 -0
- package/icons/checkmark-square-outline.svg +1 -0
- package/icons/chevron-down-outline.svg +1 -0
- package/icons/chevron-left-outline.svg +1 -0
- package/icons/chevron-right-outline.svg +1 -0
- package/icons/chevron-up-outline.svg +1 -0
- package/icons/clipboard-outline.svg +1 -0
- package/icons/clock-outline.svg +1 -0
- package/icons/close-circle-outline.svg +1 -0
- package/icons/close-outline.svg +1 -0
- package/icons/close-square-outline.svg +1 -0
- package/icons/cloud-download-outline.svg +1 -0
- package/icons/cloud-upload-outline.svg +1 -0
- package/icons/code-download-outline.svg +1 -0
- package/icons/code-outline.svg +1 -0
- package/icons/collapse-outline.svg +1 -0
- package/icons/color-palette-outline.svg +1 -0
- package/icons/color-picker-outline.svg +1 -0
- package/icons/compass-outline.svg +1 -0
- package/icons/copy-outline.svg +1 -0
- package/icons/corner-down-left-outline.svg +1 -0
- package/icons/corner-down-right-outline.svg +1 -0
- package/icons/corner-left-down-outline.svg +1 -0
- package/icons/corner-left-up-outline.svg +1 -0
- package/icons/corner-right-down-outline.svg +1 -0
- package/icons/corner-right-up-outline.svg +1 -0
- package/icons/corner-up-left-outline.svg +1 -0
- package/icons/corner-up-right-outline.svg +1 -0
- package/icons/credit-card-outline.svg +1 -0
- package/icons/crop-outline.svg +1 -0
- package/icons/cube-outline.svg +1 -0
- package/icons/diagonal-arrow-left-down-outline.svg +1 -0
- package/icons/diagonal-arrow-left-up-outline.svg +1 -0
- package/icons/diagonal-arrow-right-down-outline.svg +1 -0
- package/icons/diagonal-arrow-right-up-outline.svg +1 -0
- package/icons/done-all-outline.svg +1 -0
- package/icons/download-outline.svg +1 -0
- package/icons/droplet-off-outline.svg +1 -0
- package/icons/droplet-outline.svg +1 -0
- package/icons/edit-2-outline.svg +1 -0
- package/icons/edit-outline.svg +1 -0
- package/icons/email-outline.svg +1 -0
- package/icons/expand-outline.svg +1 -0
- package/icons/external-link-outline.svg +1 -0
- package/icons/eye-off-2-outline.svg +1 -0
- package/icons/eye-off-outline.svg +1 -0
- package/icons/eye-outline.svg +1 -0
- package/icons/facebook-outline.svg +1 -0
- package/icons/file-add-outline.svg +1 -0
- package/icons/file-outline.svg +1 -0
- package/icons/file-remove-outline.svg +1 -0
- package/icons/file-text-outline.svg +1 -0
- package/icons/film-outline.svg +1 -0
- package/icons/flag-outline.svg +1 -0
- package/icons/flash-off-outline.svg +1 -0
- package/icons/flash-outline.svg +1 -0
- package/icons/flip-2-outline.svg +1 -0
- package/icons/flip-outline.svg +1 -0
- package/icons/folder-add-outline.svg +1 -0
- package/icons/folder-outline.svg +1 -0
- package/icons/folder-remove-outline.svg +1 -0
- package/icons/funnel-outline.svg +1 -0
- package/icons/gift-outline.svg +1 -0
- package/icons/github-outline.svg +1 -0
- package/icons/globe-2-outline.svg +1 -0
- package/icons/globe-outline.svg +1 -0
- package/icons/google-outline.svg +1 -0
- package/icons/grid-outline.svg +1 -0
- package/icons/hard-drive-outline.svg +1 -0
- package/icons/hash-outline.svg +1 -0
- package/icons/headphones-outline.svg +1 -0
- package/icons/heart-outline.svg +1 -0
- package/icons/home-outline.svg +1 -0
- package/icons/image-outline.svg +1 -0
- package/icons/inbox-outline.svg +1 -0
- package/icons/info-outline.svg +1 -0
- package/icons/keypad-outline.svg +1 -0
- package/icons/layers-outline.svg +1 -0
- package/icons/layout-outline.svg +1 -0
- package/icons/link-2-outline.svg +1 -0
- package/icons/link-outline.svg +1 -0
- package/icons/linkedin-outline.svg +1 -0
- package/icons/list-outline.svg +1 -0
- package/icons/loader-outline.svg +1 -0
- package/icons/lock-outline.svg +1 -0
- package/icons/log-in-outline.svg +1 -0
- package/icons/log-out-outline.svg +1 -0
- package/icons/map-outline.svg +1 -0
- package/icons/maximize-outline.svg +1 -0
- package/icons/menu-2-outline.svg +1 -0
- package/icons/menu-arrow-outline.svg +1 -0
- package/icons/menu-outline.svg +1 -0
- package/icons/message-circle-outline.svg +1 -0
- package/icons/message-square-outline.svg +1 -0
- package/icons/mic-off-outline.svg +1 -0
- package/icons/mic-outline.svg +1 -0
- package/icons/minimize-outline.svg +1 -0
- package/icons/minus-circle-outline.svg +1 -0
- package/icons/minus-outline.svg +1 -0
- package/icons/minus-square-outline.svg +1 -0
- package/icons/monitor-outline.svg +1 -0
- package/icons/moon-outline.svg +1 -0
- package/icons/more-horizontal-outline.svg +1 -0
- package/icons/more-vertical-outline.svg +1 -0
- package/icons/move-outline.svg +1 -0
- package/icons/music-outline.svg +1 -0
- package/icons/navigation-2-outline.svg +1 -0
- package/icons/navigation-outline.svg +1 -0
- package/icons/npm-outline.svg +1 -0
- package/icons/options-2-outline.svg +1 -0
- package/icons/options-outline.svg +1 -0
- package/icons/pantone-outline.svg +1 -0
- package/icons/paper-plane-outline.svg +1 -0
- package/icons/pause-circle-outline.svg +1 -0
- package/icons/people-outline.svg +1 -0
- package/icons/percent-outline.svg +1 -0
- package/icons/person-add-outline.svg +1 -0
- package/icons/person-delete-outline.svg +1 -0
- package/icons/person-done-outline.svg +1 -0
- package/icons/person-outline.svg +1 -0
- package/icons/person-remove-outline.svg +1 -0
- package/icons/phone-call-outline.svg +1 -0
- package/icons/phone-missed-outline.svg +1 -0
- package/icons/phone-off-outline.svg +1 -0
- package/icons/phone-outline.svg +1 -0
- package/icons/pie-chart-outline.svg +1 -0
- package/icons/pin-outline.svg +1 -0
- package/icons/play-circle-outline.svg +1 -0
- package/icons/plus-circle-outline.svg +1 -0
- package/icons/plus-outline.svg +1 -0
- package/icons/plus-square-outline.svg +1 -0
- package/icons/power-outline.svg +1 -0
- package/icons/pricetags-outline.svg +1 -0
- package/icons/printer-outline.svg +1 -0
- package/icons/question-mark-circle-outline.svg +1 -0
- package/icons/question-mark-outline.svg +1 -0
- package/icons/radio-button-off-outline.svg +1 -0
- package/icons/radio-button-on-outline.svg +1 -0
- package/icons/radio-outline.svg +1 -0
- package/icons/recording-outline.svg +1 -0
- package/icons/refresh-outline.svg +1 -0
- package/icons/repeat-outline.svg +1 -0
- package/icons/rewind-left-outline.svg +1 -0
- package/icons/rewind-right-outline.svg +1 -0
- package/icons/save-outline.svg +1 -0
- package/icons/scissors-outline.svg +1 -0
- package/icons/search-outline.svg +1 -0
- package/icons/settings-2-outline.svg +1 -0
- package/icons/settings-outline.svg +1 -0
- package/icons/shake-outline.svg +1 -0
- package/icons/share-outline.svg +1 -0
- package/icons/shield-off-outline.svg +1 -0
- package/icons/shield-outline.svg +1 -0
- package/icons/shopping-bag-outline.svg +1 -0
- package/icons/shopping-cart-outline.svg +1 -0
- package/icons/shuffle-2-outline.svg +1 -0
- package/icons/shuffle-outline.svg +1 -0
- package/icons/skip-back-outline.svg +1 -0
- package/icons/skip-forward-outline.svg +1 -0
- package/icons/slash-outline.svg +1 -0
- package/icons/smartphone-outline.svg +1 -0
- package/icons/smiling-face-outline.svg +1 -0
- package/icons/speaker-outline.svg +1 -0
- package/icons/square-outline.svg +1 -0
- package/icons/star-outline.svg +1 -0
- package/icons/stop-circle-outline.svg +1 -0
- package/icons/sun-outline.svg +1 -0
- package/icons/swap-outline.svg +1 -0
- package/icons/sync-outline.svg +1 -0
- package/icons/text-outline.svg +1 -0
- package/icons/thermometer-minus-outline.svg +1 -0
- package/icons/thermometer-outline.svg +1 -0
- package/icons/thermometer-plus-outline.svg +1 -0
- package/icons/toggle-left-outline.svg +1 -0
- package/icons/toggle-right-outline.svg +1 -0
- package/icons/trash-2-outline.svg +1 -0
- package/icons/trash-outline.svg +1 -0
- package/icons/trending-down-outline.svg +1 -0
- package/icons/trending-up-outline.svg +1 -0
- package/icons/tv-outline.svg +1 -0
- package/icons/twitter-outline.svg +1 -0
- package/icons/umbrella-outline.svg +1 -0
- package/icons/undo-outline.svg +1 -0
- package/icons/unlock-outline.svg +1 -0
- package/icons/upload-outline.svg +1 -0
- package/icons/video-off-outline.svg +1 -0
- package/icons/video-outline.svg +1 -0
- package/icons/volume-down-outline.svg +1 -0
- package/icons/volume-mute-outline.svg +1 -0
- package/icons/volume-off-outline.svg +1 -0
- package/icons/volume-up-outline.svg +1 -0
- package/icons/wifi-off-outline.svg +1 -0
- package/icons/wifi-outline.svg +1 -0
- package/js/carousel.js +133 -0
- package/js/carousel.ts +173 -0
- package/js/chart.js +257 -0
- package/js/code-viewer.js +148 -0
- package/js/code-viewer.ts +188 -0
- package/js/datepicker.js +497 -0
- package/js/datepicker.ts +619 -0
- package/js/dropdown.js +122 -0
- package/js/dropdown.ts +180 -0
- package/js/editor.js +421 -0
- package/js/editor.ts +426 -0
- package/js/file-uploader.js +268 -0
- package/js/file-uploader.ts +350 -0
- package/js/flyout-menu.js +195 -0
- package/js/flyout-menu.ts +250 -0
- package/js/form-builder.js +107 -0
- package/js/gallery.js +177 -0
- package/js/gallery.ts +231 -0
- package/js/guitar-chords.js +268 -0
- package/js/index.js +720 -0
- package/js/index.ts +874 -0
- package/js/lazy-loader.js +121 -0
- package/js/modal.js +113 -0
- package/js/modal.ts +167 -0
- package/js/push-menu.js +101 -0
- package/js/push-menu.ts +130 -0
- package/js/request.js +51 -0
- package/js/scroll.js +27 -0
- package/js/scroll.ts +47 -0
- package/js/scrollbar.js +219 -0
- package/js/scrollbar.ts +308 -0
- package/js/select.js +158 -0
- package/js/select.ts +217 -0
- package/js/table.js +359 -0
- package/js/table.ts +453 -0
- package/js/tabs.js +225 -0
- package/js/tabs.ts +280 -0
- package/js/theme.js +194 -0
- package/js/theme.ts +225 -0
- package/js/timepicker.js +98 -0
- package/js/timepicker.ts +131 -0
- package/js/toast.js +93 -0
- package/js/toast.ts +138 -0
- package/js/tooltip.js +193 -0
- package/js/tooltip.ts +252 -0
- package/js/tree.js +162 -0
- package/js/tree.ts +218 -0
- package/js/tsconfig.json +18 -0
- package/js/utils.js +69 -0
- package/js/utils.ts +84 -0
- package/js/virtual-dropdown.js +277 -0
- package/js/virtual-dropdown.ts +366 -0
- package/package.json +38 -0
package/js/scroll.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
class Scroll {
|
|
2
|
+
static to(target, options = {}) {
|
|
3
|
+
const fixed_header = document.querySelector('.main-header');
|
|
4
|
+
const offset = fixed_header ? fixed_header.offsetHeight : 0;
|
|
5
|
+
const settings = {
|
|
6
|
+
behavior: "smooth",
|
|
7
|
+
offset: offset,
|
|
8
|
+
block: "start",
|
|
9
|
+
...options
|
|
10
|
+
};
|
|
11
|
+
let el = target instanceof Element ? target : null;
|
|
12
|
+
if (typeof target === "string") {
|
|
13
|
+
el = document.querySelector(target);
|
|
14
|
+
}
|
|
15
|
+
if (!el)
|
|
16
|
+
return;
|
|
17
|
+
const rect = el.getBoundingClientRect();
|
|
18
|
+
const scrollTop = window.scrollY;
|
|
19
|
+
const offsetTop = rect.top + scrollTop - settings.offset;
|
|
20
|
+
window.scrollTo({
|
|
21
|
+
top: offsetTop,
|
|
22
|
+
behavior: settings.behavior
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
window.Scroll = Scroll;
|
|
27
|
+
export { Scroll };
|
package/js/scroll.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
interface ScrollOptions {
|
|
2
|
+
behavior?: ScrollBehavior;
|
|
3
|
+
offset?: number;
|
|
4
|
+
block?: ScrollLogicalPosition;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
class Scroll {
|
|
8
|
+
public static to(target: string | Element, options: ScrollOptions = {}): void {
|
|
9
|
+
const fixed_header = document.querySelector('.main-header') as HTMLElement | null;
|
|
10
|
+
const offset = fixed_header ? fixed_header.offsetHeight : 0;
|
|
11
|
+
|
|
12
|
+
const settings: Required<ScrollOptions> = {
|
|
13
|
+
behavior: "smooth",
|
|
14
|
+
offset: offset,
|
|
15
|
+
block: "start",
|
|
16
|
+
...options
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let el: Element | null = target instanceof Element ? target : null;
|
|
20
|
+
|
|
21
|
+
if (typeof target === "string") {
|
|
22
|
+
el = document.querySelector(target);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!el) return;
|
|
26
|
+
|
|
27
|
+
const rect = el.getBoundingClientRect();
|
|
28
|
+
const scrollTop = window.scrollY;
|
|
29
|
+
const offsetTop = rect.top + scrollTop - settings.offset;
|
|
30
|
+
|
|
31
|
+
window.scrollTo({
|
|
32
|
+
top: offsetTop,
|
|
33
|
+
behavior: settings.behavior
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare global {
|
|
39
|
+
interface Window {
|
|
40
|
+
Scroll: typeof Scroll;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
window.Scroll = Scroll;
|
|
45
|
+
|
|
46
|
+
export { Scroll };
|
|
47
|
+
export type { ScrollOptions };
|
package/js/scrollbar.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
class Scrollbar {
|
|
2
|
+
constructor(elementOrSelector) {
|
|
3
|
+
this.dragging = false;
|
|
4
|
+
this.activePointerId = null;
|
|
5
|
+
this.startPointerY = 0;
|
|
6
|
+
this.startThumbTop = 0;
|
|
7
|
+
const container = typeof elementOrSelector === 'string'
|
|
8
|
+
? document.querySelector(elementOrSelector)
|
|
9
|
+
: elementOrSelector;
|
|
10
|
+
if (!container) {
|
|
11
|
+
throw new Error(`Scrollbar: Element not found for selector "${elementOrSelector}"`);
|
|
12
|
+
}
|
|
13
|
+
// Return existing instance if already initialized
|
|
14
|
+
const existingInstance = Scrollbar.instances.get(container);
|
|
15
|
+
if (existingInstance) {
|
|
16
|
+
return existingInstance;
|
|
17
|
+
}
|
|
18
|
+
this.container = container;
|
|
19
|
+
// Query and validate required elements
|
|
20
|
+
const elements = this.getRequiredElements(container);
|
|
21
|
+
this.viewport = elements.viewport;
|
|
22
|
+
this.content = elements.content;
|
|
23
|
+
this.track = elements.track;
|
|
24
|
+
this.thumb = elements.thumb;
|
|
25
|
+
// Get minimum thumb height from CSS variable or use default
|
|
26
|
+
this.MIN_THUMB_HEIGHT = this.getMinThumbHeight();
|
|
27
|
+
// Bind all event handlers once
|
|
28
|
+
this.boundPointerMove = this.handlePointerMove.bind(this);
|
|
29
|
+
this.boundPointerUp = this.handlePointerUp.bind(this);
|
|
30
|
+
this.boundThumbPointerDown = this.handleThumbPointerDown.bind(this);
|
|
31
|
+
this.boundTrackClick = this.handleTrackClick.bind(this);
|
|
32
|
+
this.boundViewportScroll = this.updateThumb.bind(this);
|
|
33
|
+
this.boundUpdateThumb = this.updateThumb.bind(this);
|
|
34
|
+
// Setup ResizeObserver
|
|
35
|
+
this.ro = new ResizeObserver(this.boundUpdateThumb);
|
|
36
|
+
// Initialize
|
|
37
|
+
this.attachEventListeners();
|
|
38
|
+
Scrollbar.instances.set(container, this);
|
|
39
|
+
// Install global listeners once for all instances
|
|
40
|
+
if (!Scrollbar.globalListenersInstalled) {
|
|
41
|
+
Scrollbar.installGlobalListeners();
|
|
42
|
+
}
|
|
43
|
+
// Initial thumb update
|
|
44
|
+
requestAnimationFrame(this.boundUpdateThumb);
|
|
45
|
+
}
|
|
46
|
+
getRequiredElements(container) {
|
|
47
|
+
const viewport = container.querySelector('.viewport');
|
|
48
|
+
const content = container.querySelector('.content');
|
|
49
|
+
const track = container.querySelector('.track');
|
|
50
|
+
const thumb = container.querySelector('.thumb');
|
|
51
|
+
if (!viewport || !content || !track || !thumb) {
|
|
52
|
+
throw new Error('Required scrollbar elements not found. Expected: .viewport, .content, .track, .thumb');
|
|
53
|
+
}
|
|
54
|
+
return { viewport, content, track, thumb };
|
|
55
|
+
}
|
|
56
|
+
getMinThumbHeight() {
|
|
57
|
+
const cssValue = getComputedStyle(document.documentElement)
|
|
58
|
+
.getPropertyValue('--thumb-min')
|
|
59
|
+
.trim();
|
|
60
|
+
const parsed = parseInt(cssValue, 10);
|
|
61
|
+
const defaultMin = 28;
|
|
62
|
+
const absoluteMin = 16;
|
|
63
|
+
return Math.max(absoluteMin, parsed || defaultMin);
|
|
64
|
+
}
|
|
65
|
+
static installGlobalListeners() {
|
|
66
|
+
// Route pointer events to the active scrollbar instance
|
|
67
|
+
document.addEventListener('pointermove', (e) => {
|
|
68
|
+
Scrollbar.activeInstance?.boundPointerMove(e);
|
|
69
|
+
}, { passive: false });
|
|
70
|
+
document.addEventListener('pointerup', (e) => {
|
|
71
|
+
Scrollbar.activeInstance?.boundPointerUp(e);
|
|
72
|
+
});
|
|
73
|
+
document.addEventListener('pointercancel', (e) => {
|
|
74
|
+
Scrollbar.activeInstance?.boundPointerUp(e);
|
|
75
|
+
});
|
|
76
|
+
Scrollbar.globalListenersInstalled = true;
|
|
77
|
+
}
|
|
78
|
+
attachEventListeners() {
|
|
79
|
+
// Instance-specific events
|
|
80
|
+
this.viewport.addEventListener('scroll', this.boundViewportScroll, { passive: true });
|
|
81
|
+
this.thumb.addEventListener('pointerdown', this.boundThumbPointerDown);
|
|
82
|
+
this.track.addEventListener('click', this.boundTrackClick);
|
|
83
|
+
// Observe size changes
|
|
84
|
+
this.ro.observe(this.viewport);
|
|
85
|
+
this.ro.observe(this.content);
|
|
86
|
+
window.addEventListener('resize', this.boundUpdateThumb);
|
|
87
|
+
}
|
|
88
|
+
updateThumb() {
|
|
89
|
+
const viewportHeight = this.viewport.clientHeight;
|
|
90
|
+
const contentHeight = this.content.scrollHeight;
|
|
91
|
+
const trackHeight = this.track.clientHeight;
|
|
92
|
+
// Hide thumb if content fits in viewport
|
|
93
|
+
if (contentHeight <= viewportHeight + 1) {
|
|
94
|
+
this.thumb.style.display = 'none';
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
this.thumb.style.display = '';
|
|
98
|
+
// Calculate thumb size
|
|
99
|
+
const ratio = viewportHeight / contentHeight;
|
|
100
|
+
const thumbHeight = Math.max(Math.floor(ratio * trackHeight), this.MIN_THUMB_HEIGHT);
|
|
101
|
+
this.thumb.style.height = `${thumbHeight}px`;
|
|
102
|
+
// Calculate thumb position
|
|
103
|
+
const maxScroll = contentHeight - viewportHeight;
|
|
104
|
+
const maxThumbTop = trackHeight - thumbHeight;
|
|
105
|
+
const scrollRatio = this.viewport.scrollTop / (maxScroll || 1);
|
|
106
|
+
const thumbTop = scrollRatio * (maxThumbTop || 0);
|
|
107
|
+
this.thumb.style.top = `${thumbTop}px`;
|
|
108
|
+
}
|
|
109
|
+
handleThumbPointerDown(e) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
this.dragging = true;
|
|
112
|
+
this.activePointerId = e.pointerId;
|
|
113
|
+
Scrollbar.activeInstance = this;
|
|
114
|
+
// Capture pointer for reliable tracking
|
|
115
|
+
try {
|
|
116
|
+
this.thumb.setPointerCapture(e.pointerId);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
console.warn('Failed to capture pointer:', err);
|
|
120
|
+
}
|
|
121
|
+
this.startPointerY = e.clientY;
|
|
122
|
+
const thumbRect = this.thumb.getBoundingClientRect();
|
|
123
|
+
const trackRect = this.track.getBoundingClientRect();
|
|
124
|
+
this.startThumbTop = thumbRect.top - trackRect.top;
|
|
125
|
+
// Prevent text selection during drag
|
|
126
|
+
document.body.style.userSelect = 'none';
|
|
127
|
+
}
|
|
128
|
+
handlePointerMove(e) {
|
|
129
|
+
// Only handle events for the active pointer
|
|
130
|
+
if (!this.dragging || this.activePointerId !== e.pointerId) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
const pointerDelta = e.clientY - this.startPointerY;
|
|
135
|
+
const trackHeight = this.track.clientHeight;
|
|
136
|
+
const thumbHeight = this.thumb.clientHeight;
|
|
137
|
+
const maxThumbTop = trackHeight - thumbHeight;
|
|
138
|
+
// Calculate new thumb position
|
|
139
|
+
const newThumbTop = Math.max(0, Math.min(maxThumbTop, this.startThumbTop + pointerDelta));
|
|
140
|
+
this.thumb.style.top = `${newThumbTop}px`;
|
|
141
|
+
// Update viewport scroll position
|
|
142
|
+
const contentHeight = this.content.scrollHeight;
|
|
143
|
+
const viewportHeight = this.viewport.clientHeight;
|
|
144
|
+
const maxScroll = contentHeight - viewportHeight;
|
|
145
|
+
const scrollRatio = newThumbTop / (maxThumbTop || 1);
|
|
146
|
+
this.viewport.scrollTop = scrollRatio * (maxScroll || 0);
|
|
147
|
+
}
|
|
148
|
+
handlePointerUp(e) {
|
|
149
|
+
if (!this.dragging || this.activePointerId !== e.pointerId) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.dragging = false;
|
|
153
|
+
// Release pointer capture
|
|
154
|
+
try {
|
|
155
|
+
this.thumb.releasePointerCapture(e.pointerId);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.warn('Failed to release pointer:', err);
|
|
159
|
+
}
|
|
160
|
+
this.activePointerId = null;
|
|
161
|
+
Scrollbar.activeInstance = null;
|
|
162
|
+
document.body.style.userSelect = '';
|
|
163
|
+
}
|
|
164
|
+
handleTrackClick(e) {
|
|
165
|
+
// Ignore clicks directly on the thumb
|
|
166
|
+
if (e.target === this.thumb) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const trackRect = this.track.getBoundingClientRect();
|
|
170
|
+
const clickY = e.clientY - trackRect.top;
|
|
171
|
+
const thumbHeight = this.thumb.clientHeight;
|
|
172
|
+
const trackHeight = this.track.clientHeight;
|
|
173
|
+
// Center thumb on click position
|
|
174
|
+
const targetThumbTop = clickY - thumbHeight / 2;
|
|
175
|
+
const maxThumbTop = trackHeight - thumbHeight;
|
|
176
|
+
const clampedThumbTop = Math.max(0, Math.min(maxThumbTop, targetThumbTop));
|
|
177
|
+
// Calculate corresponding scroll position
|
|
178
|
+
const contentHeight = this.content.scrollHeight;
|
|
179
|
+
const viewportHeight = this.viewport.clientHeight;
|
|
180
|
+
const maxScroll = contentHeight - viewportHeight;
|
|
181
|
+
const scrollRatio = clampedThumbTop / (maxThumbTop || 1);
|
|
182
|
+
const scrollTop = scrollRatio * (maxScroll || 0);
|
|
183
|
+
this.viewport.scrollTo({ top: scrollTop, behavior: 'smooth' });
|
|
184
|
+
}
|
|
185
|
+
destroy() {
|
|
186
|
+
// Remove event listeners
|
|
187
|
+
this.viewport.removeEventListener('scroll', this.boundViewportScroll);
|
|
188
|
+
this.thumb.removeEventListener('pointerdown', this.boundThumbPointerDown);
|
|
189
|
+
this.track.removeEventListener('click', this.boundTrackClick);
|
|
190
|
+
window.removeEventListener('resize', this.boundUpdateThumb);
|
|
191
|
+
// Disconnect observer
|
|
192
|
+
this.ro.disconnect();
|
|
193
|
+
// Clear from instances map
|
|
194
|
+
Scrollbar.instances.delete(this.container);
|
|
195
|
+
// Clear active instance if this was it
|
|
196
|
+
if (Scrollbar.activeInstance === this) {
|
|
197
|
+
Scrollbar.activeInstance = null;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Static factory methods
|
|
201
|
+
static initAll(selector) {
|
|
202
|
+
const containers = document.querySelectorAll(selector);
|
|
203
|
+
return Array.from(containers).map(container => new Scrollbar(container));
|
|
204
|
+
}
|
|
205
|
+
static initOne(elementOrSelector) {
|
|
206
|
+
return new Scrollbar(elementOrSelector);
|
|
207
|
+
}
|
|
208
|
+
static getInstance(container) {
|
|
209
|
+
return Scrollbar.instances.get(container);
|
|
210
|
+
}
|
|
211
|
+
static destroyAll() {
|
|
212
|
+
// Note: WeakMap doesn't support iteration, so this is a no-op
|
|
213
|
+
// Individual instances should be destroyed by calling destroy()
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
Scrollbar.instances = new WeakMap();
|
|
217
|
+
Scrollbar.activeInstance = null;
|
|
218
|
+
Scrollbar.globalListenersInstalled = false;
|
|
219
|
+
export { Scrollbar };
|
package/js/scrollbar.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
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
|
+
|
|
308
|
+
export { Scrollbar, ScrollbarElements };
|