diff --git a/README.md b/README.md
index 5ff911a..737e19a 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,17 @@
# ClockPicker [](http://badge.fury.io/bo/clockpicker) [](https://travis-ci.org/weareoutman/clockpicker) [](https://david-dm.org/weareoutman/clockpicker#info=devDependencies)
-A clock-style timepicker for Bootstrap (or jQuery).
-[Documentation and examples](http://weareoutman.github.io/clockpicker/).
+A clock-style timepicker for Bootstrap 4 (or Bootstrap 3 or jQuery).
+
+[Light documentation and examples for Bootstrap 4](https://jsfiddle.net/djibe89/9wj67d5u/).
+
+[Original documentation and examples for Bootstrap 3](http://weareoutman.github.io/clockpicker/).
+
+Below are the screens for Bootstrap 4 and Bootstrap 4 with Daemonite material UI
+
+
+
+
+And the original screens from Weareoutman for Bootstrap 3


@@ -15,7 +25,7 @@ Both desktop and mobile device are supported. It also works great in touch scree
## Dependencies
-ClockPicker was designed for Bootstrap in the beginning. So Bootstrap (and jQuery) is the only dependency(s).
+ClockPicker was designed for Bootstrap in the beginning. So Bootstrap (3 or 4 and jQuery) is the only dependency(s).
Since it only used `.popover` and some of `.btn` styles of Bootstrap, I picked these styles to build a jQuery plugin.
Feel free to use `jquery-*` files instead of `bootstrap-*` , for non-bootstrap project.
@@ -72,7 +82,7 @@ if (something) {
| default | '' | default time, 'now' or '13:14' e.g. |
| placement | 'bottom' | popover placement |
| align | 'left' | popover arrow align |
-| donetext | '完成' | done button text |
+| donetext | 'OK' ('完成' in BS3) | done button text |
| autoclose | false | auto close when minute is selected |
| twelvehour | false | enables twelve hour mode with AM & PM buttons |
| vibrate | true | vibrate the device when dragging clock hand |
@@ -101,10 +111,14 @@ if (something) {
```bash
clockpicker/
├── dist/
-│ ├── bootstrap-clockpicker.css # full code for bootstrap
+│ ├── bootstrap-clockpicker.css # full code for bootstrap 3
│ ├── bootstrap-clockpicker.js
-│ ├── bootstrap-clockpicker.min.css # compiled and minified files for bootstrap
+│ ├── bootstrap-clockpicker.min.css # compiled and minified files for bootstrap 3
│ ├── bootstrap-clockpicker.min.js
+| |── bootstrap4-clockpicker.css # full code for bootstrap 4
+│ ├── bootstrap4-clockpicker.js
+│ ├── bootstrap4-clockpicker.min.css # compiled and minified files for bootstrap 4
+│ ├── bootstrap4-clockpicker.min.js
│ ├── jquery-clockpicker.css # full code for jquery
│ ├── jquery-clockpicker.js
│ ├── jquery-clockpicker.min.css # compiled and minified files for jquery
@@ -134,6 +148,38 @@ gulp
- [ ] Seconds View ?
## Change log
+0.2.3
+
+* [Poradz : Prevented the popover position from overflowing outside the window](https://github.com/djibe/clockpicker/commit/191ca92ae612bf6cec4c9981e3704d9d482e0ad9).
+* [Poradz : Parsing input value in getTime function broke picked value when beforeHide or beforeDone callbacks were in use]( https://github.com/djibe/clockpicker/pull/2/commits/204417a37ad02f0f7581907368a3d0c03af865a7).
+
+0.2.2
+
+* phanku : Fixed clock picker so the clock picker will work when the trigger element is within a modal
+* SCSS source file added for easier maintenance
+* Minor CSS tweaks
+* fallback added for a Bootstrap free use (ex: background-color: var(--primary, #007bff);)
+
+0.2.1
+
+* moved AM-PM buttons to the header and removed AM-PM block
+* inverted animation for top positioned picker
+* unified CSS files (compatible with BS 4.1.3, MDBootstrap 4.5.4 and Daemonite material UI)
+* need help to fix cancel button
+
+0.2
+
+* migrated all classes to BS4
+* enhenced material design
+* added popover opening animation
+* added Cancel button (doesn't work right now :( )
+* prevent user-select on all elements
+* next version : move AM and PM buttons in popover-header, fix cancel button
+
+0.1
+
+* Bootstrap 4 compatible (tested with BS 4.1.1)
+* Universal theming using CSS variables
0.0.7
diff --git a/assets/images/clockpicker-bs4-material.png b/assets/images/clockpicker-bs4-material.png
new file mode 100644
index 0000000..0860c9e
Binary files /dev/null and b/assets/images/clockpicker-bs4-material.png differ
diff --git a/assets/images/clockpicker-bs4.png b/assets/images/clockpicker-bs4.png
new file mode 100644
index 0000000..a3c58c4
Binary files /dev/null and b/assets/images/clockpicker-bs4.png differ
diff --git a/bower.json b/bower.json
index 4acf252..889e305 100644
--- a/bower.json
+++ b/bower.json
@@ -1,10 +1,10 @@
{
"name": "clockpicker",
"description": "A clock-style timepicker for Bootstrap (or jQuery)",
- "version": "0.0.7",
+ "version": "0.2",
"main": [
- "dist/jquery-clockpicker.js",
- "dist/jquery-clockpicker.css"
+ "dist/bootstrap4-clockpicker.js",
+ "dist/bootstrap4-clockpicker.css"
],
"license": "MIT",
"ignore": [
@@ -14,7 +14,7 @@
"!LICENSE",
"!README.md"
],
- "keywords": ["timepicker", "jquery"],
+ "keywords": ["timepicker", "bootstrap4"],
"authors": [
{
"name": "Wang Shenwei",
@@ -22,7 +22,7 @@
"homepage": "http://wangshenwei.com/"
}
],
- "homepage": "http://weareoutman.github.io/clockpicker/",
+ "homepage": "https://github.com/djibe/clockpicker",
"repository": {
"type": "git",
"url": "git://github.com/weareoutman/clockpicker.git"
@@ -30,4 +30,4 @@
"dependencies": {
"jquery" : ">=1.7"
}
-}
\ No newline at end of file
+}
diff --git a/dist/bootstrap4-clockpicker.css b/dist/bootstrap4-clockpicker.css
new file mode 100644
index 0000000..88d9efa
--- /dev/null
+++ b/dist/bootstrap4-clockpicker.css
@@ -0,0 +1,273 @@
+/*!
+ * ClockPicker v0.2.2 for Bootstrap (https://weareoutman.github.io/clockpicker/)
+ * Copyright 2014 Wang Shenwei
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
+ * Bootstrap 4 compatibility by djibe (https://github.com/djibe/clockpicker)
+ */
+
+:root {
+ /* Just set your primary color in decimal RGB, here or in your own CSS */
+ --primary-color: 0, 123, 255;
+}
+
+.clockpicker .input-group-addon {
+ cursor: pointer;
+}
+
+.clockpicker-moving {
+ cursor: move;
+}
+
+.clockpicker-align-left.popover>.arrow {
+ left: 25px;
+}
+
+.clockpicker-align-top.popover>.arrow {
+ top: 17px;
+}
+
+.clockpicker-align-right.popover>.arrow {
+ left: auto;
+ right: 25px;
+}
+
+.clockpicker-align-bottom.popover>.arrow {
+ top: auto;
+ bottom: 6px;
+}
+
+.clockpicker-popover {
+ -webkit-animation: pickerFadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ animation: pickerFadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ border-radius: 4px;
+ border: 0;
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ -webkit-transform-origin: center top 0px;
+ transform-origin: center top 0px;
+ -webkit-box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px 0 rgba(0, 0, 0, 0.2);
+ box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14), 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px 0 rgba(0, 0, 0, 0.2);
+}
+
+.clockpicker-popover.top {
+ -webkit-transform-origin: center bottom 0px;
+ transform-origin: center bottom 0px;
+}
+
+.clockpicker-popover * {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.clockpicker-popover .popover-header {
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ background-color: var(--primary, #007bff);
+ color: #fff;
+ display: -webkit-box;
+ display: -ms-flexbox;
+ display: flex;
+ font-size: 3rem;
+ font-weight: normal;
+ letter-spacing: normal;
+ text-align: center;
+ padding: 0.5rem;
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+}
+
+.clockpicker-popover .popover-header span {
+ cursor: pointer;
+}
+
+.clockpicker-popover .popover-body {
+ background-color: #fff;
+ padding: 1rem 0.75rem 0.75rem;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+
+.clockpicker-popover .btn {
+ border: 0 !important;
+ border-radius: 4px;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ font-size: 0.8125rem;
+ font-weight: 500;
+ padding: 0.59375rem 1rem;
+ min-width: 0;
+ margin: 0;
+ margin-left: 0.25rem;
+ text-transform: uppercase;
+}
+
+.clockpicker-popover .btn:focus,
+.clockpicker-popover .btn:hover,
+.clockpicker-popover .btn:active {
+ outline: none !important;
+ background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 0.12)), to(rgba(0, 0, 0, 0.12)));
+ background-image: linear-gradient(180deg, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0.12));
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+.clockpicker-span-hours {
+ margin-right: 0.25rem;
+}
+
+.clockpicker-span-minutes {
+ margin-left: 0.25rem;
+}
+
+.clockpicker-close-block {
+ margin-top: 0.75rem;
+}
+
+.clockpicker-plate {
+ background-color: #ededee;
+ border-radius: 50%;
+ width: 200px;
+ height: 200px;
+ overflow: visible;
+ position: relative;
+}
+
+.clockpicker-canvas,
+.clockpicker-dial {
+ width: 200px;
+ height: 200px;
+ position: absolute;
+ left: -1px;
+ top: -1px;
+}
+
+.clockpicker-minutes {
+ visibility: hidden;
+}
+
+.clockpicker-tick {
+ border-radius: 50%;
+ line-height: 26px;
+ text-align: center;
+ width: 26px;
+ height: 26px;
+ position: absolute;
+ cursor: pointer;
+}
+
+.clockpicker-tick.active,
+.clockpicker-tick:not(.disabled):hover {
+ background-color: rgba(var(--primary-color, 0, 123, 255), 0.25);
+}
+
+.clockpicker-tick.disabled {
+ color: #eee;
+ cursor: default;
+}
+
+.clockpicker-dial {
+ -webkit-transition: opacity 350ms, -webkit-transform 350ms;
+ transition: opacity 350ms, -webkit-transform 350ms;
+ transition: transform 350ms, opacity 350ms;
+ transition: transform 350ms, opacity 350ms, -webkit-transform 350ms;
+}
+
+.clockpicker-dial-out {
+ opacity: 0;
+}
+
+.clockpicker-hours.clockpicker-dial-out {
+ -webkit-transform: scale(1.2, 1.2);
+ transform: scale(1.2, 1.2);
+}
+
+.clockpicker-minutes.clockpicker-dial-out {
+ -webkit-transform: scale(0.8, 0.8);
+ transform: scale(0.8, 0.8);
+}
+
+.clockpicker-canvas {
+ -webkit-transition: opacity 175ms;
+ transition: opacity 175ms;
+}
+
+.clockpicker-canvas-out {
+ opacity: 0.25;
+}
+
+.clockpicker-canvas line {
+ stroke: var(--primary, #007bff);
+ stroke-width: 2;
+ stroke-linecap: round;
+}
+
+.clockpicker-canvas-bearing {
+ stroke: none;
+ fill: var(--primary, #007bff);
+}
+
+.clockpicker-canvas-fg {
+ stroke: none;
+ fill: rgba(var(--primary-color, 0, 123, 255), 0.5);
+}
+
+.clockpicker-canvas-bg {
+ stroke: none;
+ fill: rgba(var(--primary-color, 0, 123, 255), 0.25);
+}
+
+.clockpicker-canvas-bg-trans {
+ fill: rgba(var(--primary-color, 0, 123, 255), 0.25);
+}
+
+.clockpicker-buttons-am-pm {
+ color: white;
+ display: none;
+ -ms-flex-wrap: nowrap;
+ flex-wrap: nowrap;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -ms-flex-pack: distribute;
+ justify-content: space-around;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ font-size: 1rem;
+ margin-left: 0.75rem;
+}
+
+@-webkit-keyframes pickerFadeIn {
+ from {
+ opacity: 0;
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ }
+
+ to {
+ opacity: 1;
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ }
+}
+
+@keyframes pickerFadeIn {
+ from {
+ opacity: 0;
+ -webkit-transform: scale(0.8);
+ transform: scale(0.8);
+ }
+
+ to {
+ opacity: 1;
+ -webkit-transform: scale(1);
+ transform: scale(1);
+ }
+}
\ No newline at end of file
diff --git a/dist/bootstrap4-clockpicker.js b/dist/bootstrap4-clockpicker.js
new file mode 100644
index 0000000..5062952
--- /dev/null
+++ b/dist/bootstrap4-clockpicker.js
@@ -0,0 +1,978 @@
+/*!
+ * ClockPicker v0.2.3 original by (http://weareoutman.github.io/clockpicker/)
+ * Copyright 2014 Wang Shenwei.
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
+ * Bootstrap 4 support by djibe
+ */
+
+(function($) {
+ var $win = $(window),
+ $doc = $(document),
+ $body;
+
+ // Can I use inline svg ?
+ var svgNS = "http://www.w3.org/2000/svg",
+ svgSupported =
+ "SVGAngle" in window &&
+ (function() {
+ var supported,
+ el = document.createElement("div");
+ el.innerHTML = "";
+ supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS;
+ el.innerHTML = "";
+ return supported;
+ })();
+
+ // Can I use transition ?
+ var transitionSupported = (function() {
+ var style = document.createElement("div").style;
+ return (
+ "transition" in style ||
+ "WebkitTransition" in style ||
+ "MozTransition" in style ||
+ "msTransition" in style ||
+ "OTransition" in style
+ );
+ })();
+
+ // Listen touch events in touch screen device, instead of mouse events in desktop.
+ var touchSupported = "ontouchstart" in window,
+ mousedownEvent = "mousedown" + (touchSupported ? " touchstart" : ""),
+ mousemoveEvent =
+ "mousemove.clockpicker" +
+ (touchSupported ? " touchmove.clockpicker" : ""),
+ mouseupEvent =
+ "mouseup.clockpicker" + (touchSupported ? " touchend.clockpicker" : "");
+
+ // Vibrate the device if supported
+ var vibrate = navigator.vibrate
+ ? "vibrate"
+ : navigator.webkitVibrate
+ ? "webkitVibrate"
+ : null;
+
+ function createSvgElement(name) {
+ return document.createElementNS(svgNS, name);
+ }
+
+ function leadingZero(num) {
+ return (num < 10 ? "0" : "") + num;
+ }
+
+ // Get a unique id
+ var idCounter = 0;
+ function uniqueId(prefix) {
+ var id = ++idCounter + "";
+ return prefix ? prefix + id : id;
+ }
+
+ // Clock size
+ var dialRadius = 100,
+ outerRadius = 80,
+ // innerRadius = 80 on 12 hour clock
+ innerRadius = 54,
+ tickRadius = 13;
+ (diameter = dialRadius * 2), (duration = transitionSupported ? 350 : 1);
+
+ // Popover template
+ var tpl = [
+ '
"
+ ].join("");
+
+ // ClockPicker
+ function ClockPicker(element, options) {
+ var popover = $(tpl),
+ plate = popover.find(".clockpicker-plate"),
+ hoursView = popover.find(".clockpicker-hours"),
+ minutesView = popover.find(".clockpicker-minutes"),
+ isInput = element.prop("tagName") === "INPUT",
+ input = isInput ? element : element.find("input"),
+ isHTML5 = input.prop("type") === "time",
+ addon = element.find(".input-group-addon"),
+ popoverBody = popover.find(".popover-body"),
+ closeBlock = popoverBody.find(".clockpicker-close-block"),
+ self = this,
+ timer;
+
+ this.id = uniqueId("cp");
+ this.element = element;
+ this.options = options;
+ this.options.hourstep = this.parseStep(this.options.hourstep, 12);
+ this.options.minutestep = this.parseStep(this.options.minutestep, 60);
+ this.isAppended = false;
+ this.isShown = false;
+ this.currentView = "hours";
+ this.isInput = isInput;
+ this.isHTML5 = isHTML5;
+ this.input = input;
+ this.addon = addon;
+ this.popover = popover;
+ this.plate = plate;
+ this.hoursView = hoursView;
+ this.minutesView = minutesView;
+ this.spanHours = popover.find(".clockpicker-span-hours");
+ this.spanMinutes = popover.find(".clockpicker-span-minutes");
+ this.buttonsAmPm = popover.find(".clockpicker-buttons-am-pm");
+ this.currentPlacementClass = options.placement;
+ this.raiseCallback = function() {
+ raiseCallback.apply(self, arguments);
+ };
+
+ // Setup for for 12 hour clock if option is selected
+ if (options.twelvehour) {
+ $(this.buttonsAmPm).css("display", "flex");
+
+ $('AM')
+ .on("click", function() {
+ self.amOrPm = "AM";
+ $(this).removeClass("text-white-50");
+ $(".btn-pm").addClass("text-white-50");
+ if (options.ampmSubmit) {
+ setTimeout(function() {
+ self.done();
+ }, duration / 2);
+ }
+ })
+ .appendTo(this.buttonsAmPm);
+
+ $('PM')
+ .on("click", function() {
+ self.amOrPm = "PM";
+ $(this).removeClass("text-white-50");
+ $(".btn-am").addClass("text-white-50");
+ if (options.ampmSubmit) {
+ setTimeout(function() {
+ self.done();
+ }, duration / 2);
+ }
+ })
+ .appendTo(this.buttonsAmPm);
+ }
+
+ if (!options.autoclose) {
+ // If autoclose is not setted, append a button
+ closeBlock
+ .append(
+ '"
+ )
+ .on("click", ".cancel", function () {
+ self.hide();
+ });
+
+ closeBlock
+ .css("display", "flex")
+ .append(
+ '"
+ )
+ .on("click", ".done", $.proxy(this.done, this));
+ }
+
+ // Placement and arrow align - make sure they make sense.
+ if (
+ /^(top|bottom)/.test(options.placement) &&
+ (options.align === "top" || options.align === "bottom")
+ )
+ options.align = "left";
+ if (
+ (options.placement === "left" || options.placement === "right") &&
+ (options.align === "left" || options.align === "right")
+ )
+ options.align = "top";
+
+ popover.addClass(options.placement);
+ popover.addClass("clockpicker-align-" + options.align);
+
+ this.spanHours.click($.proxy(this.toggleView, this, "hours"));
+ this.spanMinutes.click($.proxy(this.toggleView, this, "minutes"));
+
+ // Show or toggle
+ if (!options.addonOnly) {
+ input.on("focus.clockpicker click.clockpicker", $.proxy(this.show, this));
+ }
+ addon.on("click.clockpicker", $.proxy(this.toggle, this));
+
+ // Build ticks
+ var tickTpl = $(''),
+ i,
+ tick,
+ radian,
+ radius;
+
+ // Hours view
+ if (options.twelvehour) {
+ for (i = 0; i < 12; i += options.hourstep) {
+ tick = tickTpl.clone();
+ radian = (i / 6) * Math.PI;
+ radius = outerRadius;
+ tick.css("font-size", "120%");
+ tick.css({
+ left: dialRadius + Math.sin(radian) * radius - tickRadius,
+ top: dialRadius - Math.cos(radian) * radius - tickRadius
+ });
+ tick.html(i === 0 ? 12 : i);
+ hoursView.append(tick);
+ tick.on(mousedownEvent, mousedown);
+ }
+ } else {
+ for (i = 0; i < 24; i += options.hourstep) {
+ var isDisabled = false;
+ if (
+ options.disabledhours &&
+ $.inArray(i, options.disabledhours) != -1
+ ) {
+ var isDisabled = true;
+ }
+ tick = tickTpl.clone();
+ radian = (i / 6) * Math.PI;
+ var inner = i > 0 && i < 13;
+ radius = inner ? innerRadius : outerRadius;
+ tick.css({
+ left: dialRadius + Math.sin(radian) * radius - tickRadius,
+ top: dialRadius - Math.cos(radian) * radius - tickRadius
+ });
+ if (inner) {
+ tick.css("font-size", "120%");
+ }
+ if (isDisabled) {
+ tick.addClass("disabled");
+ }
+ tick.html(i === 0 ? "00" : i);
+ hoursView.append(tick);
+ if (!isDisabled) {
+ tick.on(mousedownEvent, mousedown);
+ }
+ }
+ }
+
+ // Minutes view
+ var incrementValue = Math.max(options.minutestep, 5);
+ for (i = 0; i < 60; i += incrementValue) {
+ tick = tickTpl.clone();
+ radian = (i / 30) * Math.PI;
+ tick.css({
+ left: dialRadius + Math.sin(radian) * outerRadius - tickRadius,
+ top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
+ });
+ tick.css("font-size", "120%");
+ tick.html(leadingZero(i));
+ minutesView.append(tick);
+ tick.on(mousedownEvent, mousedown);
+ }
+
+ // Clicking on minutes view space
+ plate.on(mousedownEvent, function(e) {
+ if ($(e.target).closest(".clockpicker-tick").length === 0) {
+ mousedown(e, true);
+ }
+ });
+
+ // Mousedown or touchstart
+ function mousedown(e, space) {
+ var offset = plate.offset(),
+ isTouch = /^touch/.test(e.type),
+ x0 = offset.left + dialRadius,
+ y0 = offset.top + dialRadius,
+ dx = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
+ dy = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0,
+ z = Math.sqrt(dx * dx + dy * dy),
+ moved = false;
+
+ // When clicking on minutes view space, check the mouse position
+ if (
+ space &&
+ (z < outerRadius - tickRadius || z > outerRadius + tickRadius)
+ ) {
+ return;
+ }
+ e.preventDefault();
+
+ // Set cursor style of body after 200ms
+ var movingTimer = setTimeout(function() {
+ $body.addClass("clockpicker-moving");
+ }, 200);
+
+ // Place the canvas to top
+ if (svgSupported) {
+ plate.append(self.canvas);
+ }
+
+ // Clock
+ self.setHand(dx, dy, true);
+
+ // Mousemove on document
+ $doc.off(mousemoveEvent).on(mousemoveEvent, function(e) {
+ e.preventDefault();
+ var isTouch = /^touch/.test(e.type),
+ x = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
+ y = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0;
+ if (!moved && x === dx && y === dy) {
+ // Clicking in chrome on windows will trigger a mousemove event
+ return;
+ }
+ moved = true;
+ self.setHand(x, y, true);
+ });
+
+ // Mouseup on document
+ $doc.off(mouseupEvent).on(mouseupEvent, function(e) {
+ $doc.off(mouseupEvent);
+ e.preventDefault();
+ var isTouch = /^touch/.test(e.type),
+ x = (isTouch ? e.originalEvent.changedTouches[0] : e).pageX - x0,
+ y = (isTouch ? e.originalEvent.changedTouches[0] : e).pageY - y0;
+ if ((space || moved) && x === dx && y === dy) {
+ self.setHand(x, y);
+ }
+ if (self.currentView === "hours") {
+ self.toggleView("minutes", duration / 2);
+ } else {
+ if (options.autoclose) {
+ if (!options.ampmSubmit) {
+ self.minutesView.addClass("clockpicker-dial-out");
+ setTimeout(function() {
+ self.done();
+ }, duration / 2);
+ }
+ }
+ }
+ plate.prepend(canvas);
+
+ // Reset cursor style of body
+ clearTimeout(movingTimer);
+ $body.removeClass("clockpicker-moving");
+
+ // Unbind mousemove event
+ $doc.off(mousemoveEvent);
+ });
+ }
+
+ if (svgSupported) {
+ // Draw clock hands and others
+ var canvas = popover.find(".clockpicker-canvas"),
+ svg = createSvgElement("svg");
+ svg.setAttribute("class", "clockpicker-svg");
+ svg.setAttribute("width", diameter);
+ svg.setAttribute("height", diameter);
+ var g = createSvgElement("g");
+ g.setAttribute(
+ "transform",
+ "translate(" + dialRadius + "," + dialRadius + ")"
+ );
+ var bearing = createSvgElement("circle");
+ bearing.setAttribute("class", "clockpicker-canvas-bearing");
+ bearing.setAttribute("cx", 0);
+ bearing.setAttribute("cy", 0);
+ bearing.setAttribute("r", 3);
+ var hand = createSvgElement("line");
+ hand.setAttribute("x1", 0);
+ hand.setAttribute("y1", 0);
+ var bg = createSvgElement("circle");
+ bg.setAttribute("class", "clockpicker-canvas-bg");
+ bg.setAttribute("r", tickRadius);
+ var fg = createSvgElement("circle");
+ fg.setAttribute("class", "clockpicker-canvas-fg");
+ fg.setAttribute("r", 3.5);
+ g.appendChild(hand);
+ g.appendChild(bg);
+ g.appendChild(fg);
+ g.appendChild(bearing);
+ svg.appendChild(g);
+ canvas.append(svg);
+
+ this.hand = hand;
+ this.bg = bg;
+ this.fg = fg;
+ this.bearing = bearing;
+ this.g = g;
+ this.canvas = canvas;
+ }
+
+ this.raiseCallback(this.options.init, "init");
+ }
+
+ function raiseCallback(callbackFunction, triggerName) {
+ if (
+ callbackFunction &&
+ typeof callbackFunction === "function" &&
+ this.element
+ ) {
+ var time = this.getTime() || null;
+ callbackFunction.call(this.element, time);
+ }
+ if (triggerName) {
+ this.element.trigger("clockpicker." + triggerName || "NoName");
+ }
+ }
+
+ /**
+ * Find most suitable vertical placement, doing our best to ensure it is inside of the viewport.
+ *
+ * First try to place the element according with preferredPlacement, then try the opposite
+ * placement and as a last resort, popover will be placed on the very top of the viewport.
+ *
+ * @param {jQuery} element
+ * @param {jQuery} popover
+ * @param preferredPlacement Preferred placement, if there is enough room for it.
+ * @returns {string} One of: 'top', 'bottom' or 'viewport-top'.
+ */
+ function resolveAdaptiveVerticalPlacement(
+ element,
+ popover,
+ preferredPlacement
+ ) {
+ var popoverHeight = popover.outerHeight(),
+ elementHeight = element.outerHeight(),
+ elementTopOffset = element.offset().top,
+ elementBottomOffset = element.offset().top + elementHeight,
+ minVisibleY = elementTopOffset - element[0].getBoundingClientRect().top,
+ maxVisibleY = minVisibleY + document.documentElement.clientHeight,
+ isEnoughRoomAbove = elementTopOffset - popoverHeight >= minVisibleY,
+ isEnoughRoomBelow = elementBottomOffset + popoverHeight <= maxVisibleY;
+
+ if (preferredPlacement === "top") {
+ if (isEnoughRoomAbove) {
+ return "top";
+ } else if (isEnoughRoomBelow) {
+ return "bottom";
+ }
+ } else {
+ if (isEnoughRoomBelow) {
+ return "bottom";
+ } else if (isEnoughRoomAbove) {
+ return "top";
+ }
+ }
+
+ return "viewport-top";
+ }
+
+ ClockPicker.prototype.parseStep = function(givenStepSize, wholeSize) {
+ return wholeSize % givenStepSize === 0 ? givenStepSize : 1;
+ };
+
+ // Default options
+ ClockPicker.DEFAULTS = {
+ default: "", // default time, 'now' or '13:14' e.g.
+ fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
+ placement: "bottom", // clock popover placement
+ align: "left", // popover arrow align
+ donetext: "OK", // done button text
+ canceltext: "Cancel", // cancel button text
+ autoclose: false, // auto close when minute is selected
+ twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
+ vibrate: true, // vibrate the device when dragging clock hand
+ hourstep: 1, // allow to multi increment the hour
+ minutestep: 1, // allow to multi increment the minute
+ ampmSubmit: false, // allow submit with AM and PM buttons instead of the minute selection/picker
+ addonOnly: false, // only open on clicking on the input-addon
+ disabledhours: null // disabled hours (only 24 hour mode)
+ };
+
+ // Show or hide popover
+ ClockPicker.prototype.toggle = function() {
+ this[this.isShown ? "hide" : "show"]();
+ };
+
+ // Set new placement class for popover and remove the old one, if any.
+ ClockPicker.prototype.updatePlacementClass = function(newClass) {
+ if (this.currentPlacementClass) {
+ this.popover.removeClass(this.currentPlacementClass);
+ }
+ if (newClass) {
+ this.popover.addClass(newClass);
+ }
+
+ this.currentPlacementClass = newClass;
+ };
+
+ // Set popover position and update placement class, if needed
+ ClockPicker.prototype.locate = function() {
+ var element = this.element,
+ popover = this.popover,
+ offset = element.offset(),
+ width = element.outerWidth(),
+ height = element.outerHeight(),
+ placement = this.options.placement,
+ align = this.options.align,
+ windowHeight = $win.height(),
+ windowWidth = $win.width(),
+ popoverHeight = popover.height(),
+ popoverWidth = popover.width(),
+ styles = {},
+ self = this;
+
+ if (placement === "top-adaptive" || placement === "bottom-adaptive") {
+ var preferredPlacement = placement.substr(0, placement.indexOf("-"));
+ // Adaptive placement should be resolved into one of the "static" placement
+ // options, that is best suitable for the current window scroll position.
+ placement = resolveAdaptiveVerticalPlacement(
+ element,
+ popover,
+ preferredPlacement
+ );
+
+ this.updatePlacementClass(placement !== "viewport-top" ? placement : "");
+ }
+
+ popover.show();
+
+ // Place the popover
+ switch (placement) {
+ case "bottom":
+ styles.top = offset.top + height;
+ break;
+ case "right":
+ styles.left = offset.left + width;
+ break;
+ case "top":
+ styles.top = offset.top - popover.outerHeight();
+ break;
+ case "left":
+ styles.left = offset.left - popover.outerWidth();
+ break;
+ case "viewport-top":
+ styles.top = offset.top - element[0].getBoundingClientRect().top;
+ break;
+ }
+
+ // Align the popover arrow
+ switch (align) {
+ case "left":
+ styles.left = offset.left;
+ break;
+ case "right":
+ styles.left = offset.left + width - popover.outerWidth();
+ break;
+ case "top":
+ styles.top = offset.top;
+ break;
+ case "bottom":
+ styles.top = offset.top + height - popover.outerHeight();
+ break;
+ }
+
+ // Correct the popover position outside the window
+ if (popoverHeight + styles.top > windowHeight) {
+ styles.top = windowHeight - popoverHeight;
+ }
+ if (popoverWidth + styles.left > windowWidth) {
+ styles.left = windowWidth - popoverWidth;
+ }
+
+ popover.css(styles);
+ };
+
+ // The input can be changed by the user
+ // So before we can use this.hours/this.minutes we must update it
+ ClockPicker.prototype.parseInputValue = function() {
+ var value = this.input.prop("value") || this.options["default"] || "";
+
+ if (value === "now") {
+ value = new Date(+new Date() + this.options.fromnow);
+ }
+ if (value instanceof Date) {
+ value = value.getHours() + ":" + value.getMinutes();
+ }
+
+ value = value.split(":");
+
+ // Minutes can have AM/PM that needs to be removed
+ this.hours = +value[0] || 0;
+ this.minutes = +(value[1] + "").replace(/\D/g, "") || 0;
+
+ this.hours =
+ Math.round(this.hours / this.options.hourstep) * this.options.hourstep;
+ this.minutes =
+ Math.round(this.minutes / this.options.minutestep) *
+ this.options.minutestep;
+
+ if (this.options.twelvehour) {
+ var period = (value[1] + "").replace(/\d+/g, "").toLowerCase();
+ //this.amOrPm = this.hours > 12 || period === "pm" ? "PM" : "AM";
+ this.amOrPm = this.hours < 12 || period === "am" ? "AM" : "PM";
+ }
+ };
+
+ // Show popover
+ ClockPicker.prototype.show = function(e) {
+ // Not show again
+ if (this.isShown) {
+ return;
+ }
+
+ this.raiseCallback(this.options.beforeShow, "beforeShow");
+
+ var self = this;
+
+ // Initialize
+ if (!this.isAppended) {
+ // Append popover to body
+ $body = $(document.body).append(this.popover);
+
+ // Reset position when resize
+ $win.on("resize.clockpicker" + this.id, function() {
+ if (self.isShown) {
+ self.locate();
+ }
+ });
+
+ this.isAppended = true;
+ }
+
+ // Get the time from the input field
+ this.parseInputValue();
+
+ this.spanHours.html(leadingZero(this.hours));
+ this.spanMinutes.html(leadingZero(this.minutes));
+
+ // Toggle to hours view
+ this.toggleView("hours");
+
+ // Set position
+ this.locate();
+
+ this.isShown = true;
+
+ // Hide when clicking or tabbing on any element except the clock, input and addon
+ $doc.on(
+ "click.clockpicker." + this.id + " focusin.clockpicker." + this.id,
+ function(e) {
+ var target = $(e.target);
+ if (
+ target.closest(self.popover).length === 0 &&
+ target.closest(self.addon).length === 0 &&
+ target.closest(self.input).length === 0
+ ) {
+ self.hide();
+ }
+ }
+ );
+
+ // Hide when ESC is pressed
+ $doc.on("keyup.clockpicker." + this.id, function(e) {
+ if (e.keyCode === 27) {
+ self.hide();
+ }
+ });
+
+ this.raiseCallback(this.options.afterShow, "afterShow");
+ };
+
+ // Hide popover
+ ClockPicker.prototype.hide = function() {
+ this.raiseCallback(this.options.beforeHide, "beforeHide");
+
+ this.isShown = false;
+
+ // Unbinding events on document
+ $doc.off(
+ "click.clockpicker." + this.id + " focusin.clockpicker." + this.id
+ );
+ $doc.off("keyup.clockpicker." + this.id);
+
+ this.popover.hide();
+
+ this.raiseCallback(this.options.afterHide, "afterHide");
+ };
+
+ // Toggle to hours or minutes view
+ ClockPicker.prototype.toggleView = function(view, delay) {
+ var raiseAfterHourSelect = false;
+ if (
+ view === "minutes" &&
+ $(this.hoursView).css("visibility") === "visible"
+ ) {
+ this.raiseCallback(this.options.beforeHourSelect, "beforeHourSelect");
+ raiseAfterHourSelect = true;
+ }
+ var isHours = view === "hours",
+ nextView = isHours ? this.hoursView : this.minutesView,
+ hideView = isHours ? this.minutesView : this.hoursView;
+
+ this.currentView = view;
+
+ this.spanHours.toggleClass("text-white-50", !isHours);
+ this.spanMinutes.toggleClass("text-white-50", isHours);
+
+ // Let's make transitions
+ hideView.addClass("clockpicker-dial-out");
+ nextView.css("visibility", "visible").removeClass("clockpicker-dial-out");
+
+ // Reset clock hand
+ this.resetClock(delay);
+
+ // After transitions ended
+ clearTimeout(this.toggleViewTimer);
+ this.toggleViewTimer = setTimeout(function() {
+ hideView.css("visibility", "hidden");
+ }, duration);
+
+ if (raiseAfterHourSelect) {
+ this.raiseCallback(this.options.afterHourSelect, "afterHourSelect");
+ }
+ };
+
+ // Reset clock hand
+ ClockPicker.prototype.resetClock = function(delay) {
+ var view = this.currentView,
+ value = this[view],
+ isHours = view === "hours",
+ unit = Math.PI / (isHours ? 6 : 30),
+ radian = value * unit,
+ radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
+ x = Math.sin(radian) * radius,
+ y = -Math.cos(radian) * radius,
+ self = this;
+ if (svgSupported && delay) {
+ self.canvas.addClass("clockpicker-canvas-out");
+ setTimeout(function() {
+ self.canvas.removeClass("clockpicker-canvas-out");
+ self.setHand(x, y);
+ }, delay);
+ } else {
+ this.setHand(x, y);
+ }
+ };
+
+ // Set clock hand to (x, y)
+ ClockPicker.prototype.setHand = function(x, y, dragging) {
+ var radian = Math.atan2(x, -y),
+ isHours = this.currentView === "hours",
+ z = Math.sqrt(x * x + y * y),
+ options = this.options,
+ inner = isHours && z < (outerRadius + innerRadius) / 2,
+ radius = inner ? innerRadius : outerRadius,
+ unit,
+ value;
+
+ // Calculate the unit
+ if (isHours) {
+ unit = (options.hourstep / 6) * Math.PI;
+ } else {
+ unit = (options.minutestep / 30) * Math.PI;
+ }
+
+ if (options.twelvehour) {
+ radius = outerRadius;
+ }
+
+ // Radian should in range [0, 2PI]
+ if (radian < 0) {
+ radian = Math.PI * 2 + radian;
+ }
+
+ // Get the round value
+ value = Math.round(radian / unit);
+
+ // Get the round radian
+ radian = value * unit;
+
+ // Correct the hours or minutes
+ if (isHours) {
+ value *= options.hourstep;
+
+ if (!options.twelvehour && !inner == value > 0) {
+ value += 12;
+ }
+ if (options.twelvehour && value === 0) {
+ value = 12;
+ }
+ if (value === 24) {
+ value = 0;
+ }
+ if (
+ dragging &&
+ !options.twelvehour &&
+ options.disabledhours &&
+ $.inArray(value, options.disabledhours) != -1
+ ) {
+ return;
+ }
+ } else {
+ value *= options.minutestep;
+ if (value === 60) {
+ value = 0;
+ }
+ }
+
+ // Once hours or minutes changed, vibrate the device
+ if (this[this.currentView] !== value) {
+ if (vibrate && this.options.vibrate) {
+ // Do not vibrate too frequently
+ if (!this.vibrateTimer) {
+ navigator[vibrate](10);
+ this.vibrateTimer = setTimeout(
+ $.proxy(function() {
+ this.vibrateTimer = null;
+ }, this),
+ 100
+ );
+ }
+ }
+ }
+
+ this[this.currentView] = value;
+ this[isHours ? "spanHours" : "spanMinutes"].html(leadingZero(value));
+
+ // If svg is not supported, just add an active class to the tick
+ if (!svgSupported) {
+ this[isHours ? "hoursView" : "minutesView"]
+ .find(".clockpicker-tick")
+ .each(function() {
+ var tick = $(this);
+ tick.toggleClass("active", value === +tick.html());
+ });
+ return;
+ }
+
+ // Place clock hand at the top when dragging
+ if (dragging || (!isHours && value % 5)) {
+ this.g.insertBefore(this.hand, this.bearing);
+ this.g.insertBefore(this.bg, this.fg);
+ this.bg.setAttribute(
+ "class",
+ "clockpicker-canvas-bg clockpicker-canvas-bg-trans"
+ );
+ } else {
+ // Or place it at the bottom
+ this.g.insertBefore(this.hand, this.bg);
+ this.g.insertBefore(this.fg, this.bg);
+ this.bg.setAttribute("class", "clockpicker-canvas-bg");
+ }
+
+ // Set clock hand and others' position
+ var cx = Math.sin(radian) * radius,
+ cy = -Math.cos(radian) * radius;
+ this.hand.setAttribute("x2", cx);
+ this.hand.setAttribute("y2", cy);
+ this.bg.setAttribute("cx", cx);
+ this.bg.setAttribute("cy", cy);
+ this.fg.setAttribute("cx", cx);
+ this.fg.setAttribute("cy", cy);
+ };
+
+ // Allow user to get time time as Date object
+ ClockPicker.prototype.getTime = function(callback) {
+ var hours = this.hours;
+ if (this.options.twelvehour && hours < 12 && this.amOrPm === "PM") {
+ hours += 12;
+ }
+
+ var selectedTime = new Date();
+ selectedTime.setMinutes(this.minutes);
+ selectedTime.setHours(hours);
+ selectedTime.setSeconds(0);
+
+ return (
+ (callback && callback.apply(this.element, selectedTime)) || selectedTime
+ );
+ };
+
+ // Hours and minutes are selected
+ ClockPicker.prototype.done = function() {
+ this.raiseCallback(this.options.beforeDone, "beforeDone");
+ this.hide();
+ var last = this.input.prop("value"),
+ outHours = this.hours,
+ value = ":" + leadingZero(this.minutes);
+
+ if (this.isHTML5 && this.options.twelvehour) {
+ if (this.hours < 12 && this.amOrPm === "PM") {
+ outHours += 12;
+ }
+ if (this.hours === 12 && this.amOrPm === "AM") {
+ outHours = 0;
+ }
+ }
+
+ value = leadingZero(outHours) + value;
+
+ if (!this.isHTML5 && this.options.twelvehour) {
+ value = value + this.amOrPm;
+ }
+
+ this.input.prop("value", value);
+ if (value !== last) {
+ this.input.trigger("change");
+ if (!this.isInput) {
+ this.element.trigger("change");
+ }
+ }
+
+ if (this.options.autoclose) {
+ this.input.trigger("blur");
+ }
+
+ this.raiseCallback(this.options.afterDone, "afterDone");
+ };
+
+ // Remove clockpicker from input
+ ClockPicker.prototype.remove = function() {
+ this.element.removeData("clockpicker");
+ this.input.off("focus.clockpicker click.clockpicker");
+ this.addon.off("click.clockpicker");
+ if (this.isShown) {
+ this.hide();
+ }
+ if (this.isAppended) {
+ $win.off("resize.clockpicker" + this.id);
+ this.popover.remove();
+ }
+ };
+
+ // Extends $.fn.clockpicker
+ $.fn.clockpicker = function(option) {
+ var args = Array.prototype.slice.call(arguments, 1);
+
+ function handleClockPickerRequest() {
+ var $this = $(this),
+ data = $this.data("clockpicker");
+ if (!data) {
+ var options = $.extend(
+ {},
+ ClockPicker.DEFAULTS,
+ $this.data(),
+ typeof option == "object" && option
+ );
+ $this.data("clockpicker", new ClockPicker($this, options));
+ } else {
+ // Manual operations. show, hide, remove, getTime, e.g.
+ if (typeof data[option] === "function") {
+ return data[option].apply(data, args);
+ }
+ }
+ }
+
+ // If we explicitly do a call on a single element then we can return the value (if needed)
+ // This allows us, for example, to return the value of getTime
+ if (this.length == 1) {
+ var returnValue = handleClockPickerRequest.apply(this[0]);
+
+ // If we do not have any return value then return the object itself so you can chain
+ return returnValue !== undefined ? returnValue : this;
+ }
+
+ // If we do have a list then we do not care about return values
+ return this.each(handleClockPickerRequest);
+ };
+})(jQuery);
diff --git a/dist/bootstrap4-clockpicker.min.css b/dist/bootstrap4-clockpicker.min.css
new file mode 100644
index 0000000..c9322e0
--- /dev/null
+++ b/dist/bootstrap4-clockpicker.min.css
@@ -0,0 +1,6 @@
+/*!
+ * ClockPicker v0.2.2 for Bootstrap (https://weareoutman.github.io/clockpicker/)
+ * Copyright 2014 Wang Shenwei
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
+ * Bootstrap 4 compatibility by djibe (https://github.com/djibe/clockpicker)
+ */:root{--primary-color:0,123,255}.clockpicker .input-group-addon{cursor:pointer}.clockpicker-moving{cursor:move}.clockpicker-align-left.popover>.arrow{left:25px}.clockpicker-align-top.popover>.arrow{top:17px}.clockpicker-align-right.popover>.arrow{left:auto;right:25px}.clockpicker-align-bottom.popover>.arrow{top:auto;bottom:6px}.clockpicker-popover{-webkit-animation:pickerFadeIn .2s cubic-bezier(.4,0,.2,1);animation:pickerFadeIn .2s cubic-bezier(.4,0,.2,1);border-radius:4px;border:0;-webkit-transform:scale(1);transform:scale(1);-webkit-transform-origin:center top 0;transform-origin:center top 0;-webkit-box-shadow:0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12),0 11px 15px 0 rgba(0,0,0,.2);box-shadow:0 24px 38px 3px rgba(0,0,0,.14),0 9px 46px 8px rgba(0,0,0,.12),0 11px 15px 0 rgba(0,0,0,.2)}.clockpicker-popover.top{-webkit-transform-origin:center bottom 0;transform-origin:center bottom 0}.clockpicker-popover *{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.clockpicker-popover .popover-header{-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;background-color:var(--primary,#007bff);color:#fff;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:3rem;font-weight:400;letter-spacing:normal;text-align:center;padding:.5rem;border-top-left-radius:4px;border-top-right-radius:4px}.clockpicker-popover .popover-header span{cursor:pointer}.clockpicker-popover .popover-body{background-color:#fff;padding:1rem .75rem .75rem;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.clockpicker-popover .btn{border:0!important;border-radius:4px;-webkit-box-shadow:none;box-shadow:none;font-size:.8125rem;font-weight:500;padding:.59375rem 1rem;min-width:0;margin:0 0 0 .25rem;text-transform:uppercase}.clockpicker-popover .btn:active,.clockpicker-popover .btn:focus,.clockpicker-popover .btn:hover{outline:0!important;background-image:-webkit-gradient(linear,left top,left bottom,from(rgba(0,0,0,.12)),to(rgba(0,0,0,.12)));background-image:linear-gradient(180deg,rgba(0,0,0,.12),rgba(0,0,0,.12));-webkit-box-shadow:none;box-shadow:none}.clockpicker-span-hours{margin-right:.25rem}.clockpicker-span-minutes{margin-left:.25rem}.clockpicker-close-block{margin-top:.75rem}.clockpicker-plate{background-color:#ededee;border-radius:50%;width:200px;height:200px;overflow:visible;position:relative}.clockpicker-canvas,.clockpicker-dial{width:200px;height:200px;position:absolute;left:-1px;top:-1px}.clockpicker-minutes{visibility:hidden}.clockpicker-tick{border-radius:50%;line-height:26px;text-align:center;width:26px;height:26px;position:absolute;cursor:pointer}.clockpicker-tick.active,.clockpicker-tick:not(.disabled):hover{background-color:rgba(var(--primary-color,0,123,255),.25)}.clockpicker-tick.disabled{color:#eee;cursor:default}.clockpicker-dial{-webkit-transition:opacity 350ms,-webkit-transform 350ms;transition:opacity 350ms,-webkit-transform 350ms;transition:transform 350ms,opacity 350ms;transition:transform 350ms,opacity 350ms,-webkit-transform 350ms}.clockpicker-dial-out{opacity:0}.clockpicker-hours.clockpicker-dial-out{-webkit-transform:scale(1.2,1.2);transform:scale(1.2,1.2)}.clockpicker-minutes.clockpicker-dial-out{-webkit-transform:scale(.8,.8);transform:scale(.8,.8)}.clockpicker-canvas{-webkit-transition:opacity 175ms;transition:opacity 175ms}.clockpicker-canvas-out{opacity:.25}.clockpicker-canvas line{stroke:var(--primary,#007bff);stroke-width:2;stroke-linecap:round}.clockpicker-canvas-bearing{stroke:none;fill:var(--primary,#007bff)}.clockpicker-canvas-fg{stroke:none;fill:rgba(var(--primary-color,0,123,255),.5)}.clockpicker-canvas-bg{stroke:none;fill:rgba(var(--primary-color,0,123,255),.25)}.clockpicker-canvas-bg-trans{fill:rgba(var(--primary-color,0,123,255),.25)}.clockpicker-buttons-am-pm{color:#fff;display:none;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around;-webkit-box-align:center;-ms-flex-align:center;align-items:center;font-size:1rem;margin-left:.75rem}@-webkit-keyframes pickerFadeIn{from{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes pickerFadeIn{from{opacity:0;-webkit-transform:scale(.8);transform:scale(.8)}to{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}
\ No newline at end of file
diff --git a/dist/bootstrap4-clockpicker.min.js b/dist/bootstrap4-clockpicker.min.js
new file mode 100644
index 0000000..677fd6f
--- /dev/null
+++ b/dist/bootstrap4-clockpicker.min.js
@@ -0,0 +1,7 @@
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
+ return typeof obj;
+} : function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+};
+
+(function(a){function b(a){return document.createElementNS("http://www.w3.org/2000/svg",a)}function c(a){return (10>a?"0":"")+a}function d(a){var b=++r+"";return a?a+b:b}function e(e,l){function m(a,b){var c=r.offset(),d=/^touch/.test(a.type),f=c.left+100,g=c.top+100,i=(d?a.originalEvent.touches[0]:a).pageX-f,m=(d?a.originalEvent.touches[0]:a).pageY-g,e=Math.sqrt(i*i+m*m),n=!1;if(!(b&&(67>e||93AM").on("click",function(){B.amOrPm="AM",a(this).removeClass("text-white-50"),a(".btn-pm").addClass("text-white-50"),l.ampmSubmit&&setTimeout(function(){B.done();},duration/2);}).appendTo(this.buttonsAmPm),a("PM").on("click",function(){B.amOrPm="PM",a(this).removeClass("text-white-50"),a(".btn-am").addClass("text-white-50"),l.ampmSubmit&&setTimeout(function(){B.done();},duration/2);}).appendTo(this.buttonsAmPm)),l.autoclose||(A.append("").on("click",".cancel",function(){B.hide();}),A.css("display","flex").append("").on("click",".done",a.proxy(this.done,this))),/^(top|bottom)/.test(l.placement)&&("top"===l.align||"bottom"===l.align)&&(l.align="left"),("left"===l.placement||"right"===l.placement)&&("left"===l.align||"right"===l.align)&&(l.align="top"),q.addClass(l.placement),q.addClass("clockpicker-align-"+l.align),this.spanHours.click(a.proxy(this.toggleView,this,"hours")),this.spanMinutes.click(a.proxy(this.toggleView,this,"minutes")),l.addonOnly||w.on("focus.clockpicker click.clockpicker",a.proxy(this.show,this)),y.on("click.clockpicker",a.proxy(this.toggle,this));var C,D,E,F,G=a("");if(l.twelvehour)for(C=0;12>C;C+=l.hourstep)D=G.clone(),E=C/6*Math.PI,F=80,D.css("font-size","120%"),D.css({left:100+Math.sin(E)*F-13,top:100-Math.cos(E)*F-13}),D.html(0===C?12:C),t.append(D),D.on(n,m);else for(C=0;24>C;C+=l.hourstep){var H=!1;if(l.disabledhours&&-1!=a.inArray(C,l.disabledhours))var H=!0;D=G.clone(),E=C/6*Math.PI;var I=0C;F=I?54:80,D.css({left:100+Math.sin(E)*F-13,top:100-Math.cos(E)*F-13}),I&&D.css("font-size","120%"),H&&D.addClass("disabled"),D.html(0===C?"00":C),t.append(D),H||D.on(n,m);}var J=Math.max(l.minutestep,5);for(C=0;60>C;C+=J)D=G.clone(),E=C/30*Math.PI,D.css({left:100+80*Math.sin(E)-13,top:100-80*Math.cos(E)-13}),D.css("font-size","120%"),D.html(c(C)),u.append(D),D.on(n,m);if(r.on(n,function(b){0===a(b.target).closest(".clockpicker-tick").length&&m(b,!0);}),k){var K=q.find(".clockpicker-canvas"),L=b("svg");L.setAttribute("class","clockpicker-svg"),L.setAttribute("width",diameter),L.setAttribute("height",diameter);var M=b("g");M.setAttribute("transform","translate(100,100)");var g=b("circle");g.setAttribute("class","clockpicker-canvas-bearing"),g.setAttribute("cx",0),g.setAttribute("cy",0),g.setAttribute("r",3);var N=b("line");N.setAttribute("x1",0),N.setAttribute("y1",0);var O=b("circle");O.setAttribute("class","clockpicker-canvas-bg"),O.setAttribute("r",13);var P=b("circle");P.setAttribute("class","clockpicker-canvas-fg"),P.setAttribute("r",3.5),M.appendChild(N),M.appendChild(O),M.appendChild(P),M.appendChild(g),L.appendChild(M),K.append(L),this.hand=N,this.bg=O,this.fg=P,this.bearing=g,this.g=M,this.canvas=K;}this.raiseCallback(this.options.init,"init");}function f(a,b){if(a&&"function"==typeof a&&this.element){var c=this.getTime()||null;a.call(this.element,c);}b&&this.element.trigger("clockpicker."+b||"NoName");}function g(a,b,c){var d=b.outerHeight(),e=a.outerHeight(),f=a.offset().top,g=a.offset().top+e,h=f-a[0].getBoundingClientRect().top,i=h+document.documentElement.clientHeight,j=f-d>=h,k=g+d<=i;if("top"===c){if(j)return "top";if(k)return "bottom"}else {if(k)return "bottom";if(j)return "top"}return "viewport-top"}var h,i=a(window),j=a(document),k="SVGAngle"in window&&function(){var a,b=document.createElement("div");return b.innerHTML="",a="http://www.w3.org/2000/svg"==(b.firstChild&&b.firstChild.namespaceURI),b.innerHTML="",a}(),l=function(){var a=document.createElement("div").style;return "transition"in a||"WebkitTransition"in a||"MozTransition"in a||"msTransition"in a||"OTransition"in a}(),m="ontouchstart"in window,n="mousedown"+(m?" touchstart":""),o="mousemove.clockpicker"+(m?" touchmove.clockpicker":""),p="mouseup.clockpicker"+(m?" touchend.clockpicker":""),q=navigator.vibrate?"vibrate":navigator.webkitVibrate?"webkitVibrate":null,r=0;diameter=200,duration=l?350:1;var s="";e.prototype.parseStep=function(a,b){return 0==b%a?a:1},e.DEFAULTS={default:"",fromnow:0,placement:"bottom",align:"left",donetext:"OK",canceltext:"Cancel",autoclose:!1,twelvehour:!1,vibrate:!0,hourstep:1,minutestep:1,ampmSubmit:!1,addonOnly:!1,disabledhours:null},e.prototype.toggle=function(){this[this.isShown?"hide":"show"]();},e.prototype.updatePlacementClass=function(a){this.currentPlacementClass&&this.popover.removeClass(this.currentPlacementClass),a&&this.popover.addClass(a),this.currentPlacementClass=a;},e.prototype.locate=function(){var a=this.element,b=this.popover,c=a.offset(),d=a.outerWidth(),e=a.outerHeight(),f=this.options.placement,h=this.options.align,j=i.height(),k=i.width(),l=b.height(),m=b.width(),n={};if("top-adaptive"===f||"bottom-adaptive"===f){var p=f.substr(0,f.indexOf("-"));f=g(a,b,p),this.updatePlacementClass("viewport-top"===f?"":f);}b.show();"bottom"===f?n.top=c.top+e:"right"===f?n.left=c.left+d:"top"===f?n.top=c.top-b.outerHeight():"left"===f?n.left=c.left-b.outerWidth():"viewport-top"===f?n.top=c.top-a[0].getBoundingClientRect().top:void 0;"left"===h?n.left=c.left:"right"===h?n.left=c.left+d-b.outerWidth():"top"===h?n.top=c.top:"bottom"===h?n.top=c.top+e-b.outerHeight():void 0;l+n.top>j&&(n.top=j-l),m+n.left>k&&(n.left=k-m),b.css(n);},e.prototype.parseInputValue=function(){var a=this.input.prop("value")||this.options["default"]||"";if("now"===a&&(a=new Date(+new Date+this.options.fromnow)),a instanceof Date&&(a=a.getHours()+":"+a.getMinutes()),a=a.split(":"),this.hours=+a[0]||0,this.minutes=+(a[1]+"").replace(/\D/g,"")||0,this.hours=Math.round(this.hours/this.options.hourstep)*this.options.hourstep,this.minutes=Math.round(this.minutes/this.options.minutestep)*this.options.minutestep,this.options.twelvehour){var b=(a[1]+"").replace(/\d+/g,"").toLowerCase();this.amOrPm=12>this.hours||"am"===b?"AM":"PM";}},e.prototype.show=function(){if(!this.isShown){this.raiseCallback(this.options.beforeShow,"beforeShow");var b=this;this.isAppended||(h=a(document.body).append(this.popover),i.on("resize.clockpicker"+this.id,function(){b.isShown&&b.locate();}),this.isAppended=!0),this.parseInputValue(),this.spanHours.html(c(this.hours)),this.spanMinutes.html(c(this.minutes)),this.toggleView("hours"),this.locate(),this.isShown=!0,j.on("click.clockpicker."+this.id+" focusin.clockpicker."+this.id,function(c){var d=a(c.target);0===d.closest(b.popover).length&&0===d.closest(b.addon).length&&0===d.closest(b.input).length&&b.hide();}),j.on("keyup.clockpicker."+this.id,function(a){27===a.keyCode&&b.hide();}),this.raiseCallback(this.options.afterShow,"afterShow");}},e.prototype.hide=function(){this.raiseCallback(this.options.beforeHide,"beforeHide"),this.isShown=!1,j.off("click.clockpicker."+this.id+" focusin.clockpicker."+this.id),j.off("keyup.clockpicker."+this.id),this.popover.hide(),this.raiseCallback(this.options.afterHide,"afterHide");},e.prototype.toggleView=function(b,c){var d=!1;"minutes"===b&&"visible"===a(this.hoursView).css("visibility")&&(this.raiseCallback(this.options.beforeHourSelect,"beforeHourSelect"),d=!0);var e="hours"===b,f=e?this.hoursView:this.minutesView,g=e?this.minutesView:this.hoursView;this.currentView=b,this.spanHours.toggleClass("text-white-50",!e),this.spanMinutes.toggleClass("text-white-50",e),g.addClass("clockpicker-dial-out"),f.css("visibility","visible").removeClass("clockpicker-dial-out"),this.resetClock(c),clearTimeout(this.toggleViewTimer),this.toggleViewTimer=setTimeout(function(){g.css("visibility","hidden");},duration),d&&this.raiseCallback(this.options.afterHourSelect,"afterHourSelect");},e.prototype.resetClock=function(a){var b=this.currentView,c=this[b],d="hours"===b,e=Math.PI/(d?6:30),f=c*e,g=d&&0c?54:80,h=Math.sin(f)*g,i=-Math.cos(f)*g,j=this;k&&a?(j.canvas.addClass("clockpicker-canvas-out"),setTimeout(function(){j.canvas.removeClass("clockpicker-canvas-out"),j.setHand(h,i);},a)):this.setHand(h,i);},e.prototype.setHand=function(b,d,e){var f,g,h=Math.atan2(b,-d),i="hours"===this.currentView,j=Math.sqrt(b*b+d*d),l=this.options,m=i&&67>j,n=m?54:80;if(f=i?l.hourstep/6*Math.PI:l.minutestep/30*Math.PI,l.twelvehour&&(n=80),0>h&&(h=2*Math.PI+h),g=Math.round(h/f),h=g*f,!i)g*=l.minutestep,60===g&&(g=0);else if(g*=l.hourstep,l.twelvehour||!m!=0b&&"PM"===this.amOrPm&&(b+=12);var c=new Date;return c.setMinutes(this.minutes),c.setHours(b),c.setSeconds(0),a&&a.apply(this.element,c)||c},e.prototype.done=function(){this.raiseCallback(this.options.beforeDone,"beforeDone"),this.hide();var a=this.input.prop("value"),b=this.hours,d=":"+c(this.minutes);this.isHTML5&&this.options.twelvehour&&(12>this.hours&&"PM"===this.amOrPm&&(b+=12),12===this.hours&&"AM"===this.amOrPm&&(b=0)),d=c(b)+d,!this.isHTML5&&this.options.twelvehour&&(d+=this.amOrPm),this.input.prop("value",d),d!==a&&(this.input.trigger("change"),!this.isInput&&this.element.trigger("change")),this.options.autoclose&&this.input.trigger("blur"),this.raiseCallback(this.options.afterDone,"afterDone");},e.prototype.remove=function(){this.element.removeData("clockpicker"),this.input.off("focus.clockpicker click.clockpicker"),this.addon.off("click.clockpicker"),this.isShown&&this.hide(),this.isAppended&&(i.off("resize.clockpicker"+this.id),this.popover.remove());},a.fn.clockpicker=function(b){function c(){var c=a(this),f=c.data("clockpicker");if(!f){var g=a.extend({},e.DEFAULTS,c.data(),"object"==("undefined"==typeof b?"undefined":_typeof(b))&&b);c.data("clockpicker",new e(c,g));}else if("function"==typeof f[b])return f[b].apply(f,d)}var d=Array.prototype.slice.call(arguments,1);if(1==this.length){var f=c.apply(this[0]);return f===void 0?this:f}return this.each(c)};})(jQuery);
diff --git a/src/bootstrap4-clockpicker.scss b/src/bootstrap4-clockpicker.scss
new file mode 100644
index 0000000..e740c21
--- /dev/null
+++ b/src/bootstrap4-clockpicker.scss
@@ -0,0 +1,235 @@
+/*!
+ * ClockPicker v0.2.2 for Bootstrap (https://weareoutman.github.io/clockpicker/)
+ * Copyright 2014 Wang Shenwei
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
+ * Bootstrap 4 compatibility by djibe (https://github.com/djibe/clockpicker)
+ */
+
+$clockpicker-border-radius: 4px;
+
+@import "../bootstrap-4.2.1/functions";
+@import "../bootstrap-4.2.1/variables";
+
+.clockpicker {
+ .input-group-addon {
+ cursor: pointer;
+ }
+
+ &-moving {
+ cursor: move;
+ }
+
+ &-align-left.popover>.arrow {
+ left: 25px;
+ }
+
+ &-align-top.popover>.arrow {
+ top: 17px;
+ }
+
+ &-align-right.popover>.arrow {
+ left: auto;
+ right: 25px;
+ }
+
+ &-align-bottom.popover>.arrow {
+ top: auto;
+ bottom: 6px;
+ }
+
+ &-popover {
+ animation: pickerFadeIn 0.2s cubic-bezier(0.4, 0, 0.2, 1);
+ border-radius: 4px;
+ border: 0;
+ transform: scale(1);
+ transform-origin: center top 0px;
+ box-shadow: 0 24px 38px 3px rgba(0, 0, 0, 0.14),
+ 0 9px 46px 8px rgba(0, 0, 0, 0.12), 0 11px 15px 0 rgba(0, 0, 0, 0.2);
+
+ &.top {
+ transform-origin: center bottom 0px;
+ }
+
+ * {
+ user-select: none;
+ }
+
+ .popover-header {
+ align-items: center;
+ justify-content: center;
+ background-color: $primary;
+ color: #fff;
+ display: flex;
+ font-size: 3rem;
+ font-weight: normal;
+ letter-spacing: normal;
+ text-align: center;
+ padding: 0.5rem;
+ border-top-left-radius: $clockpicker-border-radius;
+ border-top-right-radius: $clockpicker-border-radius;
+
+ span {
+ cursor: pointer;
+ }
+ }
+
+ .popover-body {
+ background-color: #fff;
+ padding: 1rem 0.75rem 0.75rem;
+ border-bottom-right-radius: $clockpicker-border-radius;
+ border-bottom-left-radius: $clockpicker-border-radius;
+ }
+
+ .btn {
+ border: 0 !important;
+ border-radius: 4px;
+ box-shadow: none;
+ font-size: 0.8125rem;
+ font-weight: 500;
+ padding: 0.59375rem 1rem;
+ min-width: 0;
+ margin: 0;
+ margin-left: 0.25rem;
+ text-transform: uppercase;
+
+ &:focus,
+ &:hover,
+ &:active {
+ outline: none !important;
+ background-image: linear-gradient(180deg,
+ rgba(0, 0, 0, 0.12),
+ rgba(0, 0, 0, 0.12));
+ box-shadow: none;
+ }
+ }
+ }
+
+ &-span-hours {
+ margin-right: 0.25rem;
+ }
+
+ &-span-minutes {
+ margin-left: 0.25rem;
+ }
+
+ &-close-block {
+ margin-top: 0.75rem;
+ }
+
+ &-plate {
+ background-color: #ededee;
+ border-radius: 50%;
+ width: 200px;
+ height: 200px;
+ overflow: visible;
+ position: relative;
+ }
+
+ &-canvas,
+ &-dial {
+ width: 200px;
+ height: 200px;
+ position: absolute;
+ left: -1px;
+ top: -1px;
+ }
+
+ &-minutes {
+ visibility: hidden;
+ }
+
+ &-tick {
+ border-radius: 50%;
+ line-height: 26px;
+ text-align: center;
+ width: 26px;
+ height: 26px;
+ position: absolute;
+ cursor: pointer;
+
+ &.active,
+ &:not(.disabled):hover {
+ background-color: rgba($primary, 0.25);
+ }
+
+ &.disabled {
+ color: #eee;
+ cursor: default;
+ }
+ }
+
+ &-dial {
+ transition: transform 350ms, opacity 350ms;
+
+ &-out {
+ opacity: 0;
+ }
+ }
+
+ &-hours.clockpicker-dial-out {
+ transform: scale(1.2, 1.2);
+ }
+
+ &-minutes.clockpicker-dial-out {
+ transform: scale(0.8, 0.8);
+ }
+
+ &-canvas {
+ transition: opacity 175ms;
+
+ &-out {
+ opacity: 0.25;
+ }
+
+ line {
+ stroke: $primary;
+ stroke-width: 2;
+ stroke-linecap: round;
+ }
+
+ &-bearing {
+ stroke: none;
+ fill: $primary;
+ }
+
+ &-fg {
+ stroke: none;
+ fill: rgba($primary, 0.5);
+ }
+
+ // Circle selection
+ &-bg {
+ stroke: none;
+ fill: rgba($primary, 0.25);
+
+ // Circle drag selection
+ &-trans {
+ fill: rgba($primary, 0.25);
+ }
+ }
+ }
+
+ // New buttons by djibe
+ &-buttons-am-pm {
+ color: white;
+ display: none;
+ flex-wrap: nowrap;
+ flex-direction: column;
+ justify-content: space-around;
+ align-items: center;
+ font-size: 1rem;
+ margin-left: 0.75rem;
+ }
+}
+
+@keyframes pickerFadeIn {
+ from {
+ opacity: 0;
+ transform: scale(0.8);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
\ No newline at end of file