@dodlhuat/basix 1.0.0 → 1.1.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 +1 -1
- package/css/accordion.scss +31 -22
- package/css/alert.scss +79 -27
- package/css/button.scss +151 -102
- package/css/card.scss +11 -12
- package/css/carousel.scss +123 -87
- package/css/chart.scss +9 -11
- package/css/chat-bubbles.scss +2 -2
- package/css/checkbox.scss +72 -55
- package/css/chips.scss +52 -52
- package/css/code-viewer.scss +73 -98
- package/css/datepicker.scss +20 -0
- package/css/dropdown.scss +151 -137
- package/css/editor.scss +9 -6
- package/css/file-uploader.scss +187 -195
- package/css/flyout-menu.scss +20 -13
- package/css/form.scss +168 -115
- package/css/gallery.scss +62 -63
- package/css/grid.scss +0 -1
- package/css/modal.scss +117 -72
- package/css/placeholder.scss +17 -12
- package/css/properties.scss +6 -0
- package/css/push-menu.scss +70 -23
- package/css/radiobutton.scss +86 -64
- package/css/range-slider.scss +136 -0
- package/css/scrollbar.scss +69 -69
- package/css/spinner.scss +41 -66
- package/css/style.css +4351 -3735
- package/css/style.css.map +1 -1
- package/css/style.scss +2 -1
- package/css/switch.scss +43 -42
- package/css/table.scss +61 -40
- package/css/tabs.scss +12 -7
- package/css/timeline.scss +72 -69
- package/css/timepicker.scss +151 -72
- package/css/toast.scss +49 -48
- package/css/tooltip.scss +112 -122
- package/css/tree.scss +135 -192
- package/css/typography.scss +70 -9
- package/css/virtual-dropdown.scss +201 -142
- package/js/carousel.js +45 -18
- package/js/carousel.ts +217 -173
- package/js/datepicker.js +505 -497
- package/js/datepicker.ts +9 -0
- package/js/editor.js +398 -415
- package/js/file-uploader.js +142 -128
- package/js/file-uploader.ts +364 -350
- package/js/gallery.js +22 -15
- package/js/gallery.ts +17 -12
- package/js/index.js +718 -720
- package/js/index.ts +7 -8
- package/js/push-menu.js +113 -101
- package/js/push-menu.ts +17 -2
- package/js/range-slider.js +26 -0
- package/js/range-slider.ts +33 -0
- package/js/timepicker.js +144 -98
- package/js/timepicker.ts +194 -131
- package/js/tree.js +56 -28
- package/js/tree.ts +239 -218
- package/package.json +3 -2
- package/css/accordion.css +0 -109
- package/css/accordion.css.map +0 -1
- package/css/alert.css +0 -57
- package/css/alert.css.map +0 -1
- package/css/button.css +0 -69
- package/css/button.css.map +0 -1
- package/css/card.css +0 -144
- package/css/card.css.map +0 -1
- package/css/carousel.css +0 -118
- package/css/carousel.css.map +0 -1
- package/css/chart.css +0 -159
- package/css/chart.css.map +0 -1
- package/css/chat-bubbles.css +0 -97
- package/css/chat-bubbles.css.map +0 -1
- package/css/checkbox.css +0 -77
- package/css/checkbox.css.map +0 -1
- package/css/chips.css +0 -72
- package/css/chips.css.map +0 -1
- package/css/code-viewer.css +0 -97
- package/css/code-viewer.css.map +0 -1
- package/css/colors.css +0 -63
- package/css/colors.css.map +0 -1
- package/css/datepicker.css +0 -264
- package/css/datepicker.css.map +0 -1
- package/css/defaults.css +0 -118
- package/css/defaults.css.map +0 -1
- package/css/dropdown.css +0 -146
- package/css/dropdown.css.map +0 -1
- package/css/editor.css +0 -413
- package/css/file-uploader.css +0 -194
- package/css/file-uploader.css.map +0 -1
- package/css/flyout-menu.css +0 -345
- package/css/flyout-menu.css.map +0 -1
- package/css/form-builder.css +0 -9
- package/css/form-builder.css.map +0 -1
- package/css/form-builder.scss +0 -11
- package/css/form.css +0 -130
- package/css/form.css.map +0 -1
- package/css/gallery.css +0 -91
- package/css/gallery.css.map +0 -1
- package/css/grid.css +0 -44
- package/css/grid.css.map +0 -1
- package/css/icons.css +0 -327
- package/css/icons.css.map +0 -1
- package/css/modal.css +0 -97
- package/css/modal.css.map +0 -1
- package/css/parameters.css +0 -1
- package/css/parameters.css.map +0 -1
- package/css/placeholder.css +0 -50
- package/css/placeholder.css.map +0 -1
- package/css/progress.css +0 -51
- package/css/progress.css.map +0 -1
- package/css/properties.css +0 -31
- package/css/properties.css.map +0 -1
- package/css/push-menu.css +0 -145
- package/css/push-menu.css.map +0 -1
- package/css/radiobutton.css +0 -91
- package/css/radiobutton.css.map +0 -1
- package/css/reset.css +0 -46
- package/css/reset.css.map +0 -1
- package/css/scrollbar.css +0 -91
- package/css/scrollbar.css.map +0 -1
- package/css/spinner.css +0 -118
- package/css/spinner.css.map +0 -1
- package/css/switch.css +0 -66
- package/css/switch.css.map +0 -1
- package/css/table.css +0 -201
- package/css/table.css.map +0 -1
- package/css/tabs.css +0 -135
- package/css/tabs.css.map +0 -1
- package/css/timeline.css +0 -69
- package/css/timeline.css.map +0 -1
- package/css/toast.css +0 -98
- package/css/toast.css.map +0 -1
- package/css/tooltip.css +0 -151
- package/css/tooltip.css.map +0 -1
- package/css/tree.css +0 -199
- package/css/tree.css.map +0 -1
- package/css/typography.css +0 -137
- package/css/typography.css.map +0 -1
- package/css/virtual-dropdown.css +0 -149
- package/css/virtual-dropdown.css.map +0 -1
package/js/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {Tooltip} from "./tooltip.js";
|
|
|
17
17
|
import {Dropdown, DropdownSelectDetail} from "./dropdown.js";
|
|
18
18
|
import {VirtualDropdown} from "./virtual-dropdown.js";
|
|
19
19
|
import {TimeSpanPicker} from "./timepicker.js";
|
|
20
|
+
import {RangeSlider} from "./range-slider.js";
|
|
20
21
|
|
|
21
22
|
// Generate sample table data
|
|
22
23
|
const generateData = (count: number): TableRow[] => {
|
|
@@ -349,6 +350,9 @@ utils.ready(() => {
|
|
|
349
350
|
},
|
|
350
351
|
});
|
|
351
352
|
|
|
353
|
+
// Initialize range sliders
|
|
354
|
+
RangeSlider.initAll();
|
|
355
|
+
|
|
352
356
|
Tooltip.initializeAll();
|
|
353
357
|
|
|
354
358
|
const timeSpanPicker = new TimeSpanPicker('timespan-1', {
|
|
@@ -397,14 +401,9 @@ new CodeViewer(
|
|
|
397
401
|
);
|
|
398
402
|
new CodeViewer(
|
|
399
403
|
"#usage-slider-demo",
|
|
400
|
-
`<
|
|
401
|
-
<input
|
|
402
|
-
|
|
403
|
-
min="1"
|
|
404
|
-
max="100"
|
|
405
|
-
value="50"
|
|
406
|
-
id="range-slider-demo"
|
|
407
|
-
/>`,
|
|
404
|
+
`<div class="range-slider">
|
|
405
|
+
<input type="range" min="1" max="100" value="50" />
|
|
406
|
+
</div>`,
|
|
408
407
|
"html",
|
|
409
408
|
);
|
|
410
409
|
new CodeViewer(
|
package/js/push-menu.js
CHANGED
|
@@ -1,101 +1,113 @@
|
|
|
1
|
-
class PushMenu {
|
|
2
|
-
static init() {
|
|
3
|
-
if (this.initialized) {
|
|
4
|
-
console.warn('PushMenu: Already initialized');
|
|
5
|
-
return;
|
|
6
|
-
}
|
|
7
|
-
this.refresh();
|
|
8
|
-
if (!this.elements.navigation || !this.elements.content) {
|
|
9
|
-
throw new Error('PushMenu: Required elements not found (.navigation, .push-content)');
|
|
10
|
-
}
|
|
11
|
-
this.elements.navigation.addEventListener('change', this.handleNavigationChange.bind(this));
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
this.
|
|
30
|
-
this.toggleClass(this.elements.
|
|
31
|
-
this.toggleClass(this.elements.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.
|
|
72
|
-
this.elements
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
this.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.elements.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
class PushMenu {
|
|
2
|
+
static init() {
|
|
3
|
+
if (this.initialized) {
|
|
4
|
+
console.warn('PushMenu: Already initialized');
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
this.refresh();
|
|
8
|
+
if (!this.elements.navigation || !this.elements.content) {
|
|
9
|
+
throw new Error('PushMenu: Required elements not found (.navigation, .push-content)');
|
|
10
|
+
}
|
|
11
|
+
this.elements.navigation.addEventListener('change', this.handleNavigationChange.bind(this));
|
|
12
|
+
this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
|
|
13
|
+
this.initialized = true;
|
|
14
|
+
}
|
|
15
|
+
static handleNavigationChange() {
|
|
16
|
+
const isPushed = this.elements.content?.classList.contains('pushed') ?? false;
|
|
17
|
+
if (!isPushed) {
|
|
18
|
+
this.elements.content?.addEventListener('click', this.clickNav);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
this.elements.content?.removeEventListener('click', this.clickNav);
|
|
22
|
+
}
|
|
23
|
+
this.pushToggle();
|
|
24
|
+
}
|
|
25
|
+
static pushToggle() {
|
|
26
|
+
if (!this.elements.content || !this.elements.menu) {
|
|
27
|
+
throw new Error('PushMenu: Required elements not found (.push-content, .push-menu)');
|
|
28
|
+
}
|
|
29
|
+
const isPushed = this.elements.content.classList.contains('pushed');
|
|
30
|
+
this.toggleClass(this.elements.content, 'pushed', !isPushed);
|
|
31
|
+
this.toggleClass(this.elements.menu, 'pushed', !isPushed);
|
|
32
|
+
this.toggleClass(this.elements.header, 'pushed', !isPushed);
|
|
33
|
+
this.toggleClass(this.elements.backdrop, 'pushed', !isPushed);
|
|
34
|
+
if (this.elements.controlIcon) {
|
|
35
|
+
if (isPushed) {
|
|
36
|
+
this.elements.controlIcon.classList.remove('icon-menu_open');
|
|
37
|
+
this.elements.controlIcon.classList.add('icon-menu');
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.elements.controlIcon.classList.add('icon-menu_open');
|
|
41
|
+
this.elements.controlIcon.classList.remove('icon-menu');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static toggleClass(element, className, add) {
|
|
46
|
+
if (!element)
|
|
47
|
+
return;
|
|
48
|
+
if (add) {
|
|
49
|
+
element.classList.add(className);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
element.classList.remove(className);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
static open() {
|
|
56
|
+
if (!this.elements.content?.classList.contains('pushed')) {
|
|
57
|
+
this.pushToggle();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
static close() {
|
|
61
|
+
if (this.elements.content?.classList.contains('pushed')) {
|
|
62
|
+
this.pushToggle();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
static isOpen() {
|
|
66
|
+
return this.elements.content?.classList.contains('pushed') ?? false;
|
|
67
|
+
}
|
|
68
|
+
static destroy() {
|
|
69
|
+
if (!this.initialized)
|
|
70
|
+
return;
|
|
71
|
+
this.elements.navigation?.removeEventListener('change', this.handleNavigationChange);
|
|
72
|
+
this.elements.content?.removeEventListener('click', this.clickNav);
|
|
73
|
+
this.elements.backdrop?.removeEventListener('click', this.handleBackdropClick);
|
|
74
|
+
this.close();
|
|
75
|
+
this.elements = {
|
|
76
|
+
navigation: null,
|
|
77
|
+
content: null,
|
|
78
|
+
menu: null,
|
|
79
|
+
header: null,
|
|
80
|
+
controlIcon: null,
|
|
81
|
+
backdrop: null
|
|
82
|
+
};
|
|
83
|
+
this.initialized = false;
|
|
84
|
+
}
|
|
85
|
+
static refresh() {
|
|
86
|
+
this.elements.navigation = document.querySelector('.navigation');
|
|
87
|
+
this.elements.content = document.querySelector('.push-content');
|
|
88
|
+
this.elements.menu = document.querySelector('.push-menu');
|
|
89
|
+
this.elements.header = document.querySelector('.main-header');
|
|
90
|
+
this.elements.controlIcon = document.querySelector('.navigation-controls .icon');
|
|
91
|
+
this.elements.backdrop = document.querySelector('.push-menu-backdrop');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
PushMenu.elements = {
|
|
95
|
+
navigation: null,
|
|
96
|
+
content: null,
|
|
97
|
+
menu: null,
|
|
98
|
+
header: null,
|
|
99
|
+
controlIcon: null,
|
|
100
|
+
backdrop: null
|
|
101
|
+
};
|
|
102
|
+
PushMenu.initialized = false;
|
|
103
|
+
PushMenu.clickNav = () => {
|
|
104
|
+
const navigation = PushMenu.elements.navigation;
|
|
105
|
+
navigation?.click();
|
|
106
|
+
};
|
|
107
|
+
PushMenu.handleBackdropClick = () => {
|
|
108
|
+
if (PushMenu.isOpen()) {
|
|
109
|
+
const navigation = PushMenu.elements.navigation;
|
|
110
|
+
navigation?.click();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
export { PushMenu };
|
package/js/push-menu.ts
CHANGED
|
@@ -4,6 +4,7 @@ interface PushMenuElements {
|
|
|
4
4
|
menu: HTMLElement | null;
|
|
5
5
|
header: HTMLElement | null;
|
|
6
6
|
controlIcon: HTMLElement | null;
|
|
7
|
+
backdrop: HTMLElement | null;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
class PushMenu {
|
|
@@ -12,7 +13,8 @@ class PushMenu {
|
|
|
12
13
|
content: null,
|
|
13
14
|
menu: null,
|
|
14
15
|
header: null,
|
|
15
|
-
controlIcon: null
|
|
16
|
+
controlIcon: null,
|
|
17
|
+
backdrop: null
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
private static initialized = false;
|
|
@@ -31,6 +33,8 @@ class PushMenu {
|
|
|
31
33
|
|
|
32
34
|
this.elements.navigation.addEventListener('change', this.handleNavigationChange.bind(this));
|
|
33
35
|
|
|
36
|
+
this.elements.backdrop?.addEventListener('click', this.handleBackdropClick);
|
|
37
|
+
|
|
34
38
|
this.initialized = true;
|
|
35
39
|
}
|
|
36
40
|
|
|
@@ -56,6 +60,7 @@ class PushMenu {
|
|
|
56
60
|
this.toggleClass(this.elements.content, 'pushed', !isPushed);
|
|
57
61
|
this.toggleClass(this.elements.menu, 'pushed', !isPushed);
|
|
58
62
|
this.toggleClass(this.elements.header, 'pushed', !isPushed);
|
|
63
|
+
this.toggleClass(this.elements.backdrop, 'pushed', !isPushed);
|
|
59
64
|
|
|
60
65
|
if (this.elements.controlIcon) {
|
|
61
66
|
if (isPushed) {
|
|
@@ -83,6 +88,13 @@ class PushMenu {
|
|
|
83
88
|
navigation?.click();
|
|
84
89
|
};
|
|
85
90
|
|
|
91
|
+
private static handleBackdropClick = (): void => {
|
|
92
|
+
if (PushMenu.isOpen()) {
|
|
93
|
+
const navigation = PushMenu.elements.navigation as HTMLElement;
|
|
94
|
+
navigation?.click();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
86
98
|
public static open(): void {
|
|
87
99
|
if (!this.elements.content?.classList.contains('pushed')) {
|
|
88
100
|
this.pushToggle();
|
|
@@ -104,6 +116,7 @@ class PushMenu {
|
|
|
104
116
|
|
|
105
117
|
this.elements.navigation?.removeEventListener('change', this.handleNavigationChange);
|
|
106
118
|
this.elements.content?.removeEventListener('click', this.clickNav);
|
|
119
|
+
this.elements.backdrop?.removeEventListener('click', this.handleBackdropClick);
|
|
107
120
|
|
|
108
121
|
this.close();
|
|
109
122
|
|
|
@@ -112,7 +125,8 @@ class PushMenu {
|
|
|
112
125
|
content: null,
|
|
113
126
|
menu: null,
|
|
114
127
|
header: null,
|
|
115
|
-
controlIcon: null
|
|
128
|
+
controlIcon: null,
|
|
129
|
+
backdrop: null
|
|
116
130
|
};
|
|
117
131
|
|
|
118
132
|
this.initialized = false;
|
|
@@ -124,6 +138,7 @@ class PushMenu {
|
|
|
124
138
|
this.elements.menu = document.querySelector('.push-menu');
|
|
125
139
|
this.elements.header = document.querySelector('.main-header');
|
|
126
140
|
this.elements.controlIcon = document.querySelector('.navigation-controls .icon');
|
|
141
|
+
this.elements.backdrop = document.querySelector('.push-menu-backdrop');
|
|
127
142
|
}
|
|
128
143
|
}
|
|
129
144
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class RangeSlider {
|
|
2
|
+
constructor(input) {
|
|
3
|
+
this.handleInput = () => {
|
|
4
|
+
this.update();
|
|
5
|
+
};
|
|
6
|
+
this.input = input;
|
|
7
|
+
this.update();
|
|
8
|
+
this.input.addEventListener('input', this.handleInput);
|
|
9
|
+
}
|
|
10
|
+
static initAll(selector = '.range-slider input[type="range"]') {
|
|
11
|
+
document.querySelectorAll(selector).forEach(input => {
|
|
12
|
+
new RangeSlider(input);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
update() {
|
|
16
|
+
const min = +this.input.min || 0;
|
|
17
|
+
const max = +this.input.max || 100;
|
|
18
|
+
const pct = ((+this.input.value - min) / (max - min)) * 100;
|
|
19
|
+
this.input.style.setProperty('--range-fill', `${pct}%`);
|
|
20
|
+
}
|
|
21
|
+
destroy() {
|
|
22
|
+
this.input.removeEventListener('input', this.handleInput);
|
|
23
|
+
this.input.style.removeProperty('--range-fill');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export { RangeSlider };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class RangeSlider {
|
|
2
|
+
private readonly input: HTMLInputElement;
|
|
3
|
+
|
|
4
|
+
constructor(input: HTMLInputElement) {
|
|
5
|
+
this.input = input;
|
|
6
|
+
this.update();
|
|
7
|
+
this.input.addEventListener('input', this.handleInput);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public static initAll(selector: string = '.range-slider input[type="range"]'): void {
|
|
11
|
+
document.querySelectorAll<HTMLInputElement>(selector).forEach(input => {
|
|
12
|
+
new RangeSlider(input);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private update(): void {
|
|
17
|
+
const min = +this.input.min || 0;
|
|
18
|
+
const max = +this.input.max || 100;
|
|
19
|
+
const pct = ((+this.input.value - min) / (max - min)) * 100;
|
|
20
|
+
this.input.style.setProperty('--range-fill', `${pct}%`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private handleInput = (): void => {
|
|
24
|
+
this.update();
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
public destroy(): void {
|
|
28
|
+
this.input.removeEventListener('input', this.handleInput);
|
|
29
|
+
this.input.style.removeProperty('--range-fill');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export { RangeSlider };
|
package/js/timepicker.js
CHANGED
|
@@ -1,98 +1,144 @@
|
|
|
1
|
-
class TimeSpanPicker {
|
|
2
|
-
constructor(containerId, options) {
|
|
3
|
-
const element = document.getElementById(containerId);
|
|
4
|
-
if (!element) {
|
|
5
|
-
throw new Error(`Container with id "${containerId}" not found`);
|
|
6
|
-
}
|
|
7
|
-
this.container = element;
|
|
8
|
-
this.onChange = options?.onChange;
|
|
9
|
-
this.render();
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.startTimeInput.value
|
|
81
|
-
this.endTimeInput.value
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
1
|
+
class TimeSpanPicker {
|
|
2
|
+
constructor(containerId, options) {
|
|
3
|
+
const element = document.getElementById(containerId);
|
|
4
|
+
if (!element) {
|
|
5
|
+
throw new Error(`Container with id "${containerId}" not found`);
|
|
6
|
+
}
|
|
7
|
+
this.container = element;
|
|
8
|
+
this.onChange = options?.onChange;
|
|
9
|
+
this.render();
|
|
10
|
+
this.startTimeInput = this.queryInput('.timespan-start');
|
|
11
|
+
this.endTimeInput = this.queryInput('.timespan-end');
|
|
12
|
+
if (options?.defaultStart) {
|
|
13
|
+
this.startTimeInput.value = options.defaultStart;
|
|
14
|
+
}
|
|
15
|
+
if (options?.defaultEnd) {
|
|
16
|
+
this.endTimeInput.value = options.defaultEnd;
|
|
17
|
+
}
|
|
18
|
+
this.attachEventListeners();
|
|
19
|
+
if (options?.defaultStart || options?.defaultEnd) {
|
|
20
|
+
this.updateUI();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
queryInput(selector) {
|
|
24
|
+
const input = this.container.querySelector(selector);
|
|
25
|
+
if (!input) {
|
|
26
|
+
throw new Error(`Input with selector "${selector}" not found`);
|
|
27
|
+
}
|
|
28
|
+
return input;
|
|
29
|
+
}
|
|
30
|
+
render() {
|
|
31
|
+
this.container.innerHTML = `
|
|
32
|
+
<div class="timespan-picker">
|
|
33
|
+
<div class="timespan-field timespan-field-start">
|
|
34
|
+
<label for="timespan-start">From</label>
|
|
35
|
+
<input
|
|
36
|
+
type="time"
|
|
37
|
+
class="timespan-start"
|
|
38
|
+
id="timespan-start"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="timespan-center">
|
|
43
|
+
<span class="timespan-arrow">→</span>
|
|
44
|
+
<span class="timespan-duration"></span>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="timespan-field timespan-field-end">
|
|
48
|
+
<label for="timespan-end">To</label>
|
|
49
|
+
<input
|
|
50
|
+
type="time"
|
|
51
|
+
class="timespan-end"
|
|
52
|
+
id="timespan-end"
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="timespan-bar" aria-hidden="true">
|
|
57
|
+
<div class="timespan-bar-fill"></div>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
attachEventListeners() {
|
|
62
|
+
this.startTimeInput.addEventListener('change', () => this.handleChange());
|
|
63
|
+
this.endTimeInput.addEventListener('change', () => this.handleChange());
|
|
64
|
+
}
|
|
65
|
+
toMinutes(time) {
|
|
66
|
+
const [h, m] = time.split(':').map(Number);
|
|
67
|
+
return h * 60 + m;
|
|
68
|
+
}
|
|
69
|
+
formatDuration(minutes) {
|
|
70
|
+
const h = Math.floor(minutes / 60);
|
|
71
|
+
const m = minutes % 60;
|
|
72
|
+
if (h && m) return `${h}h ${m}m`;
|
|
73
|
+
if (h) return `${h}h`;
|
|
74
|
+
return `${m}m`;
|
|
75
|
+
}
|
|
76
|
+
updateUI() {
|
|
77
|
+
const picker = this.container.querySelector('.timespan-picker');
|
|
78
|
+
const durationEl = this.container.querySelector('.timespan-duration');
|
|
79
|
+
const barFill = this.container.querySelector('.timespan-bar-fill');
|
|
80
|
+
const start = this.startTimeInput.value;
|
|
81
|
+
const end = this.endTimeInput.value;
|
|
82
|
+
const isError = !!(start && end && start >= end);
|
|
83
|
+
picker?.classList.toggle('is-error', isError);
|
|
84
|
+
if (isError) {
|
|
85
|
+
this.endTimeInput.setCustomValidity('End time must be after start time');
|
|
86
|
+
if (durationEl) durationEl.textContent = '!';
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.endTimeInput.setCustomValidity('');
|
|
90
|
+
if (start && end && durationEl && barFill) {
|
|
91
|
+
const startMins = this.toMinutes(start);
|
|
92
|
+
const endMins = this.toMinutes(end);
|
|
93
|
+
const duration = endMins - startMins;
|
|
94
|
+
durationEl.textContent = this.formatDuration(duration);
|
|
95
|
+
const startPct = ((startMins / 1440) * 100).toFixed(2);
|
|
96
|
+
const widthPct = ((duration / 1440) * 100).toFixed(2);
|
|
97
|
+
barFill.style.left = `${startPct}%`;
|
|
98
|
+
barFill.style.width = `${widthPct}%`;
|
|
99
|
+
} else {
|
|
100
|
+
if (durationEl) durationEl.textContent = '';
|
|
101
|
+
if (barFill) { barFill.style.left = '0'; barFill.style.width = '0'; }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
handleChange() {
|
|
105
|
+
this.updateUI();
|
|
106
|
+
const { start, end } = this.getValue();
|
|
107
|
+
if (this.onChange && start && end) {
|
|
108
|
+
this.onChange(start, end);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
getValue() {
|
|
112
|
+
return {
|
|
113
|
+
start: this.startTimeInput.value,
|
|
114
|
+
end: this.endTimeInput.value
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
setValue(start, end) {
|
|
118
|
+
this.startTimeInput.value = start;
|
|
119
|
+
this.endTimeInput.value = end;
|
|
120
|
+
this.handleChange();
|
|
121
|
+
}
|
|
122
|
+
reset() {
|
|
123
|
+
this.startTimeInput.value = '';
|
|
124
|
+
this.endTimeInput.value = '';
|
|
125
|
+
this.endTimeInput.setCustomValidity('');
|
|
126
|
+
const picker = this.container.querySelector('.timespan-picker');
|
|
127
|
+
const durationEl = this.container.querySelector('.timespan-duration');
|
|
128
|
+
const barFill = this.container.querySelector('.timespan-bar-fill');
|
|
129
|
+
picker?.classList.remove('is-error');
|
|
130
|
+
if (durationEl) durationEl.textContent = '';
|
|
131
|
+
if (barFill) { barFill.style.left = '0'; barFill.style.width = '0'; }
|
|
132
|
+
}
|
|
133
|
+
isValid() {
|
|
134
|
+
const { start, end } = this.getValue();
|
|
135
|
+
return !!(start && end && start < end);
|
|
136
|
+
}
|
|
137
|
+
destroy() {
|
|
138
|
+
const startHandler = () => this.handleChange();
|
|
139
|
+
const endHandler = () => this.handleChange();
|
|
140
|
+
this.startTimeInput.removeEventListener('change', startHandler);
|
|
141
|
+
this.endTimeInput.removeEventListener('change', endHandler);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export { TimeSpanPicker };
|