bootstrap-time 0.1.5-beta → 0.1.7-beta
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 +21 -3
- package/dist/css/bootstrap-time.css +6 -1
- package/dist/css/bootstrap-time.min.css +1 -1
- package/dist/js/bootstrap-time.js +155 -36
- package/dist/js/bootstrap-time.min.js +1 -1
- package/package.json +1 -1
- package/src/js/bootstrap-time.js +155 -36
- package/src/scss/bootstrap-time.scss +7 -1
package/README.md
CHANGED
|
@@ -33,8 +33,8 @@ Or load the published files from jsDelivr:
|
|
|
33
33
|
```html
|
|
34
34
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@6.0.0-alpha.1/dist/css/bootstrap.min.css">
|
|
35
35
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.2/font/bootstrap-icons.min.css">
|
|
36
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.
|
|
37
|
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.
|
|
36
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.7-beta/dist/css/bootstrap-time.min.css">
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.7-beta/dist/js/bootstrap-time.min.js"></script>
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
Local builds use the same paths under `dist/`:
|
|
@@ -44,10 +44,28 @@ Local builds use the same paths under `dist/`:
|
|
|
44
44
|
<script src="dist/js/bootstrap-time.min.js"></script>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
Add `bs-time` to a time field. The script builds the clock and places it under that field, so it works in an existing Bootstrap form.
|
|
48
|
+
|
|
49
|
+
```html
|
|
50
|
+
<label class="form-label" for="time">Time</label>
|
|
51
|
+
<input type="time" class="form-control bs-time" id="time" step="60">
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Minute selection follows the input `step`, including when `step` changes: `60` allows every minute by clicking between the numbers, `900` enables only 00, 15, 30 and 45. See `examples/bootstrap-time.html`.
|
|
48
55
|
|
|
49
56
|
## Changelog
|
|
50
57
|
|
|
58
|
+
### 0.1.7-beta
|
|
59
|
+
|
|
60
|
+
- Attach the clock to any field with class `bs-time`.
|
|
61
|
+
|
|
62
|
+
### 0.1.6-beta
|
|
63
|
+
|
|
64
|
+
- The example starts on the current time.
|
|
65
|
+
- Minute selection follows the input `step`, including when it changes.
|
|
66
|
+
- `step="60"` selects any minute by clicking between the numbers.
|
|
67
|
+
- `step="900"` enables only 00, 15, 30 and 45.
|
|
68
|
+
|
|
51
69
|
### 0.1.5-beta
|
|
52
70
|
|
|
53
71
|
- Release title uses `<version>-beta`.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
.clock-picker {
|
|
2
2
|
position: relative;
|
|
3
|
-
width: 20rem;
|
|
4
3
|
}
|
|
5
4
|
.clock-picker input[type=time]::-webkit-calendar-picker-indicator {
|
|
6
5
|
display: none;
|
|
@@ -76,6 +75,8 @@
|
|
|
76
75
|
width: 16rem;
|
|
77
76
|
height: 16rem;
|
|
78
77
|
border-radius: 50%;
|
|
78
|
+
touch-action: none;
|
|
79
|
+
cursor: pointer;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
.clock-hand {
|
|
@@ -118,3 +119,7 @@
|
|
|
118
119
|
.clock-number[aria-pressed=true] {
|
|
119
120
|
color: var(--bs-white, #fff);
|
|
120
121
|
}
|
|
122
|
+
.clock-number:disabled {
|
|
123
|
+
opacity: 0.35;
|
|
124
|
+
cursor: default;
|
|
125
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
.clock-picker{position:relative
|
|
1
|
+
.clock-picker{position:relative}.clock-picker input[type=time]::-webkit-calendar-picker-indicator{display:none;appearance:none}.clock-toggle{pointer-events:auto;width:2.25rem;height:2.25rem;margin:-0.25rem;border:0;border-radius:50%;background:rgba(0,0,0,0);color:var(--bs-form-adorn-icon-color, var(--bs-secondary-color))}.clock-toggle .bi{font-size:1rem;line-height:1}.clock-toggle:hover,.clock-toggle:focus-visible{background-color:var(--bs-bg-3);color:var(--bs-body-color)}.clock-widget{position:absolute;top:100%;left:0;z-index:5;width:20rem;margin-top:.25rem;padding:.25rem .5rem 1rem;background-color:var(--bs-secondary-bg-subtle);color:var(--bs-secondary-fg)}.clock-arrows{display:flex;justify-content:flex-end;gap:.25rem}.btn.clock-arrow{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;min-height:0;padding:0;border-radius:50%;background-color:rgba(0,0,0,0);color:var(--bs-body-color);text-decoration:none}.btn.clock-arrow svg{width:1.5rem;height:1.5rem;fill:currentColor}.btn.clock-arrow:hover,.btn.clock-arrow:focus-visible{background-color:var(--bs-bg-3);color:var(--bs-body-color)}.btn.clock-arrow:disabled,.btn.clock-arrow:disabled:hover{background-color:rgba(0,0,0,0);color:var(--bs-secondary-color);opacity:.45}.clock{width:16rem;height:16rem;border-radius:50%;touch-action:none;cursor:pointer}.clock-hand{position:absolute;left:50%;bottom:50%;width:2px;height:var(--hand);transform-origin:center bottom;transform:translateX(-50%) rotate(var(--angle));transition:transform .3s ease,height .3s ease;border-radius:1px;pointer-events:none}.clock-knob{position:absolute;left:50%;top:0;width:2.25rem;height:2.25rem;border-radius:50%;transform:translate(-50%, -50%)}.clock-number{position:absolute;left:50%;top:50%;z-index:1;width:2.25rem;height:2.25rem;margin:-1.125rem;border:0;border-radius:50%;background:rgba(0,0,0,0);color:inherit;transform:rotate(var(--angle)) translateY(calc(var(--radius) * -1)) rotate(calc(var(--angle) * -1))}.clock-number[aria-pressed=true]{color:var(--bs-white, #fff)}.clock-number:disabled{opacity:.35;cursor:default}
|
|
@@ -7,20 +7,72 @@
|
|
|
7
7
|
|
|
8
8
|
var INNER_RADIUS = "4.6rem";
|
|
9
9
|
var OUTER_RADIUS = "6.4rem";
|
|
10
|
+
var LEFT = "M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6z";
|
|
11
|
+
var RIGHT = "M8.59 16.59 13.17 12 8.59 7.41 10 6l6 6-6 6z";
|
|
10
12
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
function svg(path) {
|
|
14
|
+
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="' + path + '"></path></svg>';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function mount(field) {
|
|
18
|
+
if (field.dataset.bsTime === "ready") return;
|
|
19
|
+
field.dataset.bsTime = "ready";
|
|
20
|
+
|
|
21
|
+
var host = field.closest(".form-control") || field;
|
|
22
|
+
var picker = host.closest(".clock-picker");
|
|
23
|
+
if (!picker) {
|
|
24
|
+
picker = document.createElement("div");
|
|
25
|
+
picker.className = "clock-picker";
|
|
26
|
+
host.parentNode.insertBefore(picker, host);
|
|
27
|
+
picker.append(host);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var adorn = field.closest(".form-adorn");
|
|
31
|
+
if (!adorn && field.classList.contains("form-control")) {
|
|
32
|
+
adorn = document.createElement("div");
|
|
33
|
+
adorn.className = "form-control form-adorn form-adorn-end";
|
|
34
|
+
field.parentNode.insertBefore(adorn, field);
|
|
35
|
+
adorn.append(field);
|
|
36
|
+
field.classList.remove("form-control");
|
|
37
|
+
field.classList.add("form-ghost");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var toggle = picker.querySelector(".clock-toggle");
|
|
41
|
+
if (!toggle && adorn) {
|
|
42
|
+
toggle = document.createElement("button");
|
|
43
|
+
toggle.type = "button";
|
|
44
|
+
toggle.className = "form-adorn-icon clock-toggle";
|
|
45
|
+
toggle.setAttribute("aria-label", "Klok");
|
|
46
|
+
toggle.setAttribute("aria-pressed", "false");
|
|
47
|
+
toggle.innerHTML = '<i class="bi bi-clock" aria-hidden="true"></i>';
|
|
48
|
+
adorn.insertBefore(toggle, adorn.firstChild);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var widget = picker.querySelector(".clock-widget");
|
|
52
|
+
if (!widget) {
|
|
53
|
+
widget = document.createElement("div");
|
|
54
|
+
widget.className = "clock-widget card theme-secondary theme-subtle";
|
|
55
|
+
widget.hidden = true;
|
|
56
|
+
widget.innerHTML =
|
|
57
|
+
'<div class="clock-arrows">' +
|
|
58
|
+
'<button class="btn btn-link clock-arrow clock-hour" type="button" aria-label="Uren">' + svg(LEFT) + "</button>" +
|
|
59
|
+
'<button class="btn btn-link clock-arrow clock-minute" type="button" aria-label="Minuten">' + svg(RIGHT) + "</button>" +
|
|
60
|
+
"</div>" +
|
|
61
|
+
'<div class="clock position-relative mx-auto theme-inverse theme-subtle" role="group" aria-label="Kies een uur"></div>';
|
|
62
|
+
picker.append(widget);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var clock = widget.querySelector(".clock");
|
|
66
|
+
var hourArrow = widget.querySelector(".clock-hour");
|
|
67
|
+
var minuteArrow = widget.querySelector(".clock-minute");
|
|
68
|
+
if (!clock || !hourArrow || !minuteArrow) return;
|
|
69
|
+
|
|
70
|
+
var now = new Date();
|
|
71
|
+
var initial = field.value
|
|
72
|
+
? field.value.split(":")
|
|
73
|
+
: [now.getHours(), now.getMinutes()];
|
|
22
74
|
var hour = Number(initial[0]) || 0;
|
|
23
|
-
var minute = Number(initial[1]) || 0;
|
|
75
|
+
var minute = snapMinute(Number(initial[1]) || 0);
|
|
24
76
|
var mode = "hour";
|
|
25
77
|
var handAngle = 150;
|
|
26
78
|
var hand = document.createElement("div");
|
|
@@ -36,6 +88,22 @@
|
|
|
36
88
|
return String(value).padStart(2, "0");
|
|
37
89
|
}
|
|
38
90
|
|
|
91
|
+
function minuteStep() {
|
|
92
|
+
var step = Number(field.getAttribute("step") || field.step);
|
|
93
|
+
if (!step || step < 60) step = 60;
|
|
94
|
+
return Math.max(1, Math.round(step / 60));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function snapMinute(value) {
|
|
98
|
+
var step = minuteStep();
|
|
99
|
+
var snapped = Math.round(value / step) * step;
|
|
100
|
+
return ((snapped % 60) + 60) % 60;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function allowedMinute(value) {
|
|
104
|
+
return value % minuteStep() === 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
39
107
|
function targetAngle() {
|
|
40
108
|
return mode === "hour" ? (hour % 12) * 30 : minute * 6;
|
|
41
109
|
}
|
|
@@ -59,7 +127,7 @@
|
|
|
59
127
|
}
|
|
60
128
|
|
|
61
129
|
function writeInput() {
|
|
62
|
-
|
|
130
|
+
field.value = pad(hour) + ":" + pad(minute);
|
|
63
131
|
}
|
|
64
132
|
|
|
65
133
|
function showMode(nextMode) {
|
|
@@ -85,14 +153,18 @@
|
|
|
85
153
|
var button = document.createElement("button");
|
|
86
154
|
var inner = mode === "hour" && index >= 12;
|
|
87
155
|
var angle = (mode === "hour" ? index % 12 : index) * 30;
|
|
156
|
+
var disabled = mode === "minute" && !allowedMinute(value);
|
|
88
157
|
|
|
89
158
|
button.type = "button";
|
|
90
159
|
button.className = "clock-number";
|
|
91
|
-
button.textContent =
|
|
92
|
-
button.
|
|
160
|
+
button.textContent = value === 0 ? "00" : String(value);
|
|
161
|
+
button.dataset.value = String(value);
|
|
162
|
+
button.disabled = disabled;
|
|
163
|
+
button.setAttribute("aria-pressed", !disabled && value === selected ? "true" : "false");
|
|
93
164
|
button.style.setProperty("--angle", angle + "deg");
|
|
94
165
|
button.style.setProperty("--radius", inner ? INNER_RADIUS : OUTER_RADIUS);
|
|
95
166
|
button.addEventListener("click", function () {
|
|
167
|
+
if (button.disabled) return;
|
|
96
168
|
if (mode === "hour") {
|
|
97
169
|
hour = value;
|
|
98
170
|
mode = "minute";
|
|
@@ -108,35 +180,56 @@
|
|
|
108
180
|
}
|
|
109
181
|
|
|
110
182
|
function showWidget() {
|
|
111
|
-
if (!widget) return;
|
|
112
183
|
widget.hidden = false;
|
|
113
|
-
if (
|
|
184
|
+
if (toggle) toggle.setAttribute("aria-pressed", "true");
|
|
114
185
|
}
|
|
115
186
|
|
|
116
187
|
function hideWidget() {
|
|
117
|
-
if (!widget) return;
|
|
118
188
|
widget.hidden = true;
|
|
119
|
-
if (
|
|
189
|
+
if (toggle) toggle.setAttribute("aria-pressed", "false");
|
|
120
190
|
}
|
|
121
191
|
|
|
122
192
|
function toggleWidget() {
|
|
123
|
-
if (widget
|
|
193
|
+
if (widget.hidden) showWidget();
|
|
124
194
|
else hideWidget();
|
|
125
195
|
}
|
|
126
196
|
|
|
197
|
+
function valueFromPoint(event) {
|
|
198
|
+
var rect = clock.getBoundingClientRect();
|
|
199
|
+
var dx = event.clientX - (rect.left + rect.width / 2);
|
|
200
|
+
var dy = event.clientY - (rect.top + rect.height / 2);
|
|
201
|
+
var angle = (Math.atan2(dx, -dy) * 180 / Math.PI + 360) % 360;
|
|
202
|
+
if (mode === "minute") return snapMinute(Math.round(angle / 6) % 60);
|
|
203
|
+
var hour12 = Math.round(angle / 30) % 12;
|
|
204
|
+
var inner = Math.hypot(dx, dy) < rect.width * 0.32;
|
|
205
|
+
if (inner) return hour12 === 0 ? 0 : hour12 + 12;
|
|
206
|
+
return hour12 === 0 ? 12 : hour12;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
clock.addEventListener("click", function (event) {
|
|
210
|
+
if (event.target.closest(".clock-number")) return;
|
|
211
|
+
var value = valueFromPoint(event);
|
|
212
|
+
if (mode === "minute") minute = value;
|
|
213
|
+
else {
|
|
214
|
+
hour = value;
|
|
215
|
+
mode = "minute";
|
|
216
|
+
}
|
|
217
|
+
writeInput();
|
|
218
|
+
updateArrows();
|
|
219
|
+
renderClock();
|
|
220
|
+
});
|
|
221
|
+
|
|
127
222
|
hourArrow.addEventListener("click", function () {
|
|
128
223
|
showMode("hour");
|
|
129
224
|
});
|
|
130
225
|
minuteArrow.addEventListener("click", function () {
|
|
131
226
|
showMode("minute");
|
|
132
227
|
});
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!timeInput.value) return;
|
|
139
|
-
var parts = timeInput.value.split(":");
|
|
228
|
+
if (toggle) toggle.addEventListener("click", toggleWidget);
|
|
229
|
+
field.addEventListener("focus", showWidget);
|
|
230
|
+
field.addEventListener("input", function () {
|
|
231
|
+
if (!field.value) return;
|
|
232
|
+
var parts = field.value.split(":");
|
|
140
233
|
var nextHour = Number(parts[0]);
|
|
141
234
|
var nextMinute = Number(parts[1]);
|
|
142
235
|
if (nextMinute !== minute) mode = "minute";
|
|
@@ -146,24 +239,50 @@
|
|
|
146
239
|
updateArrows();
|
|
147
240
|
renderClock();
|
|
148
241
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
}
|
|
242
|
+
picker.addEventListener("mousedown", function (event) {
|
|
243
|
+
if (event.target === field) return;
|
|
244
|
+
if (toggle && toggle.contains(event.target)) return;
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
});
|
|
156
247
|
document.addEventListener("mousedown", function (event) {
|
|
157
|
-
if (
|
|
248
|
+
if (picker.contains(event.target)) return;
|
|
158
249
|
hideWidget();
|
|
159
250
|
});
|
|
160
251
|
|
|
252
|
+
function applyStep() {
|
|
253
|
+
minute = snapMinute(minute);
|
|
254
|
+
field.value = pad(hour) + ":" + pad(minute);
|
|
255
|
+
renderClock();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
var stepDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "step");
|
|
259
|
+
if (stepDescriptor && stepDescriptor.set) {
|
|
260
|
+
Object.defineProperty(field, "step", {
|
|
261
|
+
configurable: true,
|
|
262
|
+
get: function () {
|
|
263
|
+
return stepDescriptor.get.call(field);
|
|
264
|
+
},
|
|
265
|
+
set: function (value) {
|
|
266
|
+
stepDescriptor.set.call(field, value);
|
|
267
|
+
applyStep();
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
new MutationObserver(applyStep).observe(field, {
|
|
272
|
+
attributes: true,
|
|
273
|
+
attributeFilter: ["step"]
|
|
274
|
+
});
|
|
275
|
+
|
|
161
276
|
updateArrows();
|
|
162
|
-
|
|
277
|
+
if (!field.value) field.value = pad(hour) + ":" + pad(minute);
|
|
163
278
|
renderClock();
|
|
164
279
|
hideWidget();
|
|
165
280
|
}
|
|
166
281
|
|
|
282
|
+
function init() {
|
|
283
|
+
document.querySelectorAll(".bs-time").forEach(mount);
|
|
284
|
+
}
|
|
285
|
+
|
|
167
286
|
if (document.readyState === "loading") {
|
|
168
287
|
document.addEventListener("DOMContentLoaded", init);
|
|
169
288
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(){"use strict";var e="4.6rem",t="6.4rem";function n(){var
|
|
1
|
+
!function(){"use strict";var e="4.6rem",t="6.4rem";function n(e){return'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="'+e+'"></path></svg>'}function r(r){if("ready"!==r.dataset.bsTime){r.dataset.bsTime="ready";var o=r.closest(".form-control")||r,a=o.closest(".clock-picker");a||((a=document.createElement("div")).className="clock-picker",o.parentNode.insertBefore(a,o),a.append(o));var i=r.closest(".form-adorn");!i&&r.classList.contains("form-control")&&((i=document.createElement("div")).className="form-control form-adorn form-adorn-end",r.parentNode.insertBefore(i,r),i.append(r),r.classList.remove("form-control"),r.classList.add("form-ghost"));var c=a.querySelector(".clock-toggle");!c&&i&&((c=document.createElement("button")).type="button",c.className="form-adorn-icon clock-toggle",c.setAttribute("aria-label","Klok"),c.setAttribute("aria-pressed","false"),c.innerHTML='<i class="bi bi-clock" aria-hidden="true"></i>',i.insertBefore(c,i.firstChild));var u=a.querySelector(".clock-widget");u||((u=document.createElement("div")).className="clock-widget card theme-secondary theme-subtle",u.hidden=!0,u.innerHTML='<div class="clock-arrows"><button class="btn btn-link clock-arrow clock-hour" type="button" aria-label="Uren">'+n("M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6z")+'</button><button class="btn btn-link clock-arrow clock-minute" type="button" aria-label="Minuten">'+n("M8.59 16.59 13.17 12 8.59 7.41 10 6l6 6-6 6z")+'</button></div><div class="clock position-relative mx-auto theme-inverse theme-subtle" role="group" aria-label="Kies een uur"></div>',a.append(u));var s=u.querySelector(".clock"),l=u.querySelector(".clock-hour"),d=u.querySelector(".clock-minute");if(s&&l&&d){var m=new Date,f=r.value?r.value.split(":"):[m.getHours(),m.getMinutes()],b=Number(f[0])||0,p=M(Number(f[1])||0),v="hour",h=150,g=document.createElement("div"),k=document.createElement("span");g.className="clock-hand bg-primary",k.className="clock-knob bg-primary",k.setAttribute("aria-hidden","true"),g.append(k),s.append(g),s.addEventListener("click",function(e){if(!e.target.closest(".clock-number")){var t=function(e){var t=s.getBoundingClientRect(),n=e.clientX-(t.left+t.width/2),r=e.clientY-(t.top+t.height/2),o=(180*Math.atan2(n,-r)/Math.PI+360)%360;if("minute"===v)return M(Math.round(o/6)%60);var a=Math.round(o/30)%12;return Math.hypot(n,r)<.32*t.width?0===a?0:a+12:0===a?12:a}(e);"minute"===v?p=t:(b=t,v="minute"),S(),w(),q()}}),l.addEventListener("click",function(){A("hour")}),d.addEventListener("click",function(){A("minute")}),c&&c.addEventListener("click",function(){u.hidden?P():B()}),r.addEventListener("focus",P),r.addEventListener("input",function(){if(r.value){var e=r.value.split(":"),t=Number(e[0]),n=Number(e[1]);n!==p?v="minute":t!==b&&(v="hour"),b=t,p=n,w(),q()}}),a.addEventListener("mousedown",function(e){e.target!==r&&(c&&c.contains(e.target)||e.preventDefault())}),document.addEventListener("mousedown",function(e){a.contains(e.target)||B()});var y=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"step");y&&y.set&&Object.defineProperty(r,"step",{configurable:!0,get:function(){return y.get.call(r)},set:function(e){y.set.call(r,e),O()}}),new MutationObserver(O).observe(r,{attributes:!0,attributeFilter:["step"]}),w(),r.value||(r.value=E(b)+":"+E(p)),q(),B()}}function E(e){return String(e).padStart(2,"0")}function L(){var e=Number(r.getAttribute("step")||r.step);return(!e||e<60)&&(e=60),Math.max(1,Math.round(e/60))}function M(e){var t=L();return(Math.round(e/t)*t%60+60)%60}function N(){h+=((("hour"===v?b%12*30:6*p)-h)%360+540)%360-180,g.style.setProperty("--angle",h+"deg"),g.style.setProperty("--hand","hour"===v&&(0===b||b>12)?e:t)}function w(){l.disabled="hour"===v,d.disabled="minute"===v}function S(){r.value=E(b)+":"+E(p)}function A(e){v!==e&&(v=e,w(),q())}function q(){s.querySelectorAll(".clock-number").forEach(function(e){e.remove()}),s.setAttribute("aria-label","hour"===v?"Kies een uur":"Kies de minuten");var n="hour"===v?[12,1,2,3,4,5,6,7,8,9,10,11,0,13,14,15,16,17,18,19,20,21,22,23]:[0,5,10,15,20,25,30,35,40,45,50,55],r="hour"===v?b:p;N(),n.forEach(function(n,o){var a=document.createElement("button"),i="hour"===v&&o>=12,c=30*("hour"===v?o%12:o),u="minute"===v&&!function(e){return e%L()===0}(n);a.type="button",a.className="clock-number",a.textContent=0===n?"00":String(n),a.dataset.value=String(n),a.disabled=u,a.setAttribute("aria-pressed",u||n!==r?"false":"true"),a.style.setProperty("--angle",c+"deg"),a.style.setProperty("--radius",i?e:t),a.addEventListener("click",function(){a.disabled||("hour"===v?(b=n,v="minute"):p=n,S(),w(),q())}),s.append(a)})}function P(){u.hidden=!1,c&&c.setAttribute("aria-pressed","true")}function B(){u.hidden=!0,c&&c.setAttribute("aria-pressed","false")}function O(){p=M(p),r.value=E(b)+":"+E(p),q()}}function o(){document.querySelectorAll(".bs-time").forEach(r)}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",o):o()}();
|
package/package.json
CHANGED
package/src/js/bootstrap-time.js
CHANGED
|
@@ -7,20 +7,72 @@
|
|
|
7
7
|
|
|
8
8
|
var INNER_RADIUS = "4.6rem";
|
|
9
9
|
var OUTER_RADIUS = "6.4rem";
|
|
10
|
+
var LEFT = "M15.41 16.59 10.83 12l4.58-4.59L14 6l-6 6 6 6z";
|
|
11
|
+
var RIGHT = "M8.59 16.59 13.17 12 8.59 7.41 10 6l6 6-6 6z";
|
|
10
12
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
function svg(path) {
|
|
14
|
+
return '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="' + path + '"></path></svg>';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function mount(field) {
|
|
18
|
+
if (field.dataset.bsTime === "ready") return;
|
|
19
|
+
field.dataset.bsTime = "ready";
|
|
20
|
+
|
|
21
|
+
var host = field.closest(".form-control") || field;
|
|
22
|
+
var picker = host.closest(".clock-picker");
|
|
23
|
+
if (!picker) {
|
|
24
|
+
picker = document.createElement("div");
|
|
25
|
+
picker.className = "clock-picker";
|
|
26
|
+
host.parentNode.insertBefore(picker, host);
|
|
27
|
+
picker.append(host);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var adorn = field.closest(".form-adorn");
|
|
31
|
+
if (!adorn && field.classList.contains("form-control")) {
|
|
32
|
+
adorn = document.createElement("div");
|
|
33
|
+
adorn.className = "form-control form-adorn form-adorn-end";
|
|
34
|
+
field.parentNode.insertBefore(adorn, field);
|
|
35
|
+
adorn.append(field);
|
|
36
|
+
field.classList.remove("form-control");
|
|
37
|
+
field.classList.add("form-ghost");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
var toggle = picker.querySelector(".clock-toggle");
|
|
41
|
+
if (!toggle && adorn) {
|
|
42
|
+
toggle = document.createElement("button");
|
|
43
|
+
toggle.type = "button";
|
|
44
|
+
toggle.className = "form-adorn-icon clock-toggle";
|
|
45
|
+
toggle.setAttribute("aria-label", "Klok");
|
|
46
|
+
toggle.setAttribute("aria-pressed", "false");
|
|
47
|
+
toggle.innerHTML = '<i class="bi bi-clock" aria-hidden="true"></i>';
|
|
48
|
+
adorn.insertBefore(toggle, adorn.firstChild);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
var widget = picker.querySelector(".clock-widget");
|
|
52
|
+
if (!widget) {
|
|
53
|
+
widget = document.createElement("div");
|
|
54
|
+
widget.className = "clock-widget card theme-secondary theme-subtle";
|
|
55
|
+
widget.hidden = true;
|
|
56
|
+
widget.innerHTML =
|
|
57
|
+
'<div class="clock-arrows">' +
|
|
58
|
+
'<button class="btn btn-link clock-arrow clock-hour" type="button" aria-label="Uren">' + svg(LEFT) + "</button>" +
|
|
59
|
+
'<button class="btn btn-link clock-arrow clock-minute" type="button" aria-label="Minuten">' + svg(RIGHT) + "</button>" +
|
|
60
|
+
"</div>" +
|
|
61
|
+
'<div class="clock position-relative mx-auto theme-inverse theme-subtle" role="group" aria-label="Kies een uur"></div>';
|
|
62
|
+
picker.append(widget);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
var clock = widget.querySelector(".clock");
|
|
66
|
+
var hourArrow = widget.querySelector(".clock-hour");
|
|
67
|
+
var minuteArrow = widget.querySelector(".clock-minute");
|
|
68
|
+
if (!clock || !hourArrow || !minuteArrow) return;
|
|
69
|
+
|
|
70
|
+
var now = new Date();
|
|
71
|
+
var initial = field.value
|
|
72
|
+
? field.value.split(":")
|
|
73
|
+
: [now.getHours(), now.getMinutes()];
|
|
22
74
|
var hour = Number(initial[0]) || 0;
|
|
23
|
-
var minute = Number(initial[1]) || 0;
|
|
75
|
+
var minute = snapMinute(Number(initial[1]) || 0);
|
|
24
76
|
var mode = "hour";
|
|
25
77
|
var handAngle = 150;
|
|
26
78
|
var hand = document.createElement("div");
|
|
@@ -36,6 +88,22 @@
|
|
|
36
88
|
return String(value).padStart(2, "0");
|
|
37
89
|
}
|
|
38
90
|
|
|
91
|
+
function minuteStep() {
|
|
92
|
+
var step = Number(field.getAttribute("step") || field.step);
|
|
93
|
+
if (!step || step < 60) step = 60;
|
|
94
|
+
return Math.max(1, Math.round(step / 60));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function snapMinute(value) {
|
|
98
|
+
var step = minuteStep();
|
|
99
|
+
var snapped = Math.round(value / step) * step;
|
|
100
|
+
return ((snapped % 60) + 60) % 60;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function allowedMinute(value) {
|
|
104
|
+
return value % minuteStep() === 0;
|
|
105
|
+
}
|
|
106
|
+
|
|
39
107
|
function targetAngle() {
|
|
40
108
|
return mode === "hour" ? (hour % 12) * 30 : minute * 6;
|
|
41
109
|
}
|
|
@@ -59,7 +127,7 @@
|
|
|
59
127
|
}
|
|
60
128
|
|
|
61
129
|
function writeInput() {
|
|
62
|
-
|
|
130
|
+
field.value = pad(hour) + ":" + pad(minute);
|
|
63
131
|
}
|
|
64
132
|
|
|
65
133
|
function showMode(nextMode) {
|
|
@@ -85,14 +153,18 @@
|
|
|
85
153
|
var button = document.createElement("button");
|
|
86
154
|
var inner = mode === "hour" && index >= 12;
|
|
87
155
|
var angle = (mode === "hour" ? index % 12 : index) * 30;
|
|
156
|
+
var disabled = mode === "minute" && !allowedMinute(value);
|
|
88
157
|
|
|
89
158
|
button.type = "button";
|
|
90
159
|
button.className = "clock-number";
|
|
91
|
-
button.textContent =
|
|
92
|
-
button.
|
|
160
|
+
button.textContent = value === 0 ? "00" : String(value);
|
|
161
|
+
button.dataset.value = String(value);
|
|
162
|
+
button.disabled = disabled;
|
|
163
|
+
button.setAttribute("aria-pressed", !disabled && value === selected ? "true" : "false");
|
|
93
164
|
button.style.setProperty("--angle", angle + "deg");
|
|
94
165
|
button.style.setProperty("--radius", inner ? INNER_RADIUS : OUTER_RADIUS);
|
|
95
166
|
button.addEventListener("click", function () {
|
|
167
|
+
if (button.disabled) return;
|
|
96
168
|
if (mode === "hour") {
|
|
97
169
|
hour = value;
|
|
98
170
|
mode = "minute";
|
|
@@ -108,35 +180,56 @@
|
|
|
108
180
|
}
|
|
109
181
|
|
|
110
182
|
function showWidget() {
|
|
111
|
-
if (!widget) return;
|
|
112
183
|
widget.hidden = false;
|
|
113
|
-
if (
|
|
184
|
+
if (toggle) toggle.setAttribute("aria-pressed", "true");
|
|
114
185
|
}
|
|
115
186
|
|
|
116
187
|
function hideWidget() {
|
|
117
|
-
if (!widget) return;
|
|
118
188
|
widget.hidden = true;
|
|
119
|
-
if (
|
|
189
|
+
if (toggle) toggle.setAttribute("aria-pressed", "false");
|
|
120
190
|
}
|
|
121
191
|
|
|
122
192
|
function toggleWidget() {
|
|
123
|
-
if (widget
|
|
193
|
+
if (widget.hidden) showWidget();
|
|
124
194
|
else hideWidget();
|
|
125
195
|
}
|
|
126
196
|
|
|
197
|
+
function valueFromPoint(event) {
|
|
198
|
+
var rect = clock.getBoundingClientRect();
|
|
199
|
+
var dx = event.clientX - (rect.left + rect.width / 2);
|
|
200
|
+
var dy = event.clientY - (rect.top + rect.height / 2);
|
|
201
|
+
var angle = (Math.atan2(dx, -dy) * 180 / Math.PI + 360) % 360;
|
|
202
|
+
if (mode === "minute") return snapMinute(Math.round(angle / 6) % 60);
|
|
203
|
+
var hour12 = Math.round(angle / 30) % 12;
|
|
204
|
+
var inner = Math.hypot(dx, dy) < rect.width * 0.32;
|
|
205
|
+
if (inner) return hour12 === 0 ? 0 : hour12 + 12;
|
|
206
|
+
return hour12 === 0 ? 12 : hour12;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
clock.addEventListener("click", function (event) {
|
|
210
|
+
if (event.target.closest(".clock-number")) return;
|
|
211
|
+
var value = valueFromPoint(event);
|
|
212
|
+
if (mode === "minute") minute = value;
|
|
213
|
+
else {
|
|
214
|
+
hour = value;
|
|
215
|
+
mode = "minute";
|
|
216
|
+
}
|
|
217
|
+
writeInput();
|
|
218
|
+
updateArrows();
|
|
219
|
+
renderClock();
|
|
220
|
+
});
|
|
221
|
+
|
|
127
222
|
hourArrow.addEventListener("click", function () {
|
|
128
223
|
showMode("hour");
|
|
129
224
|
});
|
|
130
225
|
minuteArrow.addEventListener("click", function () {
|
|
131
226
|
showMode("minute");
|
|
132
227
|
});
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!timeInput.value) return;
|
|
139
|
-
var parts = timeInput.value.split(":");
|
|
228
|
+
if (toggle) toggle.addEventListener("click", toggleWidget);
|
|
229
|
+
field.addEventListener("focus", showWidget);
|
|
230
|
+
field.addEventListener("input", function () {
|
|
231
|
+
if (!field.value) return;
|
|
232
|
+
var parts = field.value.split(":");
|
|
140
233
|
var nextHour = Number(parts[0]);
|
|
141
234
|
var nextMinute = Number(parts[1]);
|
|
142
235
|
if (nextMinute !== minute) mode = "minute";
|
|
@@ -146,24 +239,50 @@
|
|
|
146
239
|
updateArrows();
|
|
147
240
|
renderClock();
|
|
148
241
|
});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
-
}
|
|
242
|
+
picker.addEventListener("mousedown", function (event) {
|
|
243
|
+
if (event.target === field) return;
|
|
244
|
+
if (toggle && toggle.contains(event.target)) return;
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
});
|
|
156
247
|
document.addEventListener("mousedown", function (event) {
|
|
157
|
-
if (
|
|
248
|
+
if (picker.contains(event.target)) return;
|
|
158
249
|
hideWidget();
|
|
159
250
|
});
|
|
160
251
|
|
|
252
|
+
function applyStep() {
|
|
253
|
+
minute = snapMinute(minute);
|
|
254
|
+
field.value = pad(hour) + ":" + pad(minute);
|
|
255
|
+
renderClock();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
var stepDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "step");
|
|
259
|
+
if (stepDescriptor && stepDescriptor.set) {
|
|
260
|
+
Object.defineProperty(field, "step", {
|
|
261
|
+
configurable: true,
|
|
262
|
+
get: function () {
|
|
263
|
+
return stepDescriptor.get.call(field);
|
|
264
|
+
},
|
|
265
|
+
set: function (value) {
|
|
266
|
+
stepDescriptor.set.call(field, value);
|
|
267
|
+
applyStep();
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
new MutationObserver(applyStep).observe(field, {
|
|
272
|
+
attributes: true,
|
|
273
|
+
attributeFilter: ["step"]
|
|
274
|
+
});
|
|
275
|
+
|
|
161
276
|
updateArrows();
|
|
162
|
-
|
|
277
|
+
if (!field.value) field.value = pad(hour) + ":" + pad(minute);
|
|
163
278
|
renderClock();
|
|
164
279
|
hideWidget();
|
|
165
280
|
}
|
|
166
281
|
|
|
282
|
+
function init() {
|
|
283
|
+
document.querySelectorAll(".bs-time").forEach(mount);
|
|
284
|
+
}
|
|
285
|
+
|
|
167
286
|
if (document.readyState === "loading") {
|
|
168
287
|
document.addEventListener("DOMContentLoaded", init);
|
|
169
288
|
} else {
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
.clock-picker {
|
|
6
6
|
position: relative;
|
|
7
|
-
width: 20rem;
|
|
8
7
|
|
|
9
8
|
input[type="time"]::-webkit-calendar-picker-indicator {
|
|
10
9
|
display: none;
|
|
@@ -89,6 +88,8 @@
|
|
|
89
88
|
width: 16rem;
|
|
90
89
|
height: 16rem;
|
|
91
90
|
border-radius: 50%;
|
|
91
|
+
touch-action: none;
|
|
92
|
+
cursor: pointer;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
.clock-hand {
|
|
@@ -131,4 +132,9 @@
|
|
|
131
132
|
&[aria-pressed="true"] {
|
|
132
133
|
color: var(--bs-white, #fff);
|
|
133
134
|
}
|
|
135
|
+
|
|
136
|
+
&:disabled {
|
|
137
|
+
opacity: 0.35;
|
|
138
|
+
cursor: default;
|
|
139
|
+
}
|
|
134
140
|
}
|