@dodlhuat/basix 1.3.2 → 1.3.3
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 +1 -6
- package/css/bottom-sheet.scss +3 -8
- package/css/breadcrumb.scss +6 -15
- package/css/button.scss +2 -1
- 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 +3 -7
- package/css/code-viewer.scss +1 -5
- package/css/context-menu.scss +4 -6
- package/css/datepicker.scss +4 -7
- package/css/docs.scss +0 -4
- package/css/dropdown.scss +1 -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 +3 -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 +0 -12
- package/css/stepper.scss +0 -4
- package/css/style.css +63 -68
- 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 +0 -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 +3 -3
- 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 +3 -3
- package/js/dropdown.js +6 -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 +3 -0
- package/js/gallery.js +22 -24
- 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 +14 -11
- 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 +2 -0
- package/js/sidebar-nav.js +8 -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/tabs.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
/** Tabbed content component with horizontal/vertical layouts and keyboard navigation. */
|
|
1
2
|
class Tabs {
|
|
3
|
+
container;
|
|
4
|
+
options;
|
|
5
|
+
tabItems;
|
|
6
|
+
tabPanels;
|
|
7
|
+
currentTab;
|
|
2
8
|
constructor(elementOrSelector, options = {}) {
|
|
3
9
|
const element = typeof elementOrSelector === 'string'
|
|
4
10
|
? document.querySelector(elementOrSelector)
|
|
@@ -7,7 +13,6 @@ class Tabs {
|
|
|
7
13
|
throw new Error(`Tabs: Element not found for selector "${elementOrSelector}"`);
|
|
8
14
|
}
|
|
9
15
|
this.container = element;
|
|
10
|
-
// Set default options
|
|
11
16
|
const layout = options.layout || 'horizontal';
|
|
12
17
|
this.options = {
|
|
13
18
|
layout,
|
|
@@ -24,13 +29,11 @@ class Tabs {
|
|
|
24
29
|
* Initializes the tabs component
|
|
25
30
|
*/
|
|
26
31
|
init() {
|
|
27
|
-
// Apply layout class
|
|
28
32
|
if (this.options.layout === 'vertical') {
|
|
29
33
|
this.container.classList.add('tabs-vertical');
|
|
30
34
|
}
|
|
31
35
|
this.tabItems = this.container.querySelectorAll('.tab-item');
|
|
32
36
|
this.tabPanels = this.container.querySelectorAll('.tab-panel');
|
|
33
|
-
// Validate that we have tabs and panels
|
|
34
37
|
if (this.tabItems.length === 0) {
|
|
35
38
|
console.warn('No tab items found in container');
|
|
36
39
|
return;
|
|
@@ -54,17 +57,14 @@ class Tabs {
|
|
|
54
57
|
e.preventDefault();
|
|
55
58
|
this.activateTab(index);
|
|
56
59
|
});
|
|
57
|
-
// Add keyboard navigation for accessibility
|
|
58
60
|
item.addEventListener('keydown', (e) => {
|
|
59
61
|
const keyEvent = e;
|
|
60
62
|
this.handleKeyboardNavigation(keyEvent, index);
|
|
61
63
|
});
|
|
62
|
-
// Set ARIA attributes
|
|
63
64
|
item.setAttribute('role', 'tab');
|
|
64
65
|
item.setAttribute('tabindex', index === this.options.defaultTab ? '0' : '-1');
|
|
65
66
|
item.setAttribute('aria-selected', index === this.options.defaultTab ? 'true' : 'false');
|
|
66
67
|
});
|
|
67
|
-
// Set ARIA attributes for panels
|
|
68
68
|
this.tabPanels.forEach((panel, index) => {
|
|
69
69
|
panel.setAttribute('role', 'tabpanel');
|
|
70
70
|
panel.setAttribute('aria-hidden', index === this.options.defaultTab ? 'false' : 'true');
|
|
@@ -125,7 +125,6 @@ class Tabs {
|
|
|
125
125
|
console.warn(`Invalid tab index: ${index}`);
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
|
-
// Remove active class from all
|
|
129
128
|
this.tabItems.forEach((item, i) => {
|
|
130
129
|
item.classList.remove('active');
|
|
131
130
|
item.setAttribute('tabindex', '-1');
|
|
@@ -135,7 +134,6 @@ class Tabs {
|
|
|
135
134
|
panel.classList.remove('active');
|
|
136
135
|
panel.setAttribute('aria-hidden', 'true');
|
|
137
136
|
});
|
|
138
|
-
// Add active class to selected
|
|
139
137
|
this.tabItems[index].classList.add('active');
|
|
140
138
|
this.tabItems[index].setAttribute('tabindex', '0');
|
|
141
139
|
this.tabItems[index].setAttribute('aria-selected', 'true');
|
|
@@ -143,7 +141,6 @@ class Tabs {
|
|
|
143
141
|
this.tabPanels[index].setAttribute('aria-hidden', 'false');
|
|
144
142
|
const previousTab = this.currentTab;
|
|
145
143
|
this.currentTab = index;
|
|
146
|
-
// Call onChange callback if provided
|
|
147
144
|
if (this.options.onChange && previousTab !== index) {
|
|
148
145
|
this.options.onChange(index);
|
|
149
146
|
}
|
|
@@ -153,7 +150,6 @@ class Tabs {
|
|
|
153
150
|
*/
|
|
154
151
|
goToTab(index) {
|
|
155
152
|
this.activateTab(index);
|
|
156
|
-
// Focus the tab for keyboard users
|
|
157
153
|
if (this.tabItems[index]) {
|
|
158
154
|
this.tabItems[index].focus();
|
|
159
155
|
}
|
|
@@ -191,7 +187,6 @@ class Tabs {
|
|
|
191
187
|
tab.classList.add('disabled');
|
|
192
188
|
tab.setAttribute('aria-disabled', 'true');
|
|
193
189
|
tab.style.pointerEvents = 'none';
|
|
194
|
-
// If disabling the current tab, switch to the first enabled tab
|
|
195
190
|
if (index === this.currentTab) {
|
|
196
191
|
const firstEnabled = Array.from(this.tabItems).findIndex((item) => !item.classList.contains('disabled'));
|
|
197
192
|
if (firstEnabled !== -1) {
|
|
@@ -203,14 +198,11 @@ class Tabs {
|
|
|
203
198
|
* Public API: Destroy the tabs instance and clean up
|
|
204
199
|
*/
|
|
205
200
|
destroy() {
|
|
206
|
-
// Remove event listeners by cloning and replacing nodes
|
|
207
201
|
this.tabItems.forEach((item) => {
|
|
208
202
|
const newItem = item.cloneNode(true);
|
|
209
203
|
item.parentNode?.replaceChild(newItem, item);
|
|
210
204
|
});
|
|
211
|
-
// Remove classes
|
|
212
205
|
this.container.classList.remove('tabs-vertical');
|
|
213
|
-
// Remove ARIA attributes
|
|
214
206
|
this.tabItems.forEach((item) => {
|
|
215
207
|
item.removeAttribute('role');
|
|
216
208
|
item.removeAttribute('tabindex');
|
package/js/theme.d.ts
CHANGED
package/js/theme.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
/** Static class for managing light/dark theme switching with system preference and localStorage persistence. */
|
|
1
2
|
class Theme {
|
|
3
|
+
static STORAGE_KEY = 'theme';
|
|
4
|
+
static root;
|
|
5
|
+
static elements = null;
|
|
6
|
+
static mediaQuery = null;
|
|
2
7
|
/**
|
|
3
8
|
* Initializes the theme system with toggle functionality and system preference detection
|
|
4
9
|
*/
|
|
5
10
|
static init() {
|
|
6
11
|
this.root = document.documentElement;
|
|
7
|
-
// Get DOM elements
|
|
8
12
|
const toggleBtn = document.getElementById('theme-toggle');
|
|
9
13
|
const icon = document.getElementById('theme-icon');
|
|
10
14
|
const status = document.getElementById('status');
|
|
11
|
-
// Validate required elements
|
|
12
15
|
if (!toggleBtn || !icon) {
|
|
13
16
|
console.error('Theme toggle: missing DOM elements', { toggleBtn, icon });
|
|
14
17
|
if (status) {
|
|
@@ -17,16 +20,13 @@ class Theme {
|
|
|
17
20
|
return;
|
|
18
21
|
}
|
|
19
22
|
this.elements = { toggleBtn, icon, status };
|
|
20
|
-
// Initialize media query
|
|
21
23
|
if (window.matchMedia) {
|
|
22
24
|
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
23
25
|
}
|
|
24
|
-
// Apply initial theme
|
|
25
26
|
const savedTheme = this.getSavedTheme();
|
|
26
27
|
const systemTheme = this.getSystemTheme();
|
|
27
28
|
const initialTheme = savedTheme || systemTheme;
|
|
28
29
|
this.applyTheme(initialTheme);
|
|
29
|
-
// Bind event listeners
|
|
30
30
|
this.bindToggleClick();
|
|
31
31
|
this.bindKeyboardShortcut();
|
|
32
32
|
this.bindSystemThemeChange();
|
|
@@ -77,10 +77,8 @@ class Theme {
|
|
|
77
77
|
this.root.setAttribute('data-theme', theme);
|
|
78
78
|
const isDark = theme === 'dark';
|
|
79
79
|
const { toggleBtn, icon } = this.elements;
|
|
80
|
-
// Update button state
|
|
81
80
|
toggleBtn.setAttribute('aria-pressed', String(isDark));
|
|
82
81
|
toggleBtn.setAttribute('aria-label', `Switch to ${isDark ? 'light' : 'dark'} mode`);
|
|
83
|
-
// Update icon — SVG sprite via <use> or font icon via class
|
|
84
82
|
const useEl = icon.querySelector('use');
|
|
85
83
|
if (useEl) {
|
|
86
84
|
const iconName = isDark ? icon.dataset.iconDark : icon.dataset.iconLight;
|
|
@@ -151,17 +149,14 @@ class Theme {
|
|
|
151
149
|
if (!this.mediaQuery)
|
|
152
150
|
return;
|
|
153
151
|
const handler = (e) => {
|
|
154
|
-
// Only apply system theme if user hasn't saved a preference
|
|
155
152
|
if (!this.getSavedTheme()) {
|
|
156
153
|
const matches = 'matches' in e ? e.matches : e.matches;
|
|
157
154
|
this.applyTheme(matches ? 'dark' : 'light');
|
|
158
155
|
}
|
|
159
156
|
};
|
|
160
|
-
// Modern API
|
|
161
157
|
if ('addEventListener' in this.mediaQuery) {
|
|
162
158
|
this.mediaQuery.addEventListener('change', handler);
|
|
163
159
|
}
|
|
164
|
-
// Legacy API (deprecated but still supported in older browsers)
|
|
165
160
|
else if ('addListener' in this.mediaQuery) {
|
|
166
161
|
this.mediaQuery.addListener(handler);
|
|
167
162
|
}
|
|
@@ -199,7 +194,4 @@ class Theme {
|
|
|
199
194
|
return this.getSavedTheme() !== null;
|
|
200
195
|
}
|
|
201
196
|
}
|
|
202
|
-
Theme.STORAGE_KEY = 'theme';
|
|
203
|
-
Theme.elements = null;
|
|
204
|
-
Theme.mediaQuery = null;
|
|
205
197
|
export { Theme };
|
package/js/timepicker.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/** A start/end time pair as HH:MM strings. */
|
|
1
2
|
interface TimeSpan {
|
|
2
3
|
start: string;
|
|
3
4
|
end: string;
|
|
4
5
|
}
|
|
6
|
+
/** Configuration options for a TimeSpanPicker instance. */
|
|
5
7
|
interface TimeSpanPickerOptions {
|
|
6
8
|
onChange?: (start: string, end: string) => void;
|
|
7
9
|
defaultStart?: string;
|
|
@@ -9,6 +11,7 @@ interface TimeSpanPickerOptions {
|
|
|
9
11
|
fromString?: string;
|
|
10
12
|
toString?: string;
|
|
11
13
|
}
|
|
14
|
+
/** Interactive time-range picker with a draggable bar and dual time inputs. */
|
|
12
15
|
declare class TimeSpanPicker {
|
|
13
16
|
private container;
|
|
14
17
|
private startTimeInput;
|
package/js/timepicker.js
CHANGED
|
@@ -1,72 +1,20 @@
|
|
|
1
|
+
/** Interactive time-range picker with a draggable bar and dual time inputs. */
|
|
1
2
|
class TimeSpanPicker {
|
|
3
|
+
container;
|
|
4
|
+
startTimeInput;
|
|
5
|
+
endTimeInput;
|
|
6
|
+
onChange;
|
|
7
|
+
uid;
|
|
8
|
+
fromString;
|
|
9
|
+
toLabel;
|
|
10
|
+
pickerEl;
|
|
11
|
+
durationEl;
|
|
12
|
+
barEl;
|
|
13
|
+
barFillEl;
|
|
14
|
+
startHandleEl;
|
|
15
|
+
endHandleEl;
|
|
16
|
+
dragState = null;
|
|
2
17
|
constructor(elementOrSelector, options) {
|
|
3
|
-
this.dragState = null;
|
|
4
|
-
this.handleChange = () => {
|
|
5
|
-
this.updateUI();
|
|
6
|
-
const start = this.startTimeInput.value;
|
|
7
|
-
const end = this.endTimeInput.value;
|
|
8
|
-
if (this.onChange && start && end) {
|
|
9
|
-
this.onChange(start, end);
|
|
10
|
-
}
|
|
11
|
-
};
|
|
12
|
-
this.onStartHandleDown = (e) => {
|
|
13
|
-
e.stopPropagation();
|
|
14
|
-
e.preventDefault();
|
|
15
|
-
this.beginDrag('start');
|
|
16
|
-
};
|
|
17
|
-
this.onEndHandleDown = (e) => {
|
|
18
|
-
e.stopPropagation();
|
|
19
|
-
e.preventDefault();
|
|
20
|
-
this.beginDrag('end');
|
|
21
|
-
};
|
|
22
|
-
this.onFillDown = (e) => {
|
|
23
|
-
if (e.target.classList.contains('timespan-handle'))
|
|
24
|
-
return;
|
|
25
|
-
e.preventDefault();
|
|
26
|
-
const start = this.startTimeInput.value;
|
|
27
|
-
if (!start)
|
|
28
|
-
return;
|
|
29
|
-
const rect = this.barEl.getBoundingClientRect();
|
|
30
|
-
const clickMins = ((e.clientX - rect.left) / rect.width) * 1440;
|
|
31
|
-
this.beginDrag('move', clickMins - this.toMinutes(start), rect);
|
|
32
|
-
};
|
|
33
|
-
this.onPointerMove = (e) => {
|
|
34
|
-
if (!this.dragState)
|
|
35
|
-
return;
|
|
36
|
-
e.preventDefault();
|
|
37
|
-
const { type, barLeft, barWidth, startMins, endMins, clickOffsetMins } = this.dragState;
|
|
38
|
-
const pct = Math.max(0, Math.min(1, (e.clientX - barLeft) / barWidth));
|
|
39
|
-
const rawMins = pct * 1440;
|
|
40
|
-
let start = this.startTimeInput.value;
|
|
41
|
-
let end = this.endTimeInput.value;
|
|
42
|
-
if (type === 'start') {
|
|
43
|
-
start = this.minutesToTime(Math.max(0, Math.min(endMins - 5, this.snap(rawMins))));
|
|
44
|
-
this.startTimeInput.value = start;
|
|
45
|
-
}
|
|
46
|
-
else if (type === 'end') {
|
|
47
|
-
end = this.minutesToTime(Math.max(startMins + 5, Math.min(1440, this.snap(rawMins))));
|
|
48
|
-
this.endTimeInput.value = end;
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
const duration = endMins - startMins;
|
|
52
|
-
const newStartMins = Math.max(0, Math.min(1440 - duration, this.snap(rawMins - clickOffsetMins)));
|
|
53
|
-
start = this.minutesToTime(newStartMins);
|
|
54
|
-
end = this.minutesToTime(newStartMins + duration);
|
|
55
|
-
this.startTimeInput.value = start;
|
|
56
|
-
this.endTimeInput.value = end;
|
|
57
|
-
}
|
|
58
|
-
this.updateUI();
|
|
59
|
-
if (this.onChange)
|
|
60
|
-
this.onChange(start, end);
|
|
61
|
-
};
|
|
62
|
-
this.onPointerUp = () => {
|
|
63
|
-
if (!this.dragState)
|
|
64
|
-
return;
|
|
65
|
-
this.dragState = null;
|
|
66
|
-
this.barEl.classList.remove('is-dragging');
|
|
67
|
-
document.removeEventListener('pointermove', this.onPointerMove);
|
|
68
|
-
document.removeEventListener('pointerup', this.onPointerUp);
|
|
69
|
-
};
|
|
70
18
|
const element = typeof elementOrSelector === 'string'
|
|
71
19
|
? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
|
|
72
20
|
? document.querySelector(elementOrSelector)
|
|
@@ -127,6 +75,14 @@ class TimeSpanPicker {
|
|
|
127
75
|
</div>
|
|
128
76
|
`;
|
|
129
77
|
}
|
|
78
|
+
handleChange = () => {
|
|
79
|
+
this.updateUI();
|
|
80
|
+
const start = this.startTimeInput.value;
|
|
81
|
+
const end = this.endTimeInput.value;
|
|
82
|
+
if (this.onChange && start && end) {
|
|
83
|
+
this.onChange(start, end);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
130
86
|
attachEventListeners() {
|
|
131
87
|
this.startTimeInput.addEventListener('change', this.handleChange);
|
|
132
88
|
this.endTimeInput.addEventListener('change', this.handleChange);
|
|
@@ -160,6 +116,64 @@ class TimeSpanPicker {
|
|
|
160
116
|
document.addEventListener('pointermove', this.onPointerMove);
|
|
161
117
|
document.addEventListener('pointerup', this.onPointerUp);
|
|
162
118
|
}
|
|
119
|
+
onStartHandleDown = (e) => {
|
|
120
|
+
e.stopPropagation();
|
|
121
|
+
e.preventDefault();
|
|
122
|
+
this.beginDrag('start');
|
|
123
|
+
};
|
|
124
|
+
onEndHandleDown = (e) => {
|
|
125
|
+
e.stopPropagation();
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
this.beginDrag('end');
|
|
128
|
+
};
|
|
129
|
+
onFillDown = (e) => {
|
|
130
|
+
if (e.target.classList.contains('timespan-handle'))
|
|
131
|
+
return;
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
const start = this.startTimeInput.value;
|
|
134
|
+
if (!start)
|
|
135
|
+
return;
|
|
136
|
+
const rect = this.barEl.getBoundingClientRect();
|
|
137
|
+
const clickMins = ((e.clientX - rect.left) / rect.width) * 1440;
|
|
138
|
+
this.beginDrag('move', clickMins - this.toMinutes(start), rect);
|
|
139
|
+
};
|
|
140
|
+
onPointerMove = (e) => {
|
|
141
|
+
if (!this.dragState)
|
|
142
|
+
return;
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
const { type, barLeft, barWidth, startMins, endMins, clickOffsetMins } = this.dragState;
|
|
145
|
+
const pct = Math.max(0, Math.min(1, (e.clientX - barLeft) / barWidth));
|
|
146
|
+
const rawMins = pct * 1440;
|
|
147
|
+
let start = this.startTimeInput.value;
|
|
148
|
+
let end = this.endTimeInput.value;
|
|
149
|
+
if (type === 'start') {
|
|
150
|
+
start = this.minutesToTime(Math.max(0, Math.min(endMins - 5, this.snap(rawMins))));
|
|
151
|
+
this.startTimeInput.value = start;
|
|
152
|
+
}
|
|
153
|
+
else if (type === 'end') {
|
|
154
|
+
end = this.minutesToTime(Math.max(startMins + 5, Math.min(1440, this.snap(rawMins))));
|
|
155
|
+
this.endTimeInput.value = end;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const duration = endMins - startMins;
|
|
159
|
+
const newStartMins = Math.max(0, Math.min(1440 - duration, this.snap(rawMins - clickOffsetMins)));
|
|
160
|
+
start = this.minutesToTime(newStartMins);
|
|
161
|
+
end = this.minutesToTime(newStartMins + duration);
|
|
162
|
+
this.startTimeInput.value = start;
|
|
163
|
+
this.endTimeInput.value = end;
|
|
164
|
+
}
|
|
165
|
+
this.updateUI();
|
|
166
|
+
if (this.onChange)
|
|
167
|
+
this.onChange(start, end);
|
|
168
|
+
};
|
|
169
|
+
onPointerUp = () => {
|
|
170
|
+
if (!this.dragState)
|
|
171
|
+
return;
|
|
172
|
+
this.dragState = null;
|
|
173
|
+
this.barEl.classList.remove('is-dragging');
|
|
174
|
+
document.removeEventListener('pointermove', this.onPointerMove);
|
|
175
|
+
document.removeEventListener('pointerup', this.onPointerUp);
|
|
176
|
+
};
|
|
163
177
|
toMinutes(time) {
|
|
164
178
|
const [h, m] = time.split(':').map(Number);
|
|
165
179
|
return h * 60 + m;
|
package/js/toast.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
type ToastType = 'success' | 'error' | 'warning' | 'info';
|
|
2
|
+
/** Options for creating a Toast notification. */
|
|
2
3
|
interface ToastOptions {
|
|
3
4
|
content: string;
|
|
4
5
|
header?: string;
|
|
5
6
|
type?: ToastType;
|
|
6
7
|
closeable?: boolean;
|
|
7
8
|
}
|
|
9
|
+
/** Dismissible notification banner with optional auto-hide timer and progress bar. */
|
|
8
10
|
declare class Toast {
|
|
9
11
|
private readonly content;
|
|
10
12
|
private readonly header;
|
|
@@ -19,6 +21,7 @@ declare class Toast {
|
|
|
19
21
|
show(ms?: number): void;
|
|
20
22
|
hide: () => void;
|
|
21
23
|
private startTimer;
|
|
24
|
+
destroy(): void;
|
|
22
25
|
private buildTemplate;
|
|
23
26
|
}
|
|
24
27
|
export { Toast };
|
package/js/toast.js
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
|
+
/** Dismissible notification banner with optional auto-hide timer and progress bar. */
|
|
2
3
|
class Toast {
|
|
4
|
+
content;
|
|
5
|
+
header;
|
|
6
|
+
type;
|
|
7
|
+
closeable;
|
|
8
|
+
closureIcon = '<div class="icon icon-close close"></div>';
|
|
9
|
+
template;
|
|
10
|
+
toastElement = null;
|
|
11
|
+
timerId = null;
|
|
3
12
|
constructor(contentOrOptions, header = '', type, closeable = true) {
|
|
4
|
-
this.closureIcon = '<div class="icon icon-close close"></div>';
|
|
5
|
-
this.toastElement = null;
|
|
6
|
-
this.timerId = null;
|
|
7
|
-
this.hide = () => {
|
|
8
|
-
if (this.timerId !== null) {
|
|
9
|
-
clearTimeout(this.timerId);
|
|
10
|
-
this.timerId = null;
|
|
11
|
-
}
|
|
12
|
-
this.toastElement?.classList.remove('show');
|
|
13
|
-
setTimeout(() => {
|
|
14
|
-
this.toastElement?.querySelector('.close')?.removeEventListener('click', this.hide);
|
|
15
|
-
this.toastElement?.remove();
|
|
16
|
-
this.toastElement = null;
|
|
17
|
-
}, 150);
|
|
18
|
-
};
|
|
19
13
|
if (typeof contentOrOptions === 'object') {
|
|
20
14
|
this.content = contentOrOptions.content;
|
|
21
15
|
this.header = contentOrOptions.header ?? '';
|
|
@@ -52,6 +46,18 @@ class Toast {
|
|
|
52
46
|
});
|
|
53
47
|
});
|
|
54
48
|
}
|
|
49
|
+
hide = () => {
|
|
50
|
+
if (this.timerId !== null) {
|
|
51
|
+
clearTimeout(this.timerId);
|
|
52
|
+
this.timerId = null;
|
|
53
|
+
}
|
|
54
|
+
this.toastElement?.classList.remove('show');
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
this.toastElement?.querySelector('.close')?.removeEventListener('click', this.hide);
|
|
57
|
+
this.toastElement?.remove();
|
|
58
|
+
this.toastElement = null;
|
|
59
|
+
}, 150);
|
|
60
|
+
};
|
|
55
61
|
startTimer(ms, elapsed = 0) {
|
|
56
62
|
const stepSize = 250;
|
|
57
63
|
if (elapsed >= ms) {
|
|
@@ -68,6 +74,9 @@ class Toast {
|
|
|
68
74
|
}
|
|
69
75
|
}, stepSize);
|
|
70
76
|
}
|
|
77
|
+
destroy() {
|
|
78
|
+
this.hide();
|
|
79
|
+
}
|
|
71
80
|
buildTemplate() {
|
|
72
81
|
const parts = ['<div class="bar"></div>'];
|
|
73
82
|
if (this.closeable) {
|
package/js/tooltip.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** Configuration options for a Tooltip instance. */
|
|
1
2
|
interface TooltipOptions {
|
|
2
3
|
position?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
|
|
3
4
|
offset?: number;
|
|
@@ -7,6 +8,7 @@ interface TooltipOptions {
|
|
|
7
8
|
* Defaults to false — content is treated as plain text and escaped. */
|
|
8
9
|
isHtml?: boolean;
|
|
9
10
|
}
|
|
11
|
+
/** Lightweight tooltip that positions itself relative to a trigger element. */
|
|
10
12
|
declare class Tooltip {
|
|
11
13
|
private static activeTooltip;
|
|
12
14
|
private static idCounter;
|
package/js/tooltip.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
// tooltip.ts
|
|
2
1
|
import { computePosition } from './position.js';
|
|
2
|
+
/** Lightweight tooltip that positions itself relative to a trigger element. */
|
|
3
3
|
class Tooltip {
|
|
4
|
+
static activeTooltip = null;
|
|
5
|
+
static idCounter = 0;
|
|
6
|
+
trigger;
|
|
7
|
+
content;
|
|
8
|
+
options;
|
|
9
|
+
tooltipElement = null;
|
|
10
|
+
showTimeout = null;
|
|
11
|
+
isVisible = false;
|
|
4
12
|
constructor(trigger, content, options = {}) {
|
|
5
|
-
this.tooltipElement = null;
|
|
6
|
-
this.showTimeout = null;
|
|
7
|
-
this.isVisible = false;
|
|
8
|
-
this.handleMouseEnter = () => {
|
|
9
|
-
this.show();
|
|
10
|
-
};
|
|
11
|
-
this.handleMouseLeave = () => {
|
|
12
|
-
this.hide();
|
|
13
|
-
};
|
|
14
|
-
this.handleFocus = () => {
|
|
15
|
-
this.show();
|
|
16
|
-
};
|
|
17
|
-
this.handleBlur = () => {
|
|
18
|
-
this.hide();
|
|
19
|
-
};
|
|
20
13
|
this.trigger = trigger;
|
|
21
14
|
this.content = content;
|
|
22
15
|
this.options = {
|
|
@@ -38,7 +31,6 @@ class Tooltip {
|
|
|
38
31
|
new Tooltip(trigger, content, { position, className, isHtml: false });
|
|
39
32
|
}
|
|
40
33
|
});
|
|
41
|
-
// Also support content from separate elements
|
|
42
34
|
const advancedTriggers = document.querySelectorAll('[data-tooltip-id]');
|
|
43
35
|
advancedTriggers.forEach(trigger => {
|
|
44
36
|
const contentId = trigger.getAttribute('data-tooltip-id');
|
|
@@ -127,6 +119,18 @@ class Tooltip {
|
|
|
127
119
|
this.trigger.addEventListener('focus', this.handleFocus);
|
|
128
120
|
this.trigger.addEventListener('blur', this.handleBlur);
|
|
129
121
|
}
|
|
122
|
+
handleMouseEnter = () => {
|
|
123
|
+
this.show();
|
|
124
|
+
};
|
|
125
|
+
handleMouseLeave = () => {
|
|
126
|
+
this.hide();
|
|
127
|
+
};
|
|
128
|
+
handleFocus = () => {
|
|
129
|
+
this.show();
|
|
130
|
+
};
|
|
131
|
+
handleBlur = () => {
|
|
132
|
+
this.hide();
|
|
133
|
+
};
|
|
130
134
|
destroy() {
|
|
131
135
|
this.hide();
|
|
132
136
|
this.trigger.removeEventListener('mouseenter', this.handleMouseEnter);
|
|
@@ -143,6 +147,4 @@ class Tooltip {
|
|
|
143
147
|
this.trigger.removeAttribute('data-previous-describedby');
|
|
144
148
|
}
|
|
145
149
|
}
|
|
146
|
-
Tooltip.activeTooltip = null;
|
|
147
|
-
Tooltip.idCounter = 0;
|
|
148
150
|
export { Tooltip };
|
package/js/tree.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
type NodeType = 'file' | 'folder';
|
|
2
|
+
/** Configuration options for a TreeComponent instance. */
|
|
2
3
|
interface TreeOptions {
|
|
3
4
|
onSelect?: (node: TreeNode) => void;
|
|
4
5
|
}
|
|
6
|
+
/** Represents a single node in a tree structure, either a file or folder. */
|
|
5
7
|
declare class TreeNode {
|
|
6
8
|
label: string;
|
|
7
9
|
type: NodeType;
|
|
@@ -12,6 +14,7 @@ declare class TreeNode {
|
|
|
12
14
|
childrenContainer: HTMLUListElement | null;
|
|
13
15
|
constructor(label: string, type?: NodeType, children?: TreeNode[]);
|
|
14
16
|
}
|
|
17
|
+
/** Renders an interactive collapsible tree view from a list of TreeNode objects. */
|
|
15
18
|
declare class TreeComponent {
|
|
16
19
|
private container;
|
|
17
20
|
private data;
|
package/js/tree.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
/** Represents a single node in a tree structure, either a file or folder. */
|
|
1
2
|
class TreeNode {
|
|
3
|
+
label;
|
|
4
|
+
type;
|
|
5
|
+
children;
|
|
6
|
+
expanded;
|
|
7
|
+
selected;
|
|
8
|
+
element;
|
|
9
|
+
childrenContainer;
|
|
2
10
|
constructor(label, type = 'file', children = []) {
|
|
3
11
|
this.label = label;
|
|
4
12
|
this.type = type;
|
|
@@ -9,7 +17,12 @@ class TreeNode {
|
|
|
9
17
|
this.childrenContainer = null;
|
|
10
18
|
}
|
|
11
19
|
}
|
|
20
|
+
/** Renders an interactive collapsible tree view from a list of TreeNode objects. */
|
|
12
21
|
class TreeComponent {
|
|
22
|
+
container;
|
|
23
|
+
data;
|
|
24
|
+
selectedNode;
|
|
25
|
+
options;
|
|
13
26
|
constructor(elementOrSelector, data, options = {}) {
|
|
14
27
|
const container = typeof elementOrSelector === 'string'
|
|
15
28
|
? document.querySelector(elementOrSelector)
|
package/js/utils.d.ts
CHANGED
package/js/utils.js
CHANGED
package/js/virtual-dropdown.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/** A single selectable option for a VirtualDropdown. */
|
|
1
2
|
interface DropdownOption {
|
|
2
3
|
label: string;
|
|
3
4
|
value: string | number;
|
|
4
5
|
}
|
|
6
|
+
/** Configuration for a VirtualDropdown instance. */
|
|
5
7
|
interface VirtualDropdownConfig {
|
|
6
8
|
container: string | HTMLElement;
|
|
7
9
|
options: DropdownOption[];
|
|
@@ -12,6 +14,7 @@ interface VirtualDropdownConfig {
|
|
|
12
14
|
itemHeight?: number;
|
|
13
15
|
onSelect?: (selectedValues: Array<string | number>) => void;
|
|
14
16
|
}
|
|
17
|
+
/** Virtualised dropdown that renders only visible items for performance with large option lists. */
|
|
15
18
|
declare class VirtualDropdown {
|
|
16
19
|
private readonly container;
|
|
17
20
|
private readonly options;
|
package/js/virtual-dropdown.js
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import { escapeHtml } from './utils.js';
|
|
2
|
+
/** Virtualised dropdown that renders only visible items for performance with large option lists. */
|
|
2
3
|
class VirtualDropdown {
|
|
4
|
+
container;
|
|
5
|
+
options;
|
|
6
|
+
multiSelect;
|
|
7
|
+
searchable;
|
|
8
|
+
placeholder;
|
|
9
|
+
renderLimit;
|
|
10
|
+
itemHeight;
|
|
11
|
+
onSelect;
|
|
12
|
+
// Unique CSS anchor name for this instance — prevents conflicts when
|
|
13
|
+
// multiple dropdowns exist on the same page.
|
|
14
|
+
anchorName;
|
|
15
|
+
trigger;
|
|
16
|
+
triggerText;
|
|
17
|
+
menu;
|
|
18
|
+
listWrapper;
|
|
19
|
+
scroller;
|
|
20
|
+
spacer;
|
|
21
|
+
content;
|
|
22
|
+
searchInput;
|
|
23
|
+
selectedValues;
|
|
24
|
+
filteredOptions;
|
|
25
|
+
isOpen;
|
|
26
|
+
scrollTop;
|
|
27
|
+
boundHandlers;
|
|
3
28
|
constructor(config) {
|
|
4
29
|
const containerElement = typeof config.container === 'string'
|
|
5
30
|
? document.querySelector(config.container)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dodlhuat/basix",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Basix is intended as a starter for the rapid development of a design. Each design element can be added individually to include only the data required. It is using plain javascript / typescript and therefore is not dependent on any plugin.",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./css/*": "./css/*",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
},
|
|
34
34
|
"homepage": "https://github.com/dodlhuat/basix#readme",
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"typescript": "^
|
|
36
|
+
"typescript": "^6.0.0"
|
|
37
37
|
}
|
|
38
38
|
}
|