@dodlhuat/basix 1.3.2 → 1.3.4
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 +13 -7
- package/css/accordion.scss +0 -5
- package/css/badge.scss +9 -6
- package/css/bottom-sheet.scss +3 -8
- package/css/breadcrumb.scss +6 -15
- package/css/button.scss +63 -16
- package/css/calendar.scss +0 -54
- package/css/card.scss +0 -5
- package/css/carousel.scss +0 -3
- package/css/chart.scss +0 -25
- package/css/chat-bubbles.scss +0 -15
- package/css/checkbox.scss +3 -2
- package/css/chips.scss +13 -7
- package/css/code-viewer.scss +1 -5
- package/css/colors.scss +4 -0
- package/css/context-menu.scss +4 -6
- package/css/datepicker.scss +4 -7
- package/css/docs.scss +0 -8
- package/css/dropdown.scss +15 -1
- package/css/editor.scss +1 -23
- package/css/file-uploader.scss +2 -2
- package/css/flyout-menu.scss +66 -44
- package/css/form.scss +0 -28
- package/css/gallery.scss +2 -3
- package/css/group-picker.scss +5 -35
- package/css/icons.scss +0 -3
- package/css/lightbox.scss +2 -4
- package/css/mixins.scss +8 -0
- package/css/modal.scss +24 -3
- package/css/parameters.scss +6 -1
- package/css/popover.scss +3 -15
- package/css/progress.scss +0 -6
- package/css/push-menu.scss +3 -28
- package/css/radiobutton.scss +2 -1
- package/css/range-slider.scss +1 -7
- package/css/scrollbar.scss +2 -6
- package/css/sidebar-nav.scss +24 -11
- package/css/stepper.scss +0 -4
- package/css/style.css +287 -89
- 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 -1
- package/css/table.scss +0 -4
- package/css/tabs.scss +49 -2
- package/css/timeline.scss +1 -13
- package/css/timepicker.scss +6 -7
- package/css/toast.scss +1 -1
- package/css/tooltip.scss +1 -5
- package/css/tree.scss +1 -1
- package/css/typography.scss +38 -9
- package/css/virtual-dropdown.scss +3 -28
- package/js/bottom-sheet.d.ts +3 -1
- package/js/bottom-sheet.js +26 -27
- package/js/calendar.d.ts +7 -0
- package/js/calendar.js +14 -33
- package/js/carousel.d.ts +2 -0
- package/js/carousel.js +13 -5
- package/js/chart.d.ts +4 -0
- package/js/chart.js +13 -31
- package/js/code-viewer.d.ts +1 -0
- package/js/code-viewer.js +4 -0
- package/js/context-menu.d.ts +9 -2
- package/js/context-menu.js +17 -14
- package/js/datepicker.d.ts +4 -0
- package/js/datepicker.js +26 -11
- package/js/dropdown.d.ts +4 -3
- package/js/dropdown.js +17 -9
- package/js/editor.d.ts +1 -0
- package/js/editor.js +9 -3
- package/js/file-uploader.d.ts +4 -0
- package/js/file-uploader.js +52 -43
- package/js/flyout-menu.d.ts +5 -3
- package/js/flyout-menu.js +23 -46
- package/js/gallery.d.ts +5 -0
- package/js/gallery.js +44 -26
- package/js/group-picker.d.ts +5 -0
- package/js/group-picker.js +12 -17
- package/js/lightbox.d.ts +3 -0
- package/js/lightbox.js +12 -6
- package/js/modal.d.ts +3 -1
- package/js/modal.js +15 -12
- package/js/popover.d.ts +2 -0
- package/js/popover.js +26 -30
- package/js/position.d.ts +2 -0
- package/js/position.js +1 -5
- package/js/push-menu.d.ts +2 -0
- package/js/push-menu.js +22 -35
- package/js/range-slider.d.ts +1 -0
- package/js/range-slider.js +5 -3
- package/js/scroll.d.ts +2 -0
- package/js/scroll.js +1 -0
- package/js/scrollbar.d.ts +2 -0
- package/js/scrollbar.js +24 -36
- package/js/select.d.ts +1 -0
- package/js/select.js +5 -10
- package/js/sidebar-nav.d.ts +12 -0
- package/js/sidebar-nav.js +47 -0
- package/js/stepper.d.ts +2 -0
- package/js/stepper.js +7 -1
- package/js/table.d.ts +4 -0
- package/js/table.js +15 -22
- package/js/tabs.d.ts +2 -0
- package/js/tabs.js +6 -14
- package/js/theme.d.ts +1 -0
- package/js/theme.js +5 -13
- package/js/timepicker.d.ts +3 -0
- package/js/timepicker.js +81 -67
- package/js/toast.d.ts +3 -0
- package/js/toast.js +24 -15
- package/js/tooltip.d.ts +2 -0
- package/js/tooltip.js +21 -19
- package/js/tree.d.ts +3 -0
- package/js/tree.js +13 -0
- package/js/utils.d.ts +1 -3
- package/js/utils.js +0 -3
- package/js/virtual-dropdown.d.ts +3 -0
- package/js/virtual-dropdown.js +25 -0
- package/package.json +2 -2
package/js/modal.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { sanitizeHtml } from './utils.js';
|
|
2
2
|
const CLOSE_ICON = '<div class="icon icon-close close"></div>';
|
|
3
|
+
/** Overlay dialog with optional header, footer, close button, and type variants. */
|
|
3
4
|
class Modal {
|
|
5
|
+
content;
|
|
6
|
+
header;
|
|
7
|
+
footer;
|
|
8
|
+
closeable;
|
|
9
|
+
type;
|
|
10
|
+
template;
|
|
11
|
+
modalWrapper = null;
|
|
4
12
|
constructor(contentOrOptions, header, footer, closeable = true, type = 'default') {
|
|
5
|
-
this.modalWrapper = null;
|
|
6
13
|
if (typeof contentOrOptions === 'object') {
|
|
7
14
|
this.content = contentOrOptions.content;
|
|
8
15
|
this.header = contentOrOptions.header;
|
|
@@ -18,9 +25,6 @@ class Modal {
|
|
|
18
25
|
this.type = type;
|
|
19
26
|
}
|
|
20
27
|
this.template = this.buildTemplate();
|
|
21
|
-
this.hide = this.hide.bind(this);
|
|
22
|
-
this.handleEscape = this.handleEscape.bind(this);
|
|
23
|
-
this.handleBackgroundClick = this.handleBackgroundClick.bind(this);
|
|
24
28
|
}
|
|
25
29
|
show() {
|
|
26
30
|
this.hide();
|
|
@@ -45,11 +49,10 @@ class Modal {
|
|
|
45
49
|
wrapper.classList.add('is-visible');
|
|
46
50
|
});
|
|
47
51
|
}
|
|
48
|
-
hide() {
|
|
52
|
+
hide = () => {
|
|
49
53
|
const wrapper = this.modalWrapper;
|
|
50
54
|
if (!wrapper)
|
|
51
55
|
return;
|
|
52
|
-
// Remove event listeners
|
|
53
56
|
const closeBtn = wrapper.querySelector('.close');
|
|
54
57
|
closeBtn?.removeEventListener('click', this.hide);
|
|
55
58
|
const background = wrapper.querySelector('.modal-background');
|
|
@@ -63,19 +66,19 @@ class Modal {
|
|
|
63
66
|
this.modalWrapper = null;
|
|
64
67
|
}
|
|
65
68
|
}, 300);
|
|
66
|
-
}
|
|
67
|
-
handleEscape(e) {
|
|
69
|
+
};
|
|
70
|
+
handleEscape = (e) => {
|
|
68
71
|
if (e.key === 'Escape') {
|
|
69
72
|
this.hide();
|
|
70
73
|
}
|
|
71
|
-
}
|
|
72
|
-
handleBackgroundClick(e) {
|
|
74
|
+
};
|
|
75
|
+
handleBackgroundClick = (e) => {
|
|
73
76
|
if (e.target?.classList.contains('modal-background')) {
|
|
74
77
|
this.hide();
|
|
75
78
|
}
|
|
76
|
-
}
|
|
79
|
+
};
|
|
77
80
|
buildTemplate() {
|
|
78
|
-
const parts = [
|
|
81
|
+
const parts = [`<div class="modal modal-${this.type}">`];
|
|
79
82
|
if (this.closeable) {
|
|
80
83
|
parts.push(CLOSE_ICON);
|
|
81
84
|
}
|
package/js/popover.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ 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. */
|
|
5
6
|
interface PopoverOptions {
|
|
6
7
|
content: string;
|
|
7
8
|
placement?: PopoverPlacement;
|
|
@@ -15,6 +16,7 @@ interface PopoverOptions {
|
|
|
15
16
|
onOpen?: () => void;
|
|
16
17
|
onClose?: () => void;
|
|
17
18
|
}
|
|
19
|
+
/** Anchored popover triggered by click or hover, with auto-placement and optional arrow. */
|
|
18
20
|
declare class Popover {
|
|
19
21
|
private static openPopovers;
|
|
20
22
|
private static idCounter;
|
package/js/popover.js
CHANGED
|
@@ -1,31 +1,16 @@
|
|
|
1
1
|
import { computePosition } from './position.js';
|
|
2
2
|
import { sanitizeHtml } from './utils.js';
|
|
3
|
-
// Must match $arrow in popover.scss
|
|
4
3
|
const ARROW_SIZE = 6;
|
|
4
|
+
/** Anchored popover triggered by click or hover, with auto-placement and optional arrow. */
|
|
5
5
|
class Popover {
|
|
6
|
+
static openPopovers = new Set();
|
|
7
|
+
static idCounter = 0;
|
|
8
|
+
trigger;
|
|
9
|
+
opts;
|
|
10
|
+
popoverEl = null;
|
|
11
|
+
_isOpen = false;
|
|
12
|
+
hoverTimer = null;
|
|
6
13
|
constructor(triggerEl, options) {
|
|
7
|
-
this.popoverEl = null;
|
|
8
|
-
this._isOpen = false;
|
|
9
|
-
this.hoverTimer = null;
|
|
10
|
-
// ── Event handlers ─────────────────────────────────────────────────────────
|
|
11
|
-
this.onClick = () => { this.toggle(); };
|
|
12
|
-
this.onMouseEnter = () => {
|
|
13
|
-
if (this.hoverTimer !== null)
|
|
14
|
-
clearTimeout(this.hoverTimer);
|
|
15
|
-
this.open();
|
|
16
|
-
};
|
|
17
|
-
this.onMouseLeave = () => {
|
|
18
|
-
this.hoverTimer = window.setTimeout(() => this.close(), 120);
|
|
19
|
-
};
|
|
20
|
-
this.onOutsideClick = (e) => {
|
|
21
|
-
const t = e.target;
|
|
22
|
-
if (!this.popoverEl?.contains(t) && !this.trigger.contains(t))
|
|
23
|
-
this.close();
|
|
24
|
-
};
|
|
25
|
-
this.onEscape = (e) => {
|
|
26
|
-
if (e.key === 'Escape')
|
|
27
|
-
this.close();
|
|
28
|
-
};
|
|
29
14
|
const el = typeof triggerEl === 'string'
|
|
30
15
|
? document.querySelector(triggerEl)
|
|
31
16
|
: triggerEl;
|
|
@@ -47,7 +32,6 @@ class Popover {
|
|
|
47
32
|
};
|
|
48
33
|
this.attachTrigger();
|
|
49
34
|
}
|
|
50
|
-
// ── Public API ─────────────────────────────────────────────────────────────
|
|
51
35
|
get isOpen() { return this._isOpen; }
|
|
52
36
|
open() {
|
|
53
37
|
if (this._isOpen)
|
|
@@ -109,7 +93,6 @@ class Popover {
|
|
|
109
93
|
});
|
|
110
94
|
});
|
|
111
95
|
}
|
|
112
|
-
// ── Build ──────────────────────────────────────────────────────────────────
|
|
113
96
|
buildEl() {
|
|
114
97
|
const id = `popover-${++Popover.idCounter}`;
|
|
115
98
|
const el = document.createElement('div');
|
|
@@ -117,8 +100,6 @@ class Popover {
|
|
|
117
100
|
el.id = id;
|
|
118
101
|
el.setAttribute('role', 'dialog');
|
|
119
102
|
el.setAttribute('data-arrow', String(this.opts.arrow));
|
|
120
|
-
// Wrap plain content in .popover-body so it gets proper padding.
|
|
121
|
-
// Skip wrapping when content already uses structured popover elements.
|
|
122
103
|
const hasStructure = /class="popover-(header|body|footer|menu)/.test(this.opts.content);
|
|
123
104
|
const safeContent = sanitizeHtml(this.opts.content);
|
|
124
105
|
el.innerHTML = hasStructure
|
|
@@ -128,7 +109,6 @@ class Popover {
|
|
|
128
109
|
this.trigger.setAttribute('aria-controls', id);
|
|
129
110
|
return el;
|
|
130
111
|
}
|
|
131
|
-
// ── Positioning ────────────────────────────────────────────────────────────
|
|
132
112
|
reposition() {
|
|
133
113
|
if (!this.popoverEl)
|
|
134
114
|
return;
|
|
@@ -145,6 +125,24 @@ class Popover {
|
|
|
145
125
|
this.popoverEl.style.left = `${left}px`;
|
|
146
126
|
this.popoverEl.style.top = `${top}px`;
|
|
147
127
|
}
|
|
128
|
+
onClick = () => { this.toggle(); };
|
|
129
|
+
onMouseEnter = () => {
|
|
130
|
+
if (this.hoverTimer !== null)
|
|
131
|
+
clearTimeout(this.hoverTimer);
|
|
132
|
+
this.open();
|
|
133
|
+
};
|
|
134
|
+
onMouseLeave = () => {
|
|
135
|
+
this.hoverTimer = window.setTimeout(() => this.close(), 120);
|
|
136
|
+
};
|
|
137
|
+
onOutsideClick = (e) => {
|
|
138
|
+
const t = e.target;
|
|
139
|
+
if (!this.popoverEl?.contains(t) && !this.trigger.contains(t))
|
|
140
|
+
this.close();
|
|
141
|
+
};
|
|
142
|
+
onEscape = (e) => {
|
|
143
|
+
if (e.key === 'Escape')
|
|
144
|
+
this.close();
|
|
145
|
+
};
|
|
148
146
|
attachTrigger() {
|
|
149
147
|
if (this.opts.triggerMode === 'click') {
|
|
150
148
|
this.trigger.addEventListener('click', this.onClick);
|
|
@@ -160,6 +158,4 @@ class Popover {
|
|
|
160
158
|
this.trigger.removeEventListener('mouseleave', this.onMouseLeave);
|
|
161
159
|
}
|
|
162
160
|
}
|
|
163
|
-
Popover.openPopovers = new Set();
|
|
164
|
-
Popover.idCounter = 0;
|
|
165
161
|
export { Popover };
|
package/js/position.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
type Placement = 'top' | 'bottom' | 'left' | 'right';
|
|
6
6
|
type Align = 'start' | 'center' | 'end';
|
|
7
|
+
/** Options accepted by `computePosition`. */
|
|
7
8
|
interface PositionOptions {
|
|
8
9
|
placement: Placement | 'auto';
|
|
9
10
|
align?: Align;
|
|
@@ -11,6 +12,7 @@ interface PositionOptions {
|
|
|
11
12
|
margin?: number;
|
|
12
13
|
arrowSize?: number;
|
|
13
14
|
}
|
|
15
|
+
/** Result returned by `computePosition`. */
|
|
14
16
|
interface PositionResult {
|
|
15
17
|
left: number;
|
|
16
18
|
top: number;
|
package/js/position.js
CHANGED
|
@@ -44,7 +44,6 @@ function computePosition(trigger, floating, opts) {
|
|
|
44
44
|
const placement = opts.placement === 'auto'
|
|
45
45
|
? bestPlacement(trigger, floating, offset)
|
|
46
46
|
: maybeFlip(opts.placement, trigger, floating, offset);
|
|
47
|
-
// Main-axis offset
|
|
48
47
|
let left = 0, top = 0;
|
|
49
48
|
switch (placement) {
|
|
50
49
|
case 'top':
|
|
@@ -60,7 +59,6 @@ function computePosition(trigger, floating, opts) {
|
|
|
60
59
|
left = trigger.right + offset;
|
|
61
60
|
break;
|
|
62
61
|
}
|
|
63
|
-
// Cross-axis alignment
|
|
64
62
|
if (placement === 'top' || placement === 'bottom') {
|
|
65
63
|
switch (align) {
|
|
66
64
|
case 'start':
|
|
@@ -87,13 +85,11 @@ function computePosition(trigger, floating, opts) {
|
|
|
87
85
|
break;
|
|
88
86
|
}
|
|
89
87
|
}
|
|
90
|
-
// Clamp to viewport
|
|
91
88
|
const l = Math.max(margin, Math.min(window.innerWidth - floating.width - margin, left));
|
|
92
89
|
const t = Math.max(margin, Math.min(window.innerHeight - floating.height - margin, top));
|
|
93
|
-
// Arrow offset: keep arrow centred on the trigger even after viewport clamping
|
|
94
90
|
let arrowOffset;
|
|
95
91
|
if (opts.arrowSize !== undefined) {
|
|
96
|
-
const minOff = opts.arrowSize + 8;
|
|
92
|
+
const minOff = opts.arrowSize + 8;
|
|
97
93
|
if (placement === 'top' || placement === 'bottom') {
|
|
98
94
|
const raw = trigger.left + trigger.width / 2 - l;
|
|
99
95
|
arrowOffset = Math.max(minOff, Math.min(floating.width - minOff, raw));
|
package/js/push-menu.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** DOM element references managed by the PushMenu static class. */
|
|
1
2
|
interface PushMenuElements {
|
|
2
3
|
navigation: HTMLElement | null;
|
|
3
4
|
content: HTMLElement | null;
|
|
@@ -6,6 +7,7 @@ interface PushMenuElements {
|
|
|
6
7
|
controlIcon: HTMLElement | null;
|
|
7
8
|
backdrop: HTMLElement | null;
|
|
8
9
|
}
|
|
10
|
+
/** Static class that manages a push-style side navigation panel. */
|
|
9
11
|
declare class PushMenu {
|
|
10
12
|
private static elements;
|
|
11
13
|
private static initialized;
|
package/js/push-menu.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
/** Static class that manages a push-style side navigation panel. */
|
|
1
2
|
class PushMenu {
|
|
3
|
+
static elements = {
|
|
4
|
+
navigation: null,
|
|
5
|
+
content: null,
|
|
6
|
+
menu: null,
|
|
7
|
+
header: null,
|
|
8
|
+
controlIcon: null,
|
|
9
|
+
backdrop: null
|
|
10
|
+
};
|
|
11
|
+
static initialized = false;
|
|
12
|
+
static panelStack = [];
|
|
13
|
+
static boundHandleNavigationChange;
|
|
2
14
|
static init() {
|
|
3
15
|
if (this.initialized) {
|
|
4
16
|
console.warn('PushMenu: Already initialized');
|
|
@@ -14,7 +26,6 @@ class PushMenu {
|
|
|
14
26
|
this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
|
|
15
27
|
this.initialized = true;
|
|
16
28
|
}
|
|
17
|
-
// ─── Panel construction ────────────────────────────────────────────────
|
|
18
29
|
static buildPanels() {
|
|
19
30
|
const menu = this.elements.menu;
|
|
20
31
|
if (!menu)
|
|
@@ -22,18 +33,15 @@ class PushMenu {
|
|
|
22
33
|
const rootUl = menu.querySelector(':scope > ul');
|
|
23
34
|
if (!rootUl)
|
|
24
35
|
return;
|
|
25
|
-
// Wrap root ul in a panel
|
|
26
36
|
const rootPanel = document.createElement('div');
|
|
27
37
|
rootPanel.classList.add('push-menu-panel', 'is-active');
|
|
28
38
|
rootPanel.dataset.level = '0';
|
|
29
39
|
rootPanel.appendChild(rootUl);
|
|
30
40
|
menu.appendChild(rootPanel);
|
|
31
|
-
// Recursively extract nested uls into sibling panels
|
|
32
41
|
this.extractSubPanels(rootPanel, 1);
|
|
33
42
|
this.panelStack = [rootPanel];
|
|
34
43
|
}
|
|
35
44
|
static extractSubPanels(panel, level) {
|
|
36
|
-
// Collect all uls currently in this panel before any mutations
|
|
37
45
|
const uls = Array.from(panel.querySelectorAll('ul'));
|
|
38
46
|
for (const ul of uls) {
|
|
39
47
|
const listItems = Array.from(ul.children);
|
|
@@ -41,14 +49,11 @@ class PushMenu {
|
|
|
41
49
|
const childUl = li.querySelector(':scope > ul');
|
|
42
50
|
if (!childUl)
|
|
43
51
|
continue;
|
|
44
|
-
// Determine label from the immediate anchor child
|
|
45
52
|
const parentAnchor = li.querySelector(':scope > a');
|
|
46
53
|
const title = parentAnchor?.textContent?.trim() ?? '';
|
|
47
|
-
// ── Build sub-panel ──────────────────────────────────────
|
|
48
54
|
const subPanel = document.createElement('div');
|
|
49
55
|
subPanel.classList.add('push-menu-panel');
|
|
50
56
|
subPanel.dataset.level = String(level);
|
|
51
|
-
// Header: back button + breadcrumb title
|
|
52
57
|
const header = document.createElement('div');
|
|
53
58
|
header.classList.add('push-menu-panel-header');
|
|
54
59
|
const backBtn = document.createElement('button');
|
|
@@ -62,15 +67,11 @@ class PushMenu {
|
|
|
62
67
|
header.appendChild(backBtn);
|
|
63
68
|
header.appendChild(titleEl);
|
|
64
69
|
subPanel.appendChild(header);
|
|
65
|
-
// Move the child ul into the sub-panel
|
|
66
70
|
subPanel.appendChild(childUl);
|
|
67
|
-
// Append sub-panel as sibling inside the nav
|
|
68
71
|
this.elements.menu?.appendChild(subPanel);
|
|
69
|
-
// ── Replace anchor with a trigger span in the parent li ──
|
|
70
72
|
const trigger = document.createElement('span');
|
|
71
73
|
trigger.classList.add('push-menu-item');
|
|
72
74
|
trigger.textContent = title;
|
|
73
|
-
// Chevron icon
|
|
74
75
|
const chevron = document.createElement('span');
|
|
75
76
|
chevron.classList.add('push-menu-chevron');
|
|
76
77
|
chevron.setAttribute('aria-hidden', 'true');
|
|
@@ -83,12 +84,10 @@ class PushMenu {
|
|
|
83
84
|
li.prepend(trigger);
|
|
84
85
|
}
|
|
85
86
|
trigger.addEventListener('click', () => PushMenu.openPanel(subPanel));
|
|
86
|
-
// Recurse into the newly created sub-panel
|
|
87
87
|
this.extractSubPanels(subPanel, level + 1);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
-
// ─── Panel navigation ──────────────────────────────────────────────────
|
|
92
91
|
static openPanel(panel) {
|
|
93
92
|
const currentPanel = this.panelStack[this.panelStack.length - 1];
|
|
94
93
|
currentPanel.classList.remove('is-active');
|
|
@@ -109,7 +108,6 @@ class PushMenu {
|
|
|
109
108
|
const menu = this.elements.menu;
|
|
110
109
|
if (!menu)
|
|
111
110
|
return;
|
|
112
|
-
// Wait for the close animation before snapping panels back
|
|
113
111
|
setTimeout(() => {
|
|
114
112
|
const panels = Array.from(menu.querySelectorAll('.push-menu-panel'));
|
|
115
113
|
panels.forEach((panel, index) => {
|
|
@@ -122,7 +120,6 @@ class PushMenu {
|
|
|
122
120
|
}
|
|
123
121
|
}, 300);
|
|
124
122
|
}
|
|
125
|
-
// ─── Open / close ──────────────────────────────────────────────────────
|
|
126
123
|
static handleNavigationChange() {
|
|
127
124
|
const isPushed = this.elements.content?.classList.contains('pushed') ?? false;
|
|
128
125
|
if (!isPushed) {
|
|
@@ -154,6 +151,16 @@ class PushMenu {
|
|
|
154
151
|
}
|
|
155
152
|
}
|
|
156
153
|
}
|
|
154
|
+
static clickNav = () => {
|
|
155
|
+
const navigation = PushMenu.elements.navigation;
|
|
156
|
+
navigation?.click();
|
|
157
|
+
};
|
|
158
|
+
static handleBackdropClick = () => {
|
|
159
|
+
if (PushMenu.isOpen()) {
|
|
160
|
+
const navigation = PushMenu.elements.navigation;
|
|
161
|
+
navigation?.click();
|
|
162
|
+
}
|
|
163
|
+
};
|
|
157
164
|
static open() {
|
|
158
165
|
if (!this.elements.content?.classList.contains('pushed')) {
|
|
159
166
|
this.pushToggle();
|
|
@@ -194,24 +201,4 @@ class PushMenu {
|
|
|
194
201
|
this.elements.backdrop = document.querySelector('.push-menu-backdrop');
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
|
-
PushMenu.elements = {
|
|
198
|
-
navigation: null,
|
|
199
|
-
content: null,
|
|
200
|
-
menu: null,
|
|
201
|
-
header: null,
|
|
202
|
-
controlIcon: null,
|
|
203
|
-
backdrop: null
|
|
204
|
-
};
|
|
205
|
-
PushMenu.initialized = false;
|
|
206
|
-
PushMenu.panelStack = [];
|
|
207
|
-
PushMenu.clickNav = () => {
|
|
208
|
-
const navigation = PushMenu.elements.navigation;
|
|
209
|
-
navigation?.click();
|
|
210
|
-
};
|
|
211
|
-
PushMenu.handleBackdropClick = () => {
|
|
212
|
-
if (PushMenu.isOpen()) {
|
|
213
|
-
const navigation = PushMenu.elements.navigation;
|
|
214
|
-
navigation?.click();
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
204
|
export { PushMenu };
|
package/js/range-slider.d.ts
CHANGED
package/js/range-slider.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
/** Enhances a native range input with a CSS fill-percentage custom property. */
|
|
1
2
|
class RangeSlider {
|
|
3
|
+
input;
|
|
2
4
|
constructor(input) {
|
|
3
|
-
this.handleInput = () => {
|
|
4
|
-
this.update();
|
|
5
|
-
};
|
|
6
5
|
this.input = input;
|
|
7
6
|
this.update();
|
|
8
7
|
this.input.addEventListener('input', this.handleInput);
|
|
@@ -18,6 +17,9 @@ class RangeSlider {
|
|
|
18
17
|
const pct = ((+this.input.value - min) / (max - min)) * 100;
|
|
19
18
|
this.input.style.setProperty('--range-fill', `${pct}%`);
|
|
20
19
|
}
|
|
20
|
+
handleInput = () => {
|
|
21
|
+
this.update();
|
|
22
|
+
};
|
|
21
23
|
destroy() {
|
|
22
24
|
this.input.removeEventListener('input', this.handleInput);
|
|
23
25
|
this.input.style.removeProperty('--range-fill');
|
package/js/scroll.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
/** Options for the Scroll.to() utility. */
|
|
1
2
|
interface ScrollOptions {
|
|
2
3
|
behavior?: ScrollBehavior;
|
|
3
4
|
offset?: number;
|
|
4
5
|
block?: ScrollLogicalPosition;
|
|
5
6
|
}
|
|
7
|
+
/** Static utility for smooth-scrolling to a target element with header offset support. */
|
|
6
8
|
declare class Scroll {
|
|
7
9
|
static to(target: string | Element, options?: ScrollOptions): void;
|
|
8
10
|
}
|
package/js/scroll.js
CHANGED
package/js/scrollbar.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
/** DOM element references for a Scrollbar instance. */
|
|
1
2
|
interface ScrollbarElements {
|
|
2
3
|
viewport: HTMLElement;
|
|
3
4
|
content: HTMLElement;
|
|
4
5
|
track: HTMLElement;
|
|
5
6
|
thumb: HTMLElement;
|
|
6
7
|
}
|
|
8
|
+
/** Custom scrollbar overlay that replaces the native scrollbar with a draggable thumb. */
|
|
7
9
|
declare class Scrollbar {
|
|
8
10
|
private static readonly instances;
|
|
9
11
|
private static activeInstance;
|
package/js/scrollbar.js
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
|
+
/** Custom scrollbar overlay that replaces the native scrollbar with a draggable thumb. */
|
|
1
2
|
class Scrollbar {
|
|
3
|
+
static instances = new WeakMap();
|
|
4
|
+
static activeInstance = null;
|
|
5
|
+
static globalListenersInstalled = false;
|
|
6
|
+
static instanceCount = 0;
|
|
7
|
+
static globalListenerAbortController = null;
|
|
8
|
+
container;
|
|
9
|
+
viewport;
|
|
10
|
+
content;
|
|
11
|
+
track;
|
|
12
|
+
thumb;
|
|
13
|
+
MIN_THUMB_HEIGHT;
|
|
14
|
+
ro;
|
|
15
|
+
dragging = false;
|
|
16
|
+
activePointerId = null;
|
|
17
|
+
startPointerY = 0;
|
|
18
|
+
startThumbTop = 0;
|
|
19
|
+
boundPointerMove;
|
|
20
|
+
boundPointerUp;
|
|
21
|
+
boundThumbPointerDown;
|
|
22
|
+
boundTrackClick;
|
|
23
|
+
boundViewportScroll;
|
|
24
|
+
boundUpdateThumb;
|
|
25
|
+
boundContainerWheel;
|
|
2
26
|
constructor(container) {
|
|
3
|
-
this.dragging = false;
|
|
4
|
-
this.activePointerId = null;
|
|
5
|
-
this.startPointerY = 0;
|
|
6
|
-
this.startThumbTop = 0;
|
|
7
27
|
this.container = container;
|
|
8
|
-
// Query and validate required elements
|
|
9
28
|
const elements = this.getRequiredElements(container);
|
|
10
29
|
this.viewport = elements.viewport;
|
|
11
30
|
this.content = elements.content;
|
|
12
31
|
this.track = elements.track;
|
|
13
32
|
this.thumb = elements.thumb;
|
|
14
|
-
// Get minimum thumb height from CSS variable or use default
|
|
15
33
|
this.MIN_THUMB_HEIGHT = this.getMinThumbHeight();
|
|
16
|
-
// Bind all event handlers once
|
|
17
34
|
this.boundPointerMove = this.handlePointerMove.bind(this);
|
|
18
35
|
this.boundPointerUp = this.handlePointerUp.bind(this);
|
|
19
36
|
this.boundThumbPointerDown = this.handleThumbPointerDown.bind(this);
|
|
@@ -21,17 +38,13 @@ class Scrollbar {
|
|
|
21
38
|
this.boundViewportScroll = this.updateThumb.bind(this);
|
|
22
39
|
this.boundUpdateThumb = this.updateThumb.bind(this);
|
|
23
40
|
this.boundContainerWheel = this.handleContainerWheel.bind(this);
|
|
24
|
-
// Setup ResizeObserver
|
|
25
41
|
this.ro = new ResizeObserver(this.boundUpdateThumb);
|
|
26
|
-
// Initialize
|
|
27
42
|
this.attachEventListeners();
|
|
28
43
|
Scrollbar.instances.set(container, this);
|
|
29
|
-
// Track instances and install global listeners once for all
|
|
30
44
|
Scrollbar.instanceCount++;
|
|
31
45
|
if (!Scrollbar.globalListenersInstalled) {
|
|
32
46
|
Scrollbar.installGlobalListeners();
|
|
33
47
|
}
|
|
34
|
-
// Initial thumb update
|
|
35
48
|
requestAnimationFrame(this.boundUpdateThumb);
|
|
36
49
|
}
|
|
37
50
|
getRequiredElements(container) {
|
|
@@ -68,12 +81,10 @@ class Scrollbar {
|
|
|
68
81
|
Scrollbar.globalListenersInstalled = true;
|
|
69
82
|
}
|
|
70
83
|
attachEventListeners() {
|
|
71
|
-
// Instance-specific events
|
|
72
84
|
this.viewport.addEventListener('scroll', this.boundViewportScroll, { passive: true });
|
|
73
85
|
this.thumb.addEventListener('pointerdown', this.boundThumbPointerDown);
|
|
74
86
|
this.track.addEventListener('click', this.boundTrackClick);
|
|
75
87
|
this.container.addEventListener('wheel', this.boundContainerWheel, { passive: false });
|
|
76
|
-
// Observe size changes
|
|
77
88
|
this.ro.observe(this.viewport);
|
|
78
89
|
this.ro.observe(this.content);
|
|
79
90
|
window.addEventListener('resize', this.boundUpdateThumb);
|
|
@@ -82,17 +93,14 @@ class Scrollbar {
|
|
|
82
93
|
const viewportHeight = this.viewport.clientHeight;
|
|
83
94
|
const contentHeight = this.content.scrollHeight;
|
|
84
95
|
const trackHeight = this.track.clientHeight;
|
|
85
|
-
// Hide thumb if content fits in viewport
|
|
86
96
|
if (contentHeight <= viewportHeight + 1) {
|
|
87
97
|
this.thumb.style.display = 'none';
|
|
88
98
|
return;
|
|
89
99
|
}
|
|
90
100
|
this.thumb.style.display = '';
|
|
91
|
-
// Calculate thumb size
|
|
92
101
|
const ratio = viewportHeight / contentHeight;
|
|
93
102
|
const thumbHeight = Math.max(Math.floor(ratio * trackHeight), this.MIN_THUMB_HEIGHT);
|
|
94
103
|
this.thumb.style.height = `${thumbHeight}px`;
|
|
95
|
-
// Calculate thumb position
|
|
96
104
|
const maxScroll = contentHeight - viewportHeight;
|
|
97
105
|
const maxThumbTop = trackHeight - thumbHeight;
|
|
98
106
|
const scrollRatio = this.viewport.scrollTop / (maxScroll || 1);
|
|
@@ -104,7 +112,6 @@ class Scrollbar {
|
|
|
104
112
|
this.dragging = true;
|
|
105
113
|
this.activePointerId = e.pointerId;
|
|
106
114
|
Scrollbar.activeInstance = this;
|
|
107
|
-
// Capture pointer for reliable tracking
|
|
108
115
|
try {
|
|
109
116
|
this.thumb.setPointerCapture(e.pointerId);
|
|
110
117
|
}
|
|
@@ -115,11 +122,9 @@ class Scrollbar {
|
|
|
115
122
|
const thumbRect = this.thumb.getBoundingClientRect();
|
|
116
123
|
const trackRect = this.track.getBoundingClientRect();
|
|
117
124
|
this.startThumbTop = thumbRect.top - trackRect.top;
|
|
118
|
-
// Prevent text selection during drag
|
|
119
125
|
document.body.style.userSelect = 'none';
|
|
120
126
|
}
|
|
121
127
|
handlePointerMove(e) {
|
|
122
|
-
// Only handle events for the active pointer
|
|
123
128
|
if (!this.dragging || this.activePointerId !== e.pointerId) {
|
|
124
129
|
return;
|
|
125
130
|
}
|
|
@@ -128,10 +133,8 @@ class Scrollbar {
|
|
|
128
133
|
const trackHeight = this.track.clientHeight;
|
|
129
134
|
const thumbHeight = this.thumb.clientHeight;
|
|
130
135
|
const maxThumbTop = trackHeight - thumbHeight;
|
|
131
|
-
// Calculate new thumb position
|
|
132
136
|
const newThumbTop = Math.max(0, Math.min(maxThumbTop, this.startThumbTop + pointerDelta));
|
|
133
137
|
this.thumb.style.top = `${newThumbTop}px`;
|
|
134
|
-
// Update viewport scroll position
|
|
135
138
|
const contentHeight = this.content.scrollHeight;
|
|
136
139
|
const viewportHeight = this.viewport.clientHeight;
|
|
137
140
|
const maxScroll = contentHeight - viewportHeight;
|
|
@@ -143,7 +146,6 @@ class Scrollbar {
|
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
145
148
|
this.dragging = false;
|
|
146
|
-
// Release pointer capture
|
|
147
149
|
try {
|
|
148
150
|
this.thumb.releasePointerCapture(e.pointerId);
|
|
149
151
|
}
|
|
@@ -155,7 +157,6 @@ class Scrollbar {
|
|
|
155
157
|
document.body.style.userSelect = '';
|
|
156
158
|
}
|
|
157
159
|
handleTrackClick(e) {
|
|
158
|
-
// Ignore clicks directly on the thumb
|
|
159
160
|
if (e.target === this.thumb) {
|
|
160
161
|
return;
|
|
161
162
|
}
|
|
@@ -163,11 +164,9 @@ class Scrollbar {
|
|
|
163
164
|
const clickY = e.clientY - trackRect.top;
|
|
164
165
|
const thumbHeight = this.thumb.clientHeight;
|
|
165
166
|
const trackHeight = this.track.clientHeight;
|
|
166
|
-
// Center thumb on click position
|
|
167
167
|
const targetThumbTop = clickY - thumbHeight / 2;
|
|
168
168
|
const maxThumbTop = trackHeight - thumbHeight;
|
|
169
169
|
const clampedThumbTop = Math.max(0, Math.min(maxThumbTop, targetThumbTop));
|
|
170
|
-
// Calculate corresponding scroll position
|
|
171
170
|
const contentHeight = this.content.scrollHeight;
|
|
172
171
|
const viewportHeight = this.viewport.clientHeight;
|
|
173
172
|
const maxScroll = contentHeight - viewportHeight;
|
|
@@ -186,21 +185,16 @@ class Scrollbar {
|
|
|
186
185
|
this.viewport.scrollTop += e.deltaY;
|
|
187
186
|
}
|
|
188
187
|
destroy() {
|
|
189
|
-
// Remove event listeners
|
|
190
188
|
this.viewport.removeEventListener('scroll', this.boundViewportScroll);
|
|
191
189
|
this.thumb.removeEventListener('pointerdown', this.boundThumbPointerDown);
|
|
192
190
|
this.track.removeEventListener('click', this.boundTrackClick);
|
|
193
191
|
this.container.removeEventListener('wheel', this.boundContainerWheel);
|
|
194
192
|
window.removeEventListener('resize', this.boundUpdateThumb);
|
|
195
|
-
// Disconnect observer
|
|
196
193
|
this.ro.disconnect();
|
|
197
|
-
// Clear from instances map
|
|
198
194
|
Scrollbar.instances.delete(this.container);
|
|
199
|
-
// Clear active instance if this was it
|
|
200
195
|
if (Scrollbar.activeInstance === this) {
|
|
201
196
|
Scrollbar.activeInstance = null;
|
|
202
197
|
}
|
|
203
|
-
// Uninstall global listeners when last instance is destroyed
|
|
204
198
|
Scrollbar.instanceCount--;
|
|
205
199
|
if (Scrollbar.instanceCount === 0) {
|
|
206
200
|
Scrollbar.globalListenerAbortController?.abort();
|
|
@@ -208,7 +202,6 @@ class Scrollbar {
|
|
|
208
202
|
Scrollbar.globalListenersInstalled = false;
|
|
209
203
|
}
|
|
210
204
|
}
|
|
211
|
-
// Static factory methods
|
|
212
205
|
static create(elementOrSelector) {
|
|
213
206
|
const container = typeof elementOrSelector === 'string'
|
|
214
207
|
? document.querySelector(elementOrSelector)
|
|
@@ -232,9 +225,4 @@ class Scrollbar {
|
|
|
232
225
|
return Scrollbar.instances.get(container);
|
|
233
226
|
}
|
|
234
227
|
}
|
|
235
|
-
Scrollbar.instances = new WeakMap();
|
|
236
|
-
Scrollbar.activeInstance = null;
|
|
237
|
-
Scrollbar.globalListenersInstalled = false;
|
|
238
|
-
Scrollbar.instanceCount = 0;
|
|
239
|
-
Scrollbar.globalListenerAbortController = null;
|
|
240
228
|
export { Scrollbar };
|
package/js/select.d.ts
CHANGED