@dodlhuat/basix 1.3.5 → 1.4.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.
- package/README.md +92 -7
- package/css/checkbox.scss +15 -2
- package/css/color-picker.scss +278 -0
- package/css/datepicker.scss +5 -3
- package/css/push-menu.scss +7 -0
- package/css/stepper.scss +1 -1
- package/css/style.css +291 -6
- package/css/style.css.map +1 -1
- package/css/style.min.css +1 -1
- package/css/style.min.css.map +1 -1
- package/css/style.scss +1 -0
- package/css/table.scss +33 -0
- package/js/bottom-sheet.d.ts +0 -2
- package/js/bottom-sheet.js +0 -2
- package/js/calendar.d.ts +9 -17
- package/js/calendar.js +4 -7
- package/js/carousel.d.ts +0 -2
- package/js/carousel.js +1 -3
- package/js/chart.d.ts +6 -14
- package/js/chart.js +3 -5
- package/js/code-viewer.d.ts +2 -1
- package/js/code-viewer.js +8 -2
- package/js/color-picker.d.ts +50 -0
- package/js/color-picker.js +291 -0
- package/js/context-menu.d.ts +0 -3
- package/js/context-menu.js +0 -3
- package/js/datepicker.d.ts +0 -4
- package/js/datepicker.js +0 -1
- package/js/docs-nav.js +1 -0
- package/js/dropdown.d.ts +1 -4
- package/js/dropdown.js +0 -1
- package/js/editor.d.ts +0 -4
- package/js/editor.js +3 -5
- package/js/file-uploader.d.ts +0 -4
- package/js/file-uploader.js +0 -1
- package/js/flyout-menu.d.ts +0 -2
- package/js/flyout-menu.js +0 -1
- package/js/gallery.d.ts +1 -4
- package/js/gallery.js +18 -23
- package/js/group-picker.d.ts +0 -5
- package/js/group-picker.js +6 -8
- package/js/lightbox.d.ts +1 -4
- package/js/lightbox.js +6 -10
- package/js/modal.d.ts +0 -2
- package/js/modal.js +0 -1
- package/js/popover.d.ts +0 -3
- package/js/popover.js +0 -2
- package/js/position.d.ts +0 -13
- package/js/position.js +0 -12
- package/js/push-menu.d.ts +0 -3
- package/js/push-menu.js +6 -11
- package/js/range-slider.d.ts +0 -1
- package/js/range-slider.js +2 -3
- package/js/scroll.d.ts +0 -2
- package/js/scroll.js +5 -6
- package/js/scrollbar.d.ts +2 -10
- package/js/scrollbar.js +25 -42
- package/js/select.d.ts +2 -3
- package/js/select.js +6 -9
- package/js/sidebar-nav.d.ts +4 -15
- package/js/sidebar-nav.js +21 -35
- package/js/stepper.d.ts +5 -2
- package/js/stepper.js +42 -6
- package/js/table.d.ts +1 -56
- package/js/table.js +4 -56
- package/js/tabs.d.ts +1 -32
- package/js/tabs.js +7 -39
- package/js/theme.d.ts +0 -44
- package/js/theme.js +0 -44
- package/js/timepicker.d.ts +1 -4
- package/js/timepicker.js +1 -2
- package/js/toast.d.ts +0 -2
- package/js/toast.js +0 -1
- package/js/tooltip.d.ts +0 -4
- package/js/tooltip.js +0 -1
- package/js/tree.d.ts +0 -3
- package/js/tree.js +9 -11
- package/js/utils.d.ts +0 -10
- package/js/utils.js +2 -42
- package/js/virtual-dropdown.d.ts +3 -5
- package/js/virtual-dropdown.js +17 -66
- package/package.json +6 -2
package/js/group-picker.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
|
-
/** Searchable picker for selecting groups and their subgroups. */
|
|
3
2
|
class GroupPicker {
|
|
4
3
|
container;
|
|
5
4
|
data;
|
|
@@ -44,9 +43,9 @@ class GroupPicker {
|
|
|
44
43
|
const searchWrap = document.createElement('div');
|
|
45
44
|
searchWrap.className = 'group-picker__search';
|
|
46
45
|
searchWrap.innerHTML = `
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
<span class="icon icon-search group-picker__search-icon" aria-hidden="true"></span>
|
|
47
|
+
<input type="text" />
|
|
48
|
+
`;
|
|
50
49
|
this.searchInput = searchWrap.querySelector('input');
|
|
51
50
|
this.searchInput.placeholder = this.options.searchPlaceholder;
|
|
52
51
|
this.listEl = document.createElement('div');
|
|
@@ -73,9 +72,9 @@ class GroupPicker {
|
|
|
73
72
|
const empty = document.createElement('div');
|
|
74
73
|
empty.className = 'group-picker__empty';
|
|
75
74
|
empty.innerHTML = `
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
<span class="icon icon-search" aria-hidden="true"></span>
|
|
76
|
+
<span>${this.options.emptyLabel}</span>
|
|
77
|
+
`;
|
|
79
78
|
this.listEl.appendChild(empty);
|
|
80
79
|
}
|
|
81
80
|
}
|
|
@@ -94,7 +93,6 @@ class GroupPicker {
|
|
|
94
93
|
el.classList.add('is-expanded');
|
|
95
94
|
if (isParentSelected)
|
|
96
95
|
el.classList.add('is-selected');
|
|
97
|
-
// Header row
|
|
98
96
|
const header = document.createElement('div');
|
|
99
97
|
header.className = 'group-picker__group-header';
|
|
100
98
|
const label = document.createElement('span');
|
package/js/lightbox.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
/** A single image entry for the Lightbox gallery. */
|
|
2
1
|
interface LightboxImage {
|
|
3
2
|
src: string;
|
|
4
3
|
alt?: string;
|
|
5
4
|
caption?: string;
|
|
6
5
|
}
|
|
7
|
-
/** Configuration options for a Lightbox instance. */
|
|
8
6
|
interface LightboxOptions {
|
|
9
7
|
src?: string;
|
|
10
8
|
alt?: string;
|
|
@@ -15,7 +13,6 @@ interface LightboxOptions {
|
|
|
15
13
|
onOpen?: () => void;
|
|
16
14
|
onClose?: () => void;
|
|
17
15
|
}
|
|
18
|
-
/** Full-screen image viewer with gallery navigation, zoom, and touch support. */
|
|
19
16
|
declare class Lightbox {
|
|
20
17
|
private images;
|
|
21
18
|
private currentIndex;
|
|
@@ -30,7 +27,7 @@ declare class Lightbox {
|
|
|
30
27
|
private abortController;
|
|
31
28
|
constructor(options: LightboxOptions);
|
|
32
29
|
show(): void;
|
|
33
|
-
hide()
|
|
30
|
+
hide: () => void;
|
|
34
31
|
next(): void;
|
|
35
32
|
prev(): void;
|
|
36
33
|
isVisible(): boolean;
|
package/js/lightbox.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Full-screen image viewer with gallery navigation, zoom, and touch support. */
|
|
2
1
|
class Lightbox {
|
|
3
2
|
images;
|
|
4
3
|
currentIndex;
|
|
@@ -22,9 +21,6 @@ class Lightbox {
|
|
|
22
21
|
this.closeable = options.closeable ?? true;
|
|
23
22
|
this.onOpen = options.onOpen;
|
|
24
23
|
this.onClose = options.onClose;
|
|
25
|
-
this.hide = this.hide.bind(this);
|
|
26
|
-
this.handleKeydown = this.handleKeydown.bind(this);
|
|
27
|
-
this.handleBackgroundClick = this.handleBackgroundClick.bind(this);
|
|
28
24
|
}
|
|
29
25
|
show() {
|
|
30
26
|
this.hide();
|
|
@@ -61,7 +57,7 @@ class Lightbox {
|
|
|
61
57
|
});
|
|
62
58
|
this.onOpen?.();
|
|
63
59
|
}
|
|
64
|
-
hide() {
|
|
60
|
+
hide = () => {
|
|
65
61
|
const wrapper = this.wrapper;
|
|
66
62
|
if (!wrapper)
|
|
67
63
|
return;
|
|
@@ -80,7 +76,7 @@ class Lightbox {
|
|
|
80
76
|
}
|
|
81
77
|
this.onClose?.();
|
|
82
78
|
}, 300);
|
|
83
|
-
}
|
|
79
|
+
};
|
|
84
80
|
next() {
|
|
85
81
|
if (this.images.length <= 1)
|
|
86
82
|
return;
|
|
@@ -168,7 +164,7 @@ class Lightbox {
|
|
|
168
164
|
this.imgEl.classList.toggle('is-zoomed', this.isZoomed);
|
|
169
165
|
this.wrapper?.querySelector('.lightbox-img-wrap')?.classList.toggle('is-zoomed', this.isZoomed);
|
|
170
166
|
}
|
|
171
|
-
handleKeydown(e) {
|
|
167
|
+
handleKeydown = (e) => {
|
|
172
168
|
switch (e.key) {
|
|
173
169
|
case 'Escape':
|
|
174
170
|
if (this.closeable)
|
|
@@ -184,7 +180,7 @@ class Lightbox {
|
|
|
184
180
|
this.trapFocus(e);
|
|
185
181
|
break;
|
|
186
182
|
}
|
|
187
|
-
}
|
|
183
|
+
};
|
|
188
184
|
trapFocus(e) {
|
|
189
185
|
if (!this.wrapper)
|
|
190
186
|
return;
|
|
@@ -206,11 +202,11 @@ class Lightbox {
|
|
|
206
202
|
}
|
|
207
203
|
}
|
|
208
204
|
}
|
|
209
|
-
handleBackgroundClick(e) {
|
|
205
|
+
handleBackgroundClick = (e) => {
|
|
210
206
|
if (e.target?.classList.contains('lightbox-background')) {
|
|
211
207
|
this.hide();
|
|
212
208
|
}
|
|
213
|
-
}
|
|
209
|
+
};
|
|
214
210
|
addTouchSupport() {
|
|
215
211
|
const wrap = this.wrapper?.querySelector('.lightbox-img-wrap');
|
|
216
212
|
if (!wrap)
|
package/js/modal.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
type ModalType = 'default' | 'success' | 'error' | 'warning' | 'info';
|
|
2
|
-
/** Configuration options for a Modal dialog. */
|
|
3
2
|
interface ModalOptions {
|
|
4
3
|
content: string;
|
|
5
4
|
header?: string;
|
|
@@ -7,7 +6,6 @@ interface ModalOptions {
|
|
|
7
6
|
closeable?: boolean;
|
|
8
7
|
type?: ModalType;
|
|
9
8
|
}
|
|
10
|
-
/** Overlay dialog with optional header, footer, close button, and type variants. */
|
|
11
9
|
declare class Modal {
|
|
12
10
|
private content;
|
|
13
11
|
private readonly header?;
|
package/js/modal.js
CHANGED
package/js/popover.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { Placement } from './position.js';
|
|
|
2
2
|
type PopoverPlacement = Placement | 'auto';
|
|
3
3
|
type PopoverAlign = 'start' | 'center' | 'end';
|
|
4
4
|
type PopoverTrigger = 'click' | 'hover';
|
|
5
|
-
/** Configuration options for a Popover instance. */
|
|
6
5
|
interface PopoverOptions {
|
|
7
6
|
content: string;
|
|
8
7
|
placement?: PopoverPlacement;
|
|
@@ -16,7 +15,6 @@ interface PopoverOptions {
|
|
|
16
15
|
onOpen?: () => void;
|
|
17
16
|
onClose?: () => void;
|
|
18
17
|
}
|
|
19
|
-
/** Anchored popover triggered by click or hover, with auto-placement and optional arrow. */
|
|
20
18
|
declare class Popover {
|
|
21
19
|
private static openPopovers;
|
|
22
20
|
private static idCounter;
|
|
@@ -32,7 +30,6 @@ declare class Popover {
|
|
|
32
30
|
toggle(): void;
|
|
33
31
|
destroy(): void;
|
|
34
32
|
static closeAll(): void;
|
|
35
|
-
/** Declarative init — reads [data-popover="#selector"] attributes */
|
|
36
33
|
static initAll(): void;
|
|
37
34
|
private buildEl;
|
|
38
35
|
private reposition;
|
package/js/popover.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { computePosition } from './position.js';
|
|
2
2
|
import { sanitizeHtml } from './utils.js';
|
|
3
3
|
const ARROW_SIZE = 6;
|
|
4
|
-
/** Anchored popover triggered by click or hover, with auto-placement and optional arrow. */
|
|
5
4
|
class Popover {
|
|
6
5
|
static openPopovers = new Set();
|
|
7
6
|
static idCounter = 0;
|
|
@@ -75,7 +74,6 @@ class Popover {
|
|
|
75
74
|
static closeAll() {
|
|
76
75
|
Popover.openPopovers.forEach(p => p.close());
|
|
77
76
|
}
|
|
78
|
-
/** Declarative init — reads [data-popover="#selector"] attributes */
|
|
79
77
|
static initAll() {
|
|
80
78
|
document.querySelectorAll('[data-popover]').forEach(trigger => {
|
|
81
79
|
const sel = trigger.getAttribute('data-popover');
|
package/js/position.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared floating-element positioning utility.
|
|
3
|
-
* Used by Tooltip and Popover.
|
|
4
|
-
*/
|
|
5
1
|
type Placement = 'top' | 'bottom' | 'left' | 'right';
|
|
6
2
|
type Align = 'start' | 'center' | 'end';
|
|
7
|
-
/** Options accepted by `computePosition`. */
|
|
8
3
|
interface PositionOptions {
|
|
9
4
|
placement: Placement | 'auto';
|
|
10
5
|
align?: Align;
|
|
@@ -12,22 +7,14 @@ interface PositionOptions {
|
|
|
12
7
|
margin?: number;
|
|
13
8
|
arrowSize?: number;
|
|
14
9
|
}
|
|
15
|
-
/** Result returned by `computePosition`. */
|
|
16
10
|
interface PositionResult {
|
|
17
11
|
left: number;
|
|
18
12
|
top: number;
|
|
19
13
|
placement: Placement;
|
|
20
14
|
arrowOffset?: number;
|
|
21
15
|
}
|
|
22
|
-
/** Pick the placement with the most available space, preferring bottom > top > right > left. */
|
|
23
16
|
declare function bestPlacement(trigger: DOMRect, floating: DOMRect, offset: number): Placement;
|
|
24
|
-
/** Flip to opposite side if preferred placement doesn't fit. */
|
|
25
17
|
declare function maybeFlip(placement: Placement, trigger: DOMRect, floating: DOMRect, offset: number): Placement;
|
|
26
|
-
/**
|
|
27
|
-
* Compute `left` / `top` for a `position: fixed` floating element anchored to a trigger.
|
|
28
|
-
* Handles placement resolution (auto + flip), cross-axis alignment, viewport clamping,
|
|
29
|
-
* and optional arrow offset calculation.
|
|
30
|
-
*/
|
|
31
18
|
declare function computePosition(trigger: DOMRect, floating: DOMRect, opts: PositionOptions): PositionResult;
|
|
32
19
|
export { computePosition, bestPlacement, maybeFlip };
|
|
33
20
|
export type { Placement, Align, PositionOptions, PositionResult };
|
package/js/position.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared floating-element positioning utility.
|
|
3
|
-
* Used by Tooltip and Popover.
|
|
4
|
-
*/
|
|
5
|
-
/** Pick the placement with the most available space, preferring bottom > top > right > left. */
|
|
6
1
|
function bestPlacement(trigger, floating, offset) {
|
|
7
2
|
const space = {
|
|
8
3
|
bottom: window.innerHeight - trigger.bottom - offset,
|
|
@@ -18,10 +13,8 @@ function bestPlacement(trigger, floating, offset) {
|
|
|
18
13
|
return 'right';
|
|
19
14
|
if (space.left >= floating.width)
|
|
20
15
|
return 'left';
|
|
21
|
-
// Fallback: largest available side
|
|
22
16
|
return (Object.entries(space).sort((a, b) => b[1] - a[1])[0][0]);
|
|
23
17
|
}
|
|
24
|
-
/** Flip to opposite side if preferred placement doesn't fit. */
|
|
25
18
|
function maybeFlip(placement, trigger, floating, offset) {
|
|
26
19
|
const fits = {
|
|
27
20
|
top: trigger.top - offset >= floating.height,
|
|
@@ -32,11 +25,6 @@ function maybeFlip(placement, trigger, floating, offset) {
|
|
|
32
25
|
const opp = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' };
|
|
33
26
|
return (!fits[placement] && fits[opp[placement]]) ? opp[placement] : placement;
|
|
34
27
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Compute `left` / `top` for a `position: fixed` floating element anchored to a trigger.
|
|
37
|
-
* Handles placement resolution (auto + flip), cross-axis alignment, viewport clamping,
|
|
38
|
-
* and optional arrow offset calculation.
|
|
39
|
-
*/
|
|
40
28
|
function computePosition(trigger, floating, opts) {
|
|
41
29
|
const offset = opts.offset ?? 8;
|
|
42
30
|
const margin = opts.margin ?? 8;
|
package/js/push-menu.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** DOM element references managed by the PushMenu static class. */
|
|
2
1
|
interface PushMenuElements {
|
|
3
2
|
navigation: HTMLElement | null;
|
|
4
3
|
content: HTMLElement | null;
|
|
@@ -7,12 +6,10 @@ interface PushMenuElements {
|
|
|
7
6
|
controlIcon: HTMLElement | null;
|
|
8
7
|
backdrop: HTMLElement | null;
|
|
9
8
|
}
|
|
10
|
-
/** Static class that manages a push-style side navigation panel. */
|
|
11
9
|
declare class PushMenu {
|
|
12
10
|
private static elements;
|
|
13
11
|
private static initialized;
|
|
14
12
|
private static panelStack;
|
|
15
|
-
private static boundHandleNavigationChange;
|
|
16
13
|
static init(): void;
|
|
17
14
|
private static buildPanels;
|
|
18
15
|
private static extractSubPanels;
|
package/js/push-menu.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Static class that manages a push-style side navigation panel. */
|
|
2
1
|
class PushMenu {
|
|
3
2
|
static elements = {
|
|
4
3
|
navigation: null,
|
|
@@ -10,7 +9,6 @@ class PushMenu {
|
|
|
10
9
|
};
|
|
11
10
|
static initialized = false;
|
|
12
11
|
static panelStack = [];
|
|
13
|
-
static boundHandleNavigationChange;
|
|
14
12
|
static init() {
|
|
15
13
|
if (this.initialized) {
|
|
16
14
|
console.warn('PushMenu: Already initialized');
|
|
@@ -21,8 +19,7 @@ class PushMenu {
|
|
|
21
19
|
throw new Error('PushMenu: Required elements not found (.navigation, .push-content)');
|
|
22
20
|
}
|
|
23
21
|
this.buildPanels();
|
|
24
|
-
this.
|
|
25
|
-
this.elements.navigation.addEventListener('change', this.boundHandleNavigationChange);
|
|
22
|
+
this.elements.navigation.addEventListener('change', this.handleNavigationChange);
|
|
26
23
|
this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
|
|
27
24
|
this.initialized = true;
|
|
28
25
|
}
|
|
@@ -120,7 +117,7 @@ class PushMenu {
|
|
|
120
117
|
}
|
|
121
118
|
}, 300);
|
|
122
119
|
}
|
|
123
|
-
static handleNavigationChange() {
|
|
120
|
+
static handleNavigationChange = () => {
|
|
124
121
|
const isPushed = this.elements.content?.classList.contains('pushed') ?? false;
|
|
125
122
|
if (!isPushed) {
|
|
126
123
|
this.elements.content?.addEventListener('click', this.clickNav);
|
|
@@ -130,7 +127,7 @@ class PushMenu {
|
|
|
130
127
|
this.resetPanels();
|
|
131
128
|
}
|
|
132
129
|
this.pushToggle();
|
|
133
|
-
}
|
|
130
|
+
};
|
|
134
131
|
static pushToggle() {
|
|
135
132
|
if (!this.elements.content || !this.elements.menu) {
|
|
136
133
|
throw new Error('PushMenu: Required elements not found (.push-content, .push-menu)');
|
|
@@ -152,13 +149,11 @@ class PushMenu {
|
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
151
|
static clickNav = () => {
|
|
155
|
-
|
|
156
|
-
navigation?.click();
|
|
152
|
+
PushMenu.elements.navigation.click();
|
|
157
153
|
};
|
|
158
154
|
static handleBackdropClick = () => {
|
|
159
155
|
if (PushMenu.isOpen()) {
|
|
160
|
-
|
|
161
|
-
navigation?.click();
|
|
156
|
+
PushMenu.elements.navigation.click();
|
|
162
157
|
}
|
|
163
158
|
};
|
|
164
159
|
static open() {
|
|
@@ -177,7 +172,7 @@ class PushMenu {
|
|
|
177
172
|
static destroy() {
|
|
178
173
|
if (!this.initialized)
|
|
179
174
|
return;
|
|
180
|
-
this.elements.navigation?.removeEventListener('change', this.
|
|
175
|
+
this.elements.navigation?.removeEventListener('change', this.handleNavigationChange);
|
|
181
176
|
this.elements.content?.removeEventListener('click', this.clickNav);
|
|
182
177
|
this.elements.backdrop?.removeEventListener('click', this.handleBackdropClick);
|
|
183
178
|
this.close();
|
package/js/range-slider.d.ts
CHANGED
package/js/range-slider.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Enhances a native range input with a CSS fill-percentage custom property. */
|
|
2
1
|
class RangeSlider {
|
|
3
2
|
input;
|
|
4
3
|
constructor(input) {
|
|
@@ -12,8 +11,8 @@ class RangeSlider {
|
|
|
12
11
|
});
|
|
13
12
|
}
|
|
14
13
|
update() {
|
|
15
|
-
const min = +this.input.min
|
|
16
|
-
const max = +this.input.max
|
|
14
|
+
const min = this.input.min !== '' ? +this.input.min : 0;
|
|
15
|
+
const max = this.input.max !== '' ? +this.input.max : 100;
|
|
17
16
|
const pct = ((+this.input.value - min) / (max - min)) * 100;
|
|
18
17
|
this.input.style.setProperty('--range-fill', `${pct}%`);
|
|
19
18
|
}
|
package/js/scroll.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
/** Options for the Scroll.to() utility. */
|
|
2
1
|
interface ScrollOptions {
|
|
3
2
|
behavior?: ScrollBehavior;
|
|
4
3
|
offset?: number;
|
|
5
4
|
block?: ScrollLogicalPosition;
|
|
6
5
|
}
|
|
7
|
-
/** Static utility for smooth-scrolling to a target element with header offset support. */
|
|
8
6
|
declare class Scroll {
|
|
9
7
|
static to(target: string | Element, options?: ScrollOptions): void;
|
|
10
8
|
}
|
package/js/scroll.js
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
/** Static utility for smooth-scrolling to a target element with header offset support. */
|
|
2
1
|
class Scroll {
|
|
3
2
|
static to(target, options = {}) {
|
|
4
|
-
const
|
|
5
|
-
const offset =
|
|
3
|
+
const fixedHeader = document.querySelector('.main-header');
|
|
4
|
+
const offset = fixedHeader ? fixedHeader.offsetHeight : 0;
|
|
6
5
|
const settings = {
|
|
7
|
-
behavior:
|
|
6
|
+
behavior: 'smooth',
|
|
8
7
|
offset: offset,
|
|
9
|
-
block:
|
|
8
|
+
block: 'start',
|
|
10
9
|
...options
|
|
11
10
|
};
|
|
12
11
|
let el = target instanceof Element ? target : null;
|
|
13
|
-
if (typeof target ===
|
|
12
|
+
if (typeof target === 'string') {
|
|
14
13
|
el = document.querySelector(target);
|
|
15
14
|
}
|
|
16
15
|
if (!el)
|
package/js/scrollbar.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
/** DOM element references for a Scrollbar instance. */
|
|
2
1
|
interface ScrollbarElements {
|
|
3
2
|
viewport: HTMLElement;
|
|
4
3
|
content: HTMLElement;
|
|
5
4
|
track: HTMLElement;
|
|
6
5
|
thumb: HTMLElement;
|
|
7
6
|
}
|
|
8
|
-
/** Custom scrollbar overlay that replaces the native scrollbar with a draggable thumb. */
|
|
9
7
|
declare class Scrollbar {
|
|
10
8
|
private static readonly instances;
|
|
11
9
|
private static activeInstance;
|
|
@@ -23,13 +21,7 @@ declare class Scrollbar {
|
|
|
23
21
|
private activePointerId;
|
|
24
22
|
private startPointerY;
|
|
25
23
|
private startThumbTop;
|
|
26
|
-
private
|
|
27
|
-
private readonly boundPointerUp;
|
|
28
|
-
private readonly boundThumbPointerDown;
|
|
29
|
-
private readonly boundTrackClick;
|
|
30
|
-
private readonly boundViewportScroll;
|
|
31
|
-
private readonly boundUpdateThumb;
|
|
32
|
-
private readonly boundContainerWheel;
|
|
24
|
+
private abortController;
|
|
33
25
|
private constructor();
|
|
34
26
|
private getRequiredElements;
|
|
35
27
|
private getMinThumbHeight;
|
|
@@ -47,4 +39,4 @@ declare class Scrollbar {
|
|
|
47
39
|
static initOne(elementOrSelector: string | HTMLElement): Scrollbar;
|
|
48
40
|
static getInstance(container: HTMLElement): Scrollbar | undefined;
|
|
49
41
|
}
|
|
50
|
-
export { Scrollbar, ScrollbarElements };
|
|
42
|
+
export { Scrollbar, type ScrollbarElements };
|
package/js/scrollbar.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Custom scrollbar overlay that replaces the native scrollbar with a draggable thumb. */
|
|
2
1
|
class Scrollbar {
|
|
3
2
|
static instances = new WeakMap();
|
|
4
3
|
static activeInstance = null;
|
|
@@ -16,13 +15,7 @@ class Scrollbar {
|
|
|
16
15
|
activePointerId = null;
|
|
17
16
|
startPointerY = 0;
|
|
18
17
|
startThumbTop = 0;
|
|
19
|
-
|
|
20
|
-
boundPointerUp;
|
|
21
|
-
boundThumbPointerDown;
|
|
22
|
-
boundTrackClick;
|
|
23
|
-
boundViewportScroll;
|
|
24
|
-
boundUpdateThumb;
|
|
25
|
-
boundContainerWheel;
|
|
18
|
+
abortController = new AbortController();
|
|
26
19
|
constructor(container) {
|
|
27
20
|
this.container = container;
|
|
28
21
|
const elements = this.getRequiredElements(container);
|
|
@@ -31,21 +24,14 @@ class Scrollbar {
|
|
|
31
24
|
this.track = elements.track;
|
|
32
25
|
this.thumb = elements.thumb;
|
|
33
26
|
this.MIN_THUMB_HEIGHT = this.getMinThumbHeight();
|
|
34
|
-
this.
|
|
35
|
-
this.boundPointerUp = this.handlePointerUp.bind(this);
|
|
36
|
-
this.boundThumbPointerDown = this.handleThumbPointerDown.bind(this);
|
|
37
|
-
this.boundTrackClick = this.handleTrackClick.bind(this);
|
|
38
|
-
this.boundViewportScroll = this.updateThumb.bind(this);
|
|
39
|
-
this.boundUpdateThumb = this.updateThumb.bind(this);
|
|
40
|
-
this.boundContainerWheel = this.handleContainerWheel.bind(this);
|
|
41
|
-
this.ro = new ResizeObserver(this.boundUpdateThumb);
|
|
27
|
+
this.ro = new ResizeObserver(this.updateThumb);
|
|
42
28
|
this.attachEventListeners();
|
|
43
29
|
Scrollbar.instances.set(container, this);
|
|
44
30
|
Scrollbar.instanceCount++;
|
|
45
31
|
if (!Scrollbar.globalListenersInstalled) {
|
|
46
32
|
Scrollbar.installGlobalListeners();
|
|
47
33
|
}
|
|
48
|
-
requestAnimationFrame(this.
|
|
34
|
+
requestAnimationFrame(this.updateThumb);
|
|
49
35
|
}
|
|
50
36
|
getRequiredElements(container) {
|
|
51
37
|
const viewport = container.querySelector('.viewport');
|
|
@@ -70,26 +56,27 @@ class Scrollbar {
|
|
|
70
56
|
const ac = new AbortController();
|
|
71
57
|
Scrollbar.globalListenerAbortController = ac;
|
|
72
58
|
document.addEventListener('pointermove', (e) => {
|
|
73
|
-
Scrollbar.activeInstance?.
|
|
59
|
+
Scrollbar.activeInstance?.handlePointerMove(e);
|
|
74
60
|
}, { passive: false, signal: ac.signal });
|
|
75
61
|
document.addEventListener('pointerup', (e) => {
|
|
76
|
-
Scrollbar.activeInstance?.
|
|
62
|
+
Scrollbar.activeInstance?.handlePointerUp(e);
|
|
77
63
|
}, { signal: ac.signal });
|
|
78
64
|
document.addEventListener('pointercancel', (e) => {
|
|
79
|
-
Scrollbar.activeInstance?.
|
|
65
|
+
Scrollbar.activeInstance?.handlePointerUp(e);
|
|
80
66
|
}, { signal: ac.signal });
|
|
81
67
|
Scrollbar.globalListenersInstalled = true;
|
|
82
68
|
}
|
|
83
69
|
attachEventListeners() {
|
|
84
|
-
|
|
85
|
-
this.
|
|
86
|
-
this.
|
|
87
|
-
this.
|
|
70
|
+
const sig = { signal: this.abortController.signal };
|
|
71
|
+
this.viewport.addEventListener('scroll', this.updateThumb, { passive: true, signal: sig.signal });
|
|
72
|
+
this.thumb.addEventListener('pointerdown', this.handleThumbPointerDown, sig);
|
|
73
|
+
this.track.addEventListener('click', this.handleTrackClick, sig);
|
|
74
|
+
this.container.addEventListener('wheel', this.handleContainerWheel, { passive: false, signal: sig.signal });
|
|
75
|
+
window.addEventListener('resize', this.updateThumb, sig);
|
|
88
76
|
this.ro.observe(this.viewport);
|
|
89
77
|
this.ro.observe(this.content);
|
|
90
|
-
window.addEventListener('resize', this.boundUpdateThumb);
|
|
91
78
|
}
|
|
92
|
-
updateThumb() {
|
|
79
|
+
updateThumb = () => {
|
|
93
80
|
const viewportHeight = this.viewport.clientHeight;
|
|
94
81
|
const contentHeight = this.content.scrollHeight;
|
|
95
82
|
const trackHeight = this.track.clientHeight;
|
|
@@ -106,8 +93,8 @@ class Scrollbar {
|
|
|
106
93
|
const scrollRatio = this.viewport.scrollTop / (maxScroll || 1);
|
|
107
94
|
const thumbTop = scrollRatio * (maxThumbTop || 0);
|
|
108
95
|
this.thumb.style.top = `${thumbTop}px`;
|
|
109
|
-
}
|
|
110
|
-
handleThumbPointerDown(e) {
|
|
96
|
+
};
|
|
97
|
+
handleThumbPointerDown = (e) => {
|
|
111
98
|
e.preventDefault();
|
|
112
99
|
this.dragging = true;
|
|
113
100
|
this.activePointerId = e.pointerId;
|
|
@@ -123,8 +110,8 @@ class Scrollbar {
|
|
|
123
110
|
const trackRect = this.track.getBoundingClientRect();
|
|
124
111
|
this.startThumbTop = thumbRect.top - trackRect.top;
|
|
125
112
|
document.body.style.userSelect = 'none';
|
|
126
|
-
}
|
|
127
|
-
handlePointerMove(e) {
|
|
113
|
+
};
|
|
114
|
+
handlePointerMove = (e) => {
|
|
128
115
|
if (!this.dragging || this.activePointerId !== e.pointerId) {
|
|
129
116
|
return;
|
|
130
117
|
}
|
|
@@ -140,8 +127,8 @@ class Scrollbar {
|
|
|
140
127
|
const maxScroll = contentHeight - viewportHeight;
|
|
141
128
|
const scrollRatio = newThumbTop / (maxThumbTop || 1);
|
|
142
129
|
this.viewport.scrollTop = scrollRatio * (maxScroll || 0);
|
|
143
|
-
}
|
|
144
|
-
handlePointerUp(e) {
|
|
130
|
+
};
|
|
131
|
+
handlePointerUp = (e) => {
|
|
145
132
|
if (!this.dragging || this.activePointerId !== e.pointerId) {
|
|
146
133
|
return;
|
|
147
134
|
}
|
|
@@ -155,8 +142,8 @@ class Scrollbar {
|
|
|
155
142
|
this.activePointerId = null;
|
|
156
143
|
Scrollbar.activeInstance = null;
|
|
157
144
|
document.body.style.userSelect = '';
|
|
158
|
-
}
|
|
159
|
-
handleTrackClick(e) {
|
|
145
|
+
};
|
|
146
|
+
handleTrackClick = (e) => {
|
|
160
147
|
if (e.target === this.thumb) {
|
|
161
148
|
return;
|
|
162
149
|
}
|
|
@@ -173,8 +160,8 @@ class Scrollbar {
|
|
|
173
160
|
const scrollRatio = clampedThumbTop / (maxThumbTop || 1);
|
|
174
161
|
const scrollTop = scrollRatio * (maxScroll || 0);
|
|
175
162
|
this.viewport.scrollTo({ top: scrollTop, behavior: 'smooth' });
|
|
176
|
-
}
|
|
177
|
-
handleContainerWheel(e) {
|
|
163
|
+
};
|
|
164
|
+
handleContainerWheel = (e) => {
|
|
178
165
|
const { scrollTop, scrollHeight, clientHeight } = this.viewport;
|
|
179
166
|
const scrollable = scrollHeight > clientHeight;
|
|
180
167
|
const atTop = scrollTop === 0 && e.deltaY < 0;
|
|
@@ -183,13 +170,9 @@ class Scrollbar {
|
|
|
183
170
|
e.preventDefault();
|
|
184
171
|
}
|
|
185
172
|
this.viewport.scrollTop += e.deltaY;
|
|
186
|
-
}
|
|
173
|
+
};
|
|
187
174
|
destroy() {
|
|
188
|
-
this.
|
|
189
|
-
this.thumb.removeEventListener('pointerdown', this.boundThumbPointerDown);
|
|
190
|
-
this.track.removeEventListener('click', this.boundTrackClick);
|
|
191
|
-
this.container.removeEventListener('wheel', this.boundContainerWheel);
|
|
192
|
-
window.removeEventListener('resize', this.boundUpdateThumb);
|
|
175
|
+
this.abortController.abort();
|
|
193
176
|
this.ro.disconnect();
|
|
194
177
|
Scrollbar.instances.delete(this.container);
|
|
195
178
|
if (Scrollbar.activeInstance === this) {
|
package/js/select.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Enhances a native `<select>` element with a custom styled dropdown. */
|
|
2
1
|
declare class Select {
|
|
3
2
|
private readonly element;
|
|
4
3
|
private readonly isMultiselect;
|
|
@@ -6,8 +5,8 @@ declare class Select {
|
|
|
6
5
|
private readonly documentClickHandler;
|
|
7
6
|
constructor(elementOrSelector: string | HTMLSelectElement);
|
|
8
7
|
destroy(): void;
|
|
9
|
-
value(): string | string[]
|
|
10
|
-
static init(elementOrSelector: string | HTMLSelectElement):
|
|
8
|
+
value(): string | string[];
|
|
9
|
+
static init(elementOrSelector: string | HTMLSelectElement): (() => void) | null;
|
|
11
10
|
private static initElement;
|
|
12
11
|
private static closeAllDropdowns;
|
|
13
12
|
private static handleMultiSelect;
|
package/js/select.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** Enhances a native `<select>` element with a custom styled dropdown. */
|
|
2
1
|
class Select {
|
|
3
2
|
element;
|
|
4
3
|
isMultiselect;
|
|
@@ -30,9 +29,6 @@ class Select {
|
|
|
30
29
|
this.dropdown?.classList.remove('open');
|
|
31
30
|
}
|
|
32
31
|
value() {
|
|
33
|
-
if (!this.element) {
|
|
34
|
-
return undefined;
|
|
35
|
-
}
|
|
36
32
|
const selectedValues = Array.from(this.element.options)
|
|
37
33
|
.filter(option => option.selected)
|
|
38
34
|
.map(option => option.value);
|
|
@@ -42,18 +38,18 @@ class Select {
|
|
|
42
38
|
const element = typeof elementOrSelector === 'string'
|
|
43
39
|
? document.querySelector(elementOrSelector)
|
|
44
40
|
: elementOrSelector;
|
|
45
|
-
if (!element)
|
|
41
|
+
if (!element)
|
|
46
42
|
return null;
|
|
47
|
-
}
|
|
48
43
|
const result = Select.initElement(element);
|
|
49
44
|
if (!result)
|
|
50
45
|
return null;
|
|
51
|
-
|
|
46
|
+
const handler = (e) => {
|
|
52
47
|
if (!result.dropdown.contains(e.target)) {
|
|
53
48
|
result.dropdown.classList.remove('open');
|
|
54
49
|
}
|
|
55
|
-
}
|
|
56
|
-
|
|
50
|
+
};
|
|
51
|
+
document.addEventListener('click', handler);
|
|
52
|
+
return () => document.removeEventListener('click', handler);
|
|
57
53
|
}
|
|
58
54
|
static initElement(element) {
|
|
59
55
|
if (!Select.transformSelect(element)) {
|
|
@@ -122,6 +118,7 @@ class Select {
|
|
|
122
118
|
selected.textContent = option.textContent?.trim() || '';
|
|
123
119
|
dropdown.classList.remove('open');
|
|
124
120
|
selectElement.value = option.dataset.value || '';
|
|
121
|
+
selectElement.dispatchEvent(new Event('change'));
|
|
125
122
|
}
|
|
126
123
|
static transformSelect(select) {
|
|
127
124
|
const parent = select.closest('.select-group');
|