bootstrap-time 0.1.6-beta → 0.1.8-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 CHANGED
@@ -18,7 +18,7 @@ dist/ generated, do not edit
18
18
  examples/ HTML that points at dist
19
19
  ```
20
20
 
21
- `npm run build` compiles `src` into `dist`. Bootstrap 6 is required. The time field uses [form adornments](https://getbootstrap.com/docs/6.0/forms/form-adorn/), which Bootstrap 5.3 does not have. Bootstrap 5.3 can only place an addon beside the input with `input-group`, not inside it. The clock icon comes from bootstrap-icons.
21
+ `npm run build` compiles `src` into `dist`. Bootstrap 5.3.8 or later 5.3 is required. The browser supplies the time field icon. Focus opens the clock and blur closes it.
22
22
 
23
23
  ## Usage
24
24
 
@@ -31,10 +31,9 @@ npm install bootstrap-time
31
31
  Or load the published files from jsDelivr:
32
32
 
33
33
  ```html
34
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@6.0.0-alpha.1/dist/css/bootstrap.min.css">
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.6-beta/dist/css/bootstrap-time.min.css">
37
- <script src="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.6-beta/dist/js/bootstrap-time.min.js"></script>
34
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css">
35
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.8-beta/dist/css/bootstrap-time.min.css">
36
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap-time@0.1.8-beta/dist/js/bootstrap-time.min.js"></script>
38
37
  ```
39
38
 
40
39
  Local builds use the same paths under `dist/`:
@@ -44,10 +43,30 @@ Local builds use the same paths under `dist/`:
44
43
  <script src="dist/js/bootstrap-time.min.js"></script>
45
44
  ```
46
45
 
47
- The script initializes the clock when `.clock-picker`, `#clock`, `#time-input`, `#hour-arrow` and `#minute-arrow` are present. The widget overlays the form under the time input. 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`.
46
+ 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.
47
+
48
+ ```html
49
+ <label class="form-label" for="time">Time</label>
50
+ <input type="time" class="form-control bs-time" id="time" step="60">
51
+ ```
52
+
53
+ 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
54
 
49
55
  ## Changelog
50
56
 
57
+ ### 0.1.8-beta
58
+
59
+ - Requires Bootstrap 5.3.8 instead of Bootstrap 6.
60
+ - Removed the adorn button. The browser time icon stays, and focus and blur still open and close the clock.
61
+
62
+ ### 0.2.0
63
+
64
+ - Stable release. Attach the clock to any field with class `bs-time`.
65
+
66
+ ### 0.1.7-beta
67
+
68
+ - Attach the clock to any field with class `bs-time`.
69
+
51
70
  ### 0.1.6-beta
52
71
 
53
72
  - The example starts on the current time.
@@ -1,29 +1,5 @@
1
1
  .clock-picker {
2
2
  position: relative;
3
- width: 20rem;
4
- }
5
- .clock-picker input[type=time]::-webkit-calendar-picker-indicator {
6
- display: none;
7
- appearance: none;
8
- }
9
-
10
- .clock-toggle {
11
- pointer-events: auto;
12
- width: 2.25rem;
13
- height: 2.25rem;
14
- margin: -0.25rem;
15
- border: 0;
16
- border-radius: 50%;
17
- background: transparent;
18
- color: var(--bs-form-adorn-icon-color, var(--bs-secondary-color));
19
- }
20
- .clock-toggle .bi {
21
- font-size: 1rem;
22
- line-height: 1;
23
- }
24
- .clock-toggle:hover, .clock-toggle:focus-visible {
25
- background-color: var(--bs-bg-3);
26
- color: var(--bs-body-color);
27
3
  }
28
4
 
29
5
  .clock-widget {
@@ -34,8 +10,8 @@
34
10
  width: 20rem;
35
11
  margin-top: 0.25rem;
36
12
  padding: 0.25rem 0.5rem 1rem;
37
- background-color: var(--bs-secondary-bg-subtle);
38
- color: var(--bs-secondary-fg);
13
+ background-color: var(--bs-secondary-bg);
14
+ color: var(--bs-body-color);
39
15
  }
40
16
 
41
17
  .clock-arrows {
@@ -63,7 +39,7 @@
63
39
  fill: currentColor;
64
40
  }
65
41
  .btn.clock-arrow:hover, .btn.clock-arrow:focus-visible {
66
- background-color: var(--bs-bg-3);
42
+ background-color: var(--bs-tertiary-bg);
67
43
  color: var(--bs-body-color);
68
44
  }
69
45
  .btn.clock-arrow:disabled, .btn.clock-arrow:disabled:hover {
@@ -1 +1 @@
1
- .clock-picker{position:relative;width:20rem}.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}
1
+ .clock-picker{position:relative}.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);color:var(--bs-body-color)}.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-tertiary-bg);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,48 @@
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 init() {
12
- var picker = document.querySelector(".clock-picker");
13
- var clock = document.querySelector("#clock");
14
- var widget = document.querySelector("#clock-widget");
15
- var timeInput = document.querySelector("#time-input");
16
- var hourArrow = document.querySelector("#hour-arrow");
17
- var minuteArrow = document.querySelector("#minute-arrow");
18
- var clockToggle = document.querySelector("#clock-toggle");
19
- if (!clock || !timeInput || !hourArrow || !minuteArrow) return;
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 widget = picker.querySelector(".clock-widget");
31
+ if (!widget) {
32
+ widget = document.createElement("div");
33
+ widget.className = "clock-widget card bg-body-secondary";
34
+ widget.hidden = true;
35
+ widget.innerHTML =
36
+ '<div class="clock-arrows">' +
37
+ '<button class="btn btn-link clock-arrow clock-hour" type="button" aria-label="Uren">' + svg(LEFT) + "</button>" +
38
+ '<button class="btn btn-link clock-arrow clock-minute" type="button" aria-label="Minuten">' + svg(RIGHT) + "</button>" +
39
+ "</div>" +
40
+ '<div class="clock position-relative mx-auto bg-body-tertiary" role="group" aria-label="Kies een uur"></div>';
41
+ picker.append(widget);
42
+ }
43
+
44
+ var clock = widget.querySelector(".clock");
45
+ var hourArrow = widget.querySelector(".clock-hour");
46
+ var minuteArrow = widget.querySelector(".clock-minute");
47
+ if (!clock || !hourArrow || !minuteArrow) return;
20
48
 
21
49
  var now = new Date();
22
- var initial = timeInput.value
23
- ? timeInput.value.split(":")
50
+ var initial = field.value
51
+ ? field.value.split(":")
24
52
  : [now.getHours(), now.getMinutes()];
25
53
  var hour = Number(initial[0]) || 0;
26
54
  var minute = snapMinute(Number(initial[1]) || 0);
@@ -40,7 +68,7 @@
40
68
  }
41
69
 
42
70
  function minuteStep() {
43
- var step = Number(timeInput.getAttribute("step") || timeInput.step);
71
+ var step = Number(field.getAttribute("step") || field.step);
44
72
  if (!step || step < 60) step = 60;
45
73
  return Math.max(1, Math.round(step / 60));
46
74
  }
@@ -78,7 +106,7 @@
78
106
  }
79
107
 
80
108
  function writeInput() {
81
- timeInput.value = pad(hour) + ":" + pad(minute);
109
+ field.value = pad(hour) + ":" + pad(minute);
82
110
  }
83
111
 
84
112
  function showMode(nextMode) {
@@ -131,20 +159,11 @@
131
159
  }
132
160
 
133
161
  function showWidget() {
134
- if (!widget) return;
135
162
  widget.hidden = false;
136
- if (clockToggle) clockToggle.setAttribute("aria-pressed", "true");
137
163
  }
138
164
 
139
165
  function hideWidget() {
140
- if (!widget) return;
141
166
  widget.hidden = true;
142
- if (clockToggle) clockToggle.setAttribute("aria-pressed", "false");
143
- }
144
-
145
- function toggleWidget() {
146
- if (widget && widget.hidden) showWidget();
147
- else hideWidget();
148
167
  }
149
168
 
150
169
  function valueFromPoint(event) {
@@ -178,13 +197,11 @@
178
197
  minuteArrow.addEventListener("click", function () {
179
198
  showMode("minute");
180
199
  });
181
- if (clockToggle) {
182
- clockToggle.addEventListener("click", toggleWidget);
183
- }
184
- timeInput.addEventListener("focus", showWidget);
185
- timeInput.addEventListener("input", function () {
186
- if (!timeInput.value) return;
187
- var parts = timeInput.value.split(":");
200
+ field.addEventListener("focus", showWidget);
201
+ field.addEventListener("blur", hideWidget);
202
+ field.addEventListener("input", function () {
203
+ if (!field.value) return;
204
+ var parts = field.value.split(":");
188
205
  var nextHour = Number(parts[0]);
189
206
  var nextMinute = Number(parts[1]);
190
207
  if (nextMinute !== minute) mode = "minute";
@@ -194,48 +211,49 @@
194
211
  updateArrows();
195
212
  renderClock();
196
213
  });
197
- if (picker) {
198
- picker.addEventListener("mousedown", function (event) {
199
- if (event.target === timeInput) return;
200
- if (clockToggle && clockToggle.contains(event.target)) return;
201
- event.preventDefault();
202
- });
203
- }
214
+ picker.addEventListener("mousedown", function (event) {
215
+ if (event.target === field) return;
216
+ event.preventDefault();
217
+ });
204
218
  document.addEventListener("mousedown", function (event) {
205
- if (!picker || picker.contains(event.target)) return;
219
+ if (picker.contains(event.target)) return;
206
220
  hideWidget();
207
221
  });
208
222
 
209
223
  function applyStep() {
210
224
  minute = snapMinute(minute);
211
- writeInput();
225
+ field.value = pad(hour) + ":" + pad(minute);
212
226
  renderClock();
213
227
  }
214
228
 
215
229
  var stepDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "step");
216
230
  if (stepDescriptor && stepDescriptor.set) {
217
- Object.defineProperty(timeInput, "step", {
231
+ Object.defineProperty(field, "step", {
218
232
  configurable: true,
219
233
  get: function () {
220
- return stepDescriptor.get.call(timeInput);
234
+ return stepDescriptor.get.call(field);
221
235
  },
222
236
  set: function (value) {
223
- stepDescriptor.set.call(timeInput, value);
237
+ stepDescriptor.set.call(field, value);
224
238
  applyStep();
225
239
  }
226
240
  });
227
241
  }
228
- new MutationObserver(applyStep).observe(timeInput, {
242
+ new MutationObserver(applyStep).observe(field, {
229
243
  attributes: true,
230
244
  attributeFilter: ["step"]
231
245
  });
232
246
 
233
247
  updateArrows();
234
- writeInput();
248
+ if (!field.value) field.value = pad(hour) + ":" + pad(minute);
235
249
  renderClock();
236
250
  hideWidget();
237
251
  }
238
252
 
253
+ function init() {
254
+ document.querySelectorAll(".bs-time").forEach(mount);
255
+ }
256
+
239
257
  if (document.readyState === "loading") {
240
258
  document.addEventListener("DOMContentLoaded", init);
241
259
  } else {
@@ -1 +1 @@
1
- !function(){"use strict";var e="4.6rem",t="6.4rem";function n(){var n=document.querySelector(".clock-picker"),r=document.querySelector("#clock"),u=document.querySelector("#clock-widget"),o=document.querySelector("#time-input"),i=document.querySelector("#hour-arrow"),a=document.querySelector("#minute-arrow"),c=document.querySelector("#clock-toggle");if(r&&o&&i&&a){var d=new Date,s=o.value?o.value.split(":"):[d.getHours(),d.getMinutes()],l=Number(s[0])||0,m=k(Number(s[1])||0),f="hour",p=150,h=document.createElement("div"),v=document.createElement("span");h.className="clock-hand bg-primary",v.className="clock-knob bg-primary",v.setAttribute("aria-hidden","true"),h.append(v),r.append(h),r.addEventListener("click",function(e){if(!e.target.closest(".clock-number")){var t=function(e){var t=r.getBoundingClientRect(),n=e.clientX-(t.left+t.width/2),u=e.clientY-(t.top+t.height/2),o=(180*Math.atan2(n,-u)/Math.PI+360)%360;if("minute"===f)return k(Math.round(o/6)%60);var i=Math.round(o/30)%12;return Math.hypot(n,u)<.32*t.width?0===i?0:i+12:0===i?12:i}(e);"minute"===f?m=t:(l=t,f="minute"),L(),S(),w()}}),i.addEventListener("click",function(){M("hour")}),a.addEventListener("click",function(){M("minute")}),c&&c.addEventListener("click",function(){u&&u.hidden?q():N()}),o.addEventListener("focus",q),o.addEventListener("input",function(){if(o.value){var e=o.value.split(":"),t=Number(e[0]),n=Number(e[1]);n!==m?f="minute":t!==l&&(f="hour"),l=t,m=n,S(),w()}}),n&&n.addEventListener("mousedown",function(e){e.target!==o&&(c&&c.contains(e.target)||e.preventDefault())}),document.addEventListener("mousedown",function(e){n&&!n.contains(e.target)&&N()});var b=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"step");b&&b.set&&Object.defineProperty(o,"step",{configurable:!0,get:function(){return b.get.call(o)},set:function(e){b.set.call(o,e),A()}}),new MutationObserver(A).observe(o,{attributes:!0,attributeFilter:["step"]}),S(),L(),w(),N()}function g(e){return String(e).padStart(2,"0")}function y(){var e=Number(o.getAttribute("step")||o.step);return(!e||e<60)&&(e=60),Math.max(1,Math.round(e/60))}function k(e){var t=y();return(Math.round(e/t)*t%60+60)%60}function E(){p+=((("hour"===f?l%12*30:6*m)-p)%360+540)%360-180,h.style.setProperty("--angle",p+"deg"),h.style.setProperty("--hand","hour"===f&&(0===l||l>12)?e:t)}function S(){i.disabled="hour"===f,a.disabled="minute"===f}function L(){o.value=g(l)+":"+g(m)}function M(e){f!==e&&(f=e,S(),w())}function w(){r.querySelectorAll(".clock-number").forEach(function(e){e.remove()}),r.setAttribute("aria-label","hour"===f?"Kies een uur":"Kies de minuten");var n="hour"===f?[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],u="hour"===f?l:m;E(),n.forEach(function(n,o){var i=document.createElement("button"),a="hour"===f&&o>=12,c=30*("hour"===f?o%12:o),d="minute"===f&&!function(e){return e%y()===0}(n);i.type="button",i.className="clock-number",i.textContent=0===n?"00":String(n),i.dataset.value=String(n),i.disabled=d,i.setAttribute("aria-pressed",d||n!==u?"false":"true"),i.style.setProperty("--angle",c+"deg"),i.style.setProperty("--radius",a?e:t),i.addEventListener("click",function(){i.disabled||("hour"===f?(l=n,f="minute"):m=n,L(),S(),w())}),r.append(i)})}function q(){u&&(u.hidden=!1,c&&c.setAttribute("aria-pressed","true"))}function N(){u&&(u.hidden=!0,c&&c.setAttribute("aria-pressed","false"))}function A(){m=k(m),L(),w()}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",n):n()}();
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 a=r.closest(".form-control")||r,o=a.closest(".clock-picker");o||((o=document.createElement("div")).className="clock-picker",a.parentNode.insertBefore(o,a),o.append(a));var u=o.querySelector(".clock-widget");u||((u=document.createElement("div")).className="clock-widget card bg-body-secondary",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 bg-body-tertiary" role="group" aria-label="Kies een uur"></div>',o.append(u));var i=u.querySelector(".clock"),c=u.querySelector(".clock-hour"),l=u.querySelector(".clock-minute");if(i&&c&&l){var d=new Date,s=r.value?r.value.split(":"):[d.getHours(),d.getMinutes()],m=Number(s[0])||0,b=E(Number(s[1])||0),v="hour",p=150,f=document.createElement("div"),h=document.createElement("span");f.className="clock-hand bg-primary",h.className="clock-knob bg-primary",h.setAttribute("aria-hidden","true"),f.append(h),i.append(f),i.addEventListener("click",function(e){if(!e.target.closest(".clock-number")){var t=function(e){var t=i.getBoundingClientRect(),n=e.clientX-(t.left+t.width/2),r=e.clientY-(t.top+t.height/2),a=(180*Math.atan2(n,-r)/Math.PI+360)%360;if("minute"===v)return E(Math.round(a/6)%60);var o=Math.round(a/30)%12;return Math.hypot(n,r)<.32*t.width?0===o?0:o+12:0===o?12:o}(e);"minute"===v?b=t:(m=t,v="minute"),w(),L(),S()}}),c.addEventListener("click",function(){N("hour")}),l.addEventListener("click",function(){N("minute")}),r.addEventListener("focus",function(){u.hidden=!1}),r.addEventListener("blur",P),r.addEventListener("input",function(){if(r.value){var e=r.value.split(":"),t=Number(e[0]),n=Number(e[1]);n!==b?v="minute":t!==m&&(v="hour"),m=t,b=n,L(),S()}}),o.addEventListener("mousedown",function(e){e.target!==r&&e.preventDefault()}),document.addEventListener("mousedown",function(e){o.contains(e.target)||P()});var g=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,"step");g&&g.set&&Object.defineProperty(r,"step",{configurable:!0,get:function(){return g.get.call(r)},set:function(e){g.set.call(r,e),q()}}),new MutationObserver(q).observe(r,{attributes:!0,attributeFilter:["step"]}),L(),r.value||(r.value=y(m)+":"+y(b)),S(),P()}}function y(e){return String(e).padStart(2,"0")}function k(){var e=Number(r.getAttribute("step")||r.step);return(!e||e<60)&&(e=60),Math.max(1,Math.round(e/60))}function E(e){var t=k();return(Math.round(e/t)*t%60+60)%60}function M(){p+=((("hour"===v?m%12*30:6*b)-p)%360+540)%360-180,f.style.setProperty("--angle",p+"deg"),f.style.setProperty("--hand","hour"===v&&(0===m||m>12)?e:t)}function L(){c.disabled="hour"===v,l.disabled="minute"===v}function w(){r.value=y(m)+":"+y(b)}function N(e){v!==e&&(v=e,L(),S())}function S(){i.querySelectorAll(".clock-number").forEach(function(e){e.remove()}),i.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?m:b;M(),n.forEach(function(n,a){var o=document.createElement("button"),u="hour"===v&&a>=12,c=30*("hour"===v?a%12:a),l="minute"===v&&!function(e){return e%k()===0}(n);o.type="button",o.className="clock-number",o.textContent=0===n?"00":String(n),o.dataset.value=String(n),o.disabled=l,o.setAttribute("aria-pressed",l||n!==r?"false":"true"),o.style.setProperty("--angle",c+"deg"),o.style.setProperty("--radius",u?e:t),o.addEventListener("click",function(){o.disabled||("hour"===v?(m=n,v="minute"):b=n,w(),L(),S())}),i.append(o)})}function P(){u.hidden=!0}function q(){b=E(b),r.value=y(m)+":"+y(b),S()}}function a(){document.querySelectorAll(".bs-time").forEach(r)}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",a):a()}();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bootstrap-time",
3
- "version": "0.1.6-beta",
3
+ "version": "0.1.8-beta",
4
4
  "description": "Time picker based on Bootstrap.",
5
5
  "author": "M. de Ridder",
6
6
  "license": "MIT",
@@ -60,7 +60,6 @@
60
60
  "terser": "^5.44.0"
61
61
  },
62
62
  "dependencies": {
63
- "bootstrap": ">=6.0.0-alpha.1 <7",
64
- "bootstrap-icons": "^1.13.2"
63
+ "bootstrap": ">=5.3.8 <6"
65
64
  }
66
65
  }
@@ -7,20 +7,48 @@
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 init() {
12
- var picker = document.querySelector(".clock-picker");
13
- var clock = document.querySelector("#clock");
14
- var widget = document.querySelector("#clock-widget");
15
- var timeInput = document.querySelector("#time-input");
16
- var hourArrow = document.querySelector("#hour-arrow");
17
- var minuteArrow = document.querySelector("#minute-arrow");
18
- var clockToggle = document.querySelector("#clock-toggle");
19
- if (!clock || !timeInput || !hourArrow || !minuteArrow) return;
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 widget = picker.querySelector(".clock-widget");
31
+ if (!widget) {
32
+ widget = document.createElement("div");
33
+ widget.className = "clock-widget card bg-body-secondary";
34
+ widget.hidden = true;
35
+ widget.innerHTML =
36
+ '<div class="clock-arrows">' +
37
+ '<button class="btn btn-link clock-arrow clock-hour" type="button" aria-label="Uren">' + svg(LEFT) + "</button>" +
38
+ '<button class="btn btn-link clock-arrow clock-minute" type="button" aria-label="Minuten">' + svg(RIGHT) + "</button>" +
39
+ "</div>" +
40
+ '<div class="clock position-relative mx-auto bg-body-tertiary" role="group" aria-label="Kies een uur"></div>';
41
+ picker.append(widget);
42
+ }
43
+
44
+ var clock = widget.querySelector(".clock");
45
+ var hourArrow = widget.querySelector(".clock-hour");
46
+ var minuteArrow = widget.querySelector(".clock-minute");
47
+ if (!clock || !hourArrow || !minuteArrow) return;
20
48
 
21
49
  var now = new Date();
22
- var initial = timeInput.value
23
- ? timeInput.value.split(":")
50
+ var initial = field.value
51
+ ? field.value.split(":")
24
52
  : [now.getHours(), now.getMinutes()];
25
53
  var hour = Number(initial[0]) || 0;
26
54
  var minute = snapMinute(Number(initial[1]) || 0);
@@ -40,7 +68,7 @@
40
68
  }
41
69
 
42
70
  function minuteStep() {
43
- var step = Number(timeInput.getAttribute("step") || timeInput.step);
71
+ var step = Number(field.getAttribute("step") || field.step);
44
72
  if (!step || step < 60) step = 60;
45
73
  return Math.max(1, Math.round(step / 60));
46
74
  }
@@ -78,7 +106,7 @@
78
106
  }
79
107
 
80
108
  function writeInput() {
81
- timeInput.value = pad(hour) + ":" + pad(minute);
109
+ field.value = pad(hour) + ":" + pad(minute);
82
110
  }
83
111
 
84
112
  function showMode(nextMode) {
@@ -131,20 +159,11 @@
131
159
  }
132
160
 
133
161
  function showWidget() {
134
- if (!widget) return;
135
162
  widget.hidden = false;
136
- if (clockToggle) clockToggle.setAttribute("aria-pressed", "true");
137
163
  }
138
164
 
139
165
  function hideWidget() {
140
- if (!widget) return;
141
166
  widget.hidden = true;
142
- if (clockToggle) clockToggle.setAttribute("aria-pressed", "false");
143
- }
144
-
145
- function toggleWidget() {
146
- if (widget && widget.hidden) showWidget();
147
- else hideWidget();
148
167
  }
149
168
 
150
169
  function valueFromPoint(event) {
@@ -178,13 +197,11 @@
178
197
  minuteArrow.addEventListener("click", function () {
179
198
  showMode("minute");
180
199
  });
181
- if (clockToggle) {
182
- clockToggle.addEventListener("click", toggleWidget);
183
- }
184
- timeInput.addEventListener("focus", showWidget);
185
- timeInput.addEventListener("input", function () {
186
- if (!timeInput.value) return;
187
- var parts = timeInput.value.split(":");
200
+ field.addEventListener("focus", showWidget);
201
+ field.addEventListener("blur", hideWidget);
202
+ field.addEventListener("input", function () {
203
+ if (!field.value) return;
204
+ var parts = field.value.split(":");
188
205
  var nextHour = Number(parts[0]);
189
206
  var nextMinute = Number(parts[1]);
190
207
  if (nextMinute !== minute) mode = "minute";
@@ -194,48 +211,49 @@
194
211
  updateArrows();
195
212
  renderClock();
196
213
  });
197
- if (picker) {
198
- picker.addEventListener("mousedown", function (event) {
199
- if (event.target === timeInput) return;
200
- if (clockToggle && clockToggle.contains(event.target)) return;
201
- event.preventDefault();
202
- });
203
- }
214
+ picker.addEventListener("mousedown", function (event) {
215
+ if (event.target === field) return;
216
+ event.preventDefault();
217
+ });
204
218
  document.addEventListener("mousedown", function (event) {
205
- if (!picker || picker.contains(event.target)) return;
219
+ if (picker.contains(event.target)) return;
206
220
  hideWidget();
207
221
  });
208
222
 
209
223
  function applyStep() {
210
224
  minute = snapMinute(minute);
211
- writeInput();
225
+ field.value = pad(hour) + ":" + pad(minute);
212
226
  renderClock();
213
227
  }
214
228
 
215
229
  var stepDescriptor = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "step");
216
230
  if (stepDescriptor && stepDescriptor.set) {
217
- Object.defineProperty(timeInput, "step", {
231
+ Object.defineProperty(field, "step", {
218
232
  configurable: true,
219
233
  get: function () {
220
- return stepDescriptor.get.call(timeInput);
234
+ return stepDescriptor.get.call(field);
221
235
  },
222
236
  set: function (value) {
223
- stepDescriptor.set.call(timeInput, value);
237
+ stepDescriptor.set.call(field, value);
224
238
  applyStep();
225
239
  }
226
240
  });
227
241
  }
228
- new MutationObserver(applyStep).observe(timeInput, {
242
+ new MutationObserver(applyStep).observe(field, {
229
243
  attributes: true,
230
244
  attributeFilter: ["step"]
231
245
  });
232
246
 
233
247
  updateArrows();
234
- writeInput();
248
+ if (!field.value) field.value = pad(hour) + ":" + pad(minute);
235
249
  renderClock();
236
250
  hideWidget();
237
251
  }
238
252
 
253
+ function init() {
254
+ document.querySelectorAll(".bs-time").forEach(mount);
255
+ }
256
+
239
257
  if (document.readyState === "loading") {
240
258
  document.addEventListener("DOMContentLoaded", init);
241
259
  } else {
@@ -4,34 +4,6 @@
4
4
 
5
5
  .clock-picker {
6
6
  position: relative;
7
- width: 20rem;
8
-
9
- input[type="time"]::-webkit-calendar-picker-indicator {
10
- display: none;
11
- appearance: none;
12
- }
13
- }
14
-
15
- .clock-toggle {
16
- pointer-events: auto;
17
- width: 2.25rem;
18
- height: 2.25rem;
19
- margin: -0.25rem;
20
- border: 0;
21
- border-radius: 50%;
22
- background: transparent;
23
- color: var(--bs-form-adorn-icon-color, var(--bs-secondary-color));
24
-
25
- .bi {
26
- font-size: 1rem;
27
- line-height: 1;
28
- }
29
-
30
- &:hover,
31
- &:focus-visible {
32
- background-color: var(--bs-bg-3);
33
- color: var(--bs-body-color);
34
- }
35
7
  }
36
8
 
37
9
  .clock-widget {
@@ -42,8 +14,8 @@
42
14
  width: 20rem;
43
15
  margin-top: 0.25rem;
44
16
  padding: 0.25rem 0.5rem 1rem;
45
- background-color: var(--bs-secondary-bg-subtle);
46
- color: var(--bs-secondary-fg);
17
+ background-color: var(--bs-secondary-bg);
18
+ color: var(--bs-body-color);
47
19
  }
48
20
 
49
21
  .clock-arrows {
@@ -73,7 +45,7 @@
73
45
 
74
46
  &:hover,
75
47
  &:focus-visible {
76
- background-color: var(--bs-bg-3);
48
+ background-color: var(--bs-tertiary-bg);
77
49
  color: var(--bs-body-color);
78
50
  }
79
51