diff --git a/README.md b/README.md index 5ff911a..737e19a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,17 @@ # ClockPicker [![Bower version](https://badge.fury.io/bo/clockpicker.svg)](http://badge.fury.io/bo/clockpicker) [![Build Status](https://travis-ci.org/weareoutman/clockpicker.svg)](https://travis-ci.org/weareoutman/clockpicker) [![devDependency Status](https://david-dm.org/weareoutman/clockpicker/dev-status.svg)](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 + +![Bootstrap 4 clockpicker](assets/images/clockpicker-bs4.png?raw=true "Clockpicker for Bootstrap 4") +![Bootstrap 4 clockpicker](assets/images/clockpicker-bs4-material.png?raw=true "Clockpicker for Bootstrap 4 with material design") + +And the original screens from Weareoutman for Bootstrap 3 ![Screenshot](http://weareoutman.github.io/clockpicker/assets/images/screenshot-1.png) ![clockpicker-12-hour-screenshot](https://cloud.githubusercontent.com/assets/5218249/3613434/03da9888-0db8-11e4-8bdb-dbabb5e91e5c.png) @@ -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