├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── demo.html ├── dist ├── main.js ├── style.css └── style.js ├── package.json ├── src ├── datetime.js ├── main.css ├── main.js ├── md-datetime.html ├── md-timepicker.html └── timepicker.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "browser": true, 8 | "commonjs": true, 9 | "es6": true 10 | }, 11 | "globals": { 12 | "angular": true, 13 | "moment": true 14 | }, 15 | "extends": "eslint:recommended", 16 | "rules": { 17 | "no-console": 0, 18 | "no-debugger": 0, 19 | 20 | "prefer-template": 1, 21 | "constructor-super": 2, 22 | "no-shadow": 2, 23 | "no-use-before-define": 2, 24 | "no-else-return": 2, 25 | "no-unused-expressions": [2, { "allowShortCircuit": true }], 26 | "no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_", "argsIgnorePattern": "^_"}], 27 | "no-const-assign": 2, 28 | "no-dupe-class-members": 2, 29 | "no-this-before-super": 2, 30 | 31 | "arrow-spacing": 2, 32 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 33 | "curly": 2, 34 | "key-spacing": 2, 35 | "eol-last": 2, 36 | "object-curly-spacing": [2, "always"], 37 | "quote-props": [2, "as-needed"], 38 | "padded-blocks": [2, "never"], 39 | "semi": [2, "always"], 40 | "space-in-parens": [2, "never"], 41 | "space-infix-ops": 2, 42 | "object-shorthand": [2, "always"], 43 | "no-var": 2, 44 | "no-extra-parens": 2, 45 | "no-multi-spaces": 2, 46 | "no-mixed-spaces-and-tabs": 2, 47 | "no-multiple-empty-lines": [2, { "max": 2 }], 48 | "no-negated-condition": 2, 49 | "no-trailing-spaces": 2, 50 | "no-underscore-dangle": 2 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Po Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # md-datetime 2 | 3 | [![npm version](https://badge.fury.io/js/md-datetime.svg)](https://badge.fury.io/js/md-datetime) 4 | 5 | Angular material datetime picker 6 | 7 | see [live demo](https://rawgit.com/princemaple/md-datetime/master/demo.html) 8 | 9 | ```html 10 | 11 | ``` 12 | 13 | Simply use it as other input widgets 14 | -------------------------------------------------------------------------------- /demo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | md-datetime demo 6 | 7 | 8 | 12 | 13 | 14 | 15 |

Angular Material Datetime Picker

16 |
17 |
18 | 19 |
dt1 value: {{dt1 | date: 'yyyy-MM-ddTHH:mmZ'}}
20 |
21 | 22 |
dt2 value: {{dt2 | date: 'yyyy-MM-ddTHH:mmZ'}}
23 |
24 | 25 |
no reset
26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 43 | 44 | Fork me on GitHub 45 | 46 | 47 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(n){if(i[n])return i[n].exports;var r=i[n]={exports:{},id:n,loaded:!1};return e[n].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t,i){"use strict";angular.module("mdDatetime",["ngMaterial"]),i(4),i(3)},function(e,t){e.exports=' Reset to NOW '},function(e,t){e.exports='
{{T.time.hour()}} : {{T.time.minute()}}
am/pm
{{::hour.viewValue}}
{{::hour.viewValue}}
{{::minute.viewValue}}
'},function(e,t,i){"use strict";angular.module("mdDatetime").component("mdDatetime",{require:{modelCtrl:"ngModel"},template:i(1),controller:["$attrs",function(e){var t=this;this.$onInit=function(){t.modelCtrl.$render=function(){t.datetime=moment(t.modelCtrl.$modelValue),t.updateParams(!0)}},this.updateDate=function(){var e=moment(t.params.date);t.datetime.year(e.year()),t.datetime.month(e.month()),t.datetime.date(e.date()),t.updateParams()},this.updateTime=function(){t.datetime.hour(t.params.time.hour),t.datetime.minute(t.params.time.minute),t.updateParams()},this.updateParams=function(e){t.params={date:t.datetime.toDate(),time:{hour:t.datetime.hour(),minute:t.datetime.minute()}},e||t.modelCtrl.$setViewValue(t.datetime.toISOString())},this.canReset=!("noReset"in e),this.reset=function(){t.datetime=moment(),t.updateParams()}}],controllerAs:"DT"})},function(e,t,i){"use strict";var n=function(){function e(e,t){var i=[],n=!0,r=!1,o=void 0;try{for(var m,a=e[Symbol.iterator]();!(n=(m=a.next()).done)&&(i.push(m.value),!t||i.length!==t);n=!0);}catch(u){r=!0,o=u}finally{try{!n&&a["return"]&&a["return"]()}finally{if(r)throw o}}return i}return function(t,i){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,i);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}();angular.module("mdDatetime").component("mdTimepicker",{template:i(2),bindings:{mode:"@"},require:{modelCtrl:"ngModel"},controller:["$scope","$window",function(e,t){var i=this;this.$onInit=function(){i.modelCtrl.$render=function(){i.viewValue=moment(i.modelCtrl.$modelValue).format("HH:mm");var e=i.modelCtrl.$modelValue,t=e.hour,n=e.minute;i.hours.forEach(function(e){e.selected=e.realValue==t}),i.minutes.forEach(function(e,t){e.selected=(Math.floor(n/5)+11)%12==t})},angular.element(t).on("click",function(){i.picking=!1,e.$digest()})},this.keepPicking=function(e){e.stopPropagation()},this.outerHours=["1","2","3","4","5","6","7","8","9","10","11","12"],this.innerHours=["13","14","15","16","17","18","19","20","21","22","23","00"],this.minutes=["05","10","15","20","25","30","35","40","45","50","55","00"];var r=function(e,t){return function(i,n){var r=30*n+30;return{type:e,viewValue:i,realValue:parseInt(i,10),style:{transform:"rotate("+r+"deg) translate(0, -"+t+"px) rotate(-"+r+"deg)"}}}};this.outerHours=this.outerHours.map(r("hour",80)),this.innerHours=this.innerHours.map(r("hour",55)),this.ampm="ampm"==this.mode,this.ampm&&(this.innerHours=[]),this.hours=this.outerHours.concat(this.innerHours),this.minutes=this.minutes.map(r("minute",80)),this.togglePicking=function(e){return i.picking?i.picking=!1:(i.picking=!0,i.pickHour(),void i.keepPicking(e))},this.pickHour=function(){i.pickingMinute=!1,i.pickingHour=!0},this.pickMinute=function(){i.pickingHour=!1,i.pickingMinute=!0},this.selectHour=function(e){i.modelCtrl.$setViewValue({hour:e.realValue,minute:i.modelCtrl.$modelValue.minute}),i.outerHours.forEach(function(e){e.selected=!1}),i.innerHours.forEach(function(e){e.selected=!1}),e.selected=!0,i.pickMinute()},this.selectMinute=function(e){i.modelCtrl.$setViewValue({hour:i.modelCtrl.$modelValue.hour,minute:e.realValue}),i.minutes.forEach(function(e){e.selected=!1}),e.selected=!0,i.picking=!1},this.timePattern=/^[0-2]?[0-9]:[0-5][0-9]$/,this.parse=function(){if(i.viewValue){var e=i.viewValue.split(":"),t=n(e,2),r=t[0],o=t[1];i.modelCtrl.$setViewValue({hour:r,minute:o})}},this.time={hour:function(){return moment(i.modelCtrl.$modelValue).format("HH")},minute:function(){return moment(i.modelCtrl.$modelValue).format("mm")}}}],controllerAs:"T"})}]); -------------------------------------------------------------------------------- /dist/style.css: -------------------------------------------------------------------------------- 1 | .md-datetime-picker-reset{vertical-align:middle}.md-timepicker-widget{position:relative;display:inline-block;outline:none}.md-timepicker-widget .md-errors-spacer{display:none}.md-timepicker-widget .md-datepicker-triangle-button{top:6px}.md-timepicker-popup{position:absolute;top:0;left:0;background:#eee;width:240px;height:288px;overflow:hidden;z-index:99}.md-timepicker-popup.ng-enter{-webkit-transition:-webkit-transform .1s ease-out;transition:-webkit-transform .1s ease-out;transition:transform .1s ease-out;transition:transform .1s ease-out,-webkit-transform .1s ease-out;-webkit-transform:translate(-50%,-50%) scale(.8) translate(50%,50%);transform:translate(-50%,-50%) scale(.8) translate(50%,50%)}.md-timepicker-popup.ng-enter-active{-webkit-transform:scale(1);transform:scale(1)}.md-timepicker-time{text-align:center;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;-ms-grid-row-align:center;align-items:center}.md-timepicker-time-hour,.md-timepicker-time-minute{font-size:32px;cursor:pointer;outline:none}.md-timepicker-time-hour.selected,.md-timepicker-time-minute.selected{font-weight:600;color:#fff}.md-timepicker-clock{position:absolute;left:0;top:64px;background-color:#ddd;width:200px;height:200px;border-radius:50%;margin:12px 20px}.md-timepicker-clock.ng-leave{-webkit-transition:all .3s ease-out;transition:all .3s ease-out;z-index:99;-webkit-transform:scale(1);transform:scale(1);opacity:1}.md-timepicker-clock.ng-leave-active{-webkit-transform:scale(1.2);transform:scale(1.2);opacity:0}.md-timepicker-clock-hour,.md-timepicker-clock-minute,.md-timepicker-hour-number,.md-timepicker-minute-number{position:absolute;top:50%;left:50%}.md-timepicker-clock-hour,.md-timepicker-clock-minute{cursor:pointer}.md-timepicker-hour-number,.md-timepicker-minute-number{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.md-timepicker-hour-number:hover:after,.md-timepicker-minute-number:hover:after,.selected>.md-timepicker-hour-number:after,.selected>.md-timepicker-minute-number:after{content:"";position:absolute;top:50%;left:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);border:14px solid #ccc;border-radius:50%;z-index:-1}.selected>.md-timepicker-hour-number:after,.selected>.md-timepicker-minute-number:after{border:14px solid #aaa} -------------------------------------------------------------------------------- /dist/style.js: -------------------------------------------------------------------------------- 1 | !function(r){function t(o){if(e[o])return e[o].exports;var n=e[o]={exports:{},id:o,loaded:!1};return r[o].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var e={};return t.m=r,t.c=e,t.p="",t(0)}([function(r,t){}]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "md-datetime", 3 | "version": "0.2.1", 4 | "description": "Angular material datetime picker", 5 | "main": "dist/main.js", 6 | "scripts": { 7 | "dev": "webpack --progress -w", 8 | "dist": "webpack -p", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/princemaple/md-datetime.git" 14 | }, 15 | "keywords": [ 16 | "angular", 17 | "material", 18 | "datetime", 19 | "picker" 20 | ], 21 | "author": "Po Chen", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/princemaple/md-datetime/issues" 25 | }, 26 | "homepage": "https://github.com/princemaple/md-datetime#readme", 27 | "dependencies": { 28 | "angular": "^1.6.3", 29 | "angular-material": "^1.1.3", 30 | "moment": "^2.18.1" 31 | }, 32 | "devDependencies": { 33 | "autoprefixer": "^7.1.1", 34 | "babel-core": "^6.24.1", 35 | "babel-loader": "^7.0.0", 36 | "babel-preset-es2015": "^6.24.1", 37 | "css-loader": "^0.28.2", 38 | "eslint": "^3.18.0", 39 | "eslint-loader": "^1.7.0", 40 | "extract-text-webpack-plugin": "^2.0.0", 41 | "html-loader": "^0.4.5", 42 | "ng-annotate-loader": "^0.6.0", 43 | "postcss-import": "^10.0.0", 44 | "postcss-loader": "^2.0.3", 45 | "postcss-nested": "^2.0.2", 46 | "postcss-simple-vars": "^4.0.0", 47 | "style-loader": "^0.18.0", 48 | "webpack": "^2.5.1" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/datetime.js: -------------------------------------------------------------------------------- 1 | angular.module('mdDatetime') 2 | .component('mdDatetime', { 3 | require: { 4 | modelCtrl: 'ngModel' 5 | }, 6 | template: require('html!./md-datetime.html'), 7 | controller($attrs) { 8 | this.$onInit = () => { 9 | this.modelCtrl.$render = () => { 10 | this.datetime = moment(this.modelCtrl.$modelValue); 11 | this.updateParams(true); 12 | }; 13 | }; 14 | 15 | this.updateDate = () => { 16 | let newDate = moment(this.params.date); 17 | 18 | this.datetime.year(newDate.year()); 19 | this.datetime.month(newDate.month()); 20 | this.datetime.date(newDate.date()); 21 | 22 | this.updateParams(); 23 | }; 24 | 25 | this.updateTime = () => { 26 | this.datetime.hour(this.params.time.hour); 27 | this.datetime.minute(this.params.time.minute); 28 | 29 | this.updateParams(); 30 | }; 31 | 32 | this.updateParams = (init) => { 33 | this.params = { 34 | date: this.datetime.toDate(), 35 | time: { 36 | hour: this.datetime.hour(), 37 | minute: this.datetime.minute() 38 | } 39 | }; 40 | 41 | if (init) { return; } 42 | this.modelCtrl.$setViewValue(this.datetime.toISOString()); 43 | }; 44 | 45 | this.canReset = !('noReset' in $attrs); 46 | 47 | this.reset = () => { 48 | this.datetime = moment(); 49 | this.updateParams(); 50 | }; 51 | }, 52 | controllerAs: 'DT' 53 | }); 54 | -------------------------------------------------------------------------------- /src/main.css: -------------------------------------------------------------------------------- 1 | .md-datetime-picker-reset { 2 | vertical-align: middle; 3 | } 4 | 5 | .md-timepicker-widget { 6 | position: relative; 7 | display: inline-block; 8 | outline: none; 9 | 10 | .md-errors-spacer { 11 | display: none; 12 | } 13 | 14 | .md-datepicker-triangle-button { 15 | top: 6px; 16 | } 17 | } 18 | 19 | .md-timepicker-popup { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | background: #eee; 24 | width: 240px; 25 | height: 288px; 26 | overflow: hidden; 27 | z-index: 99; 28 | 29 | &.ng-enter { 30 | transition: transform 0.1s ease-out; 31 | } 32 | 33 | &.ng-enter { 34 | transform: translate(-50%, -50%) scale(0.8) translate(50%, 50%); 35 | } 36 | 37 | &.ng-enter-active { 38 | transform: scale(1); 39 | } 40 | } 41 | 42 | .md-timepicker-time { 43 | text-align: center; 44 | flex-direction: row; 45 | justify-content: center; 46 | align-items: center; 47 | } 48 | 49 | .md-timepicker-time-hour, 50 | .md-timepicker-time-minute { 51 | font-size: 32px; 52 | cursor: pointer; 53 | outline: none; 54 | 55 | &.selected { 56 | font-weight: 600; 57 | color: #fff; 58 | } 59 | } 60 | 61 | .md-timepicker-clock { 62 | position: absolute; 63 | left: 0; 64 | top: 64px; 65 | background-color: #ddd; 66 | width: 200px; 67 | height: 200px; 68 | border-radius: 50%; 69 | margin: 12px 20px; 70 | 71 | &.ng-leave { 72 | transition: all .3s ease-out; 73 | z-index: 99; 74 | } 75 | 76 | &.ng-leave { 77 | transform: scale(1); 78 | opacity: 1; 79 | } 80 | 81 | &.ng-leave-active { 82 | transform: scale(1.2); 83 | opacity: 0; 84 | } 85 | } 86 | 87 | .md-timepicker-clock-hour, 88 | .md-timepicker-clock-minute, 89 | .md-timepicker-hour-number, 90 | .md-timepicker-minute-number { 91 | position: absolute; 92 | top: 50%; 93 | left: 50%; 94 | } 95 | 96 | .md-timepicker-clock-hour, 97 | .md-timepicker-clock-minute { 98 | cursor: pointer; 99 | } 100 | 101 | .md-timepicker-hour-number, 102 | .md-timepicker-minute-number { 103 | transform: translate(-50%, -50%); 104 | 105 | .selected > &::after, 106 | &:hover::after { 107 | content: ""; 108 | position: absolute; 109 | top: 50%; 110 | left: 50%; 111 | transform: translate(-50%, -50%); 112 | border: 14px solid #ccc; 113 | border-radius: 50%; 114 | z-index: -1; 115 | } 116 | 117 | .selected > &::after { 118 | border: 14px solid #aaa; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | angular.module('mdDatetime', ['ngMaterial']); 2 | 3 | require('./timepicker'); 4 | require('./datetime'); 5 | -------------------------------------------------------------------------------- /src/md-datetime.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Reset to NOW 11 | 12 | -------------------------------------------------------------------------------- /src/md-timepicker.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 15 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | {{T.time.hour()}} 23 | 24 | : 25 | 26 | {{T.time.minute()}} 27 | 28 | 29 |
30 | am/pm 31 |
32 |
33 |
35 | {{::hour.viewValue}} 36 |
37 |
39 | {{::hour.viewValue}} 40 |
41 |
42 |
43 |
45 | {{::minute.viewValue}} 46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/timepicker.js: -------------------------------------------------------------------------------- 1 | angular.module('mdDatetime') 2 | .component('mdTimepicker', { 3 | template: require('html!./md-timepicker.html'), 4 | bindings: { mode: '@' }, 5 | require: { 6 | modelCtrl: 'ngModel' 7 | }, 8 | controller($scope, $window) { 9 | this.$onInit = () => { 10 | this.modelCtrl.$render = () => { 11 | this.viewValue = moment(this.modelCtrl.$modelValue).format('HH:mm'); 12 | 13 | let { hour, minute } = this.modelCtrl.$modelValue; 14 | 15 | this.hours.forEach(h => { h.selected = h.realValue == hour; }); 16 | this.minutes.forEach((m, index) => { m.selected = (Math.floor(minute / 5) + 11) % 12 == index; }); 17 | }; 18 | 19 | angular.element($window).on('click', () => { 20 | this.picking = false; 21 | $scope.$digest(); 22 | }); 23 | }; 24 | 25 | this.keepPicking = (event) => { event.stopPropagation(); }; 26 | 27 | this.outerHours = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']; 28 | this.innerHours = ['13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '00']; 29 | this.minutes = ['05', '10', '15', '20', '25', '30', '35', '40', '45', '50', '55', '00']; 30 | 31 | let processClockNumber = (type, size) => { 32 | return (viewValue, index) => { 33 | let deg = index * 30 + 30; 34 | 35 | return { 36 | type, 37 | viewValue, 38 | realValue: parseInt(viewValue, 10), 39 | style: { 40 | transform: `rotate(${deg}deg) translate(0, -${size}px) rotate(-${deg}deg)` 41 | } 42 | }; 43 | }; 44 | }; 45 | 46 | this.outerHours = this.outerHours.map(processClockNumber('hour', 80)); 47 | this.innerHours = this.innerHours.map(processClockNumber('hour', 55)); 48 | 49 | this.ampm = this.mode == 'ampm'; 50 | if (this.ampm) { this.innerHours = []; } 51 | 52 | this.hours = this.outerHours.concat(this.innerHours); 53 | this.minutes = this.minutes.map(processClockNumber('minute', 80)); 54 | 55 | this.togglePicking = (event) => { 56 | if (this.picking) { return this.picking = false; } 57 | 58 | this.picking = true; 59 | this.pickHour(); 60 | 61 | this.keepPicking(event); 62 | }; 63 | 64 | this.pickHour = () => { 65 | this.pickingMinute = false; 66 | this.pickingHour = true; 67 | }; 68 | 69 | this.pickMinute = () => { 70 | this.pickingHour = false; 71 | this.pickingMinute = true; 72 | }; 73 | 74 | this.selectHour = (hour) => { 75 | this.modelCtrl.$setViewValue({ 76 | hour: hour.realValue, 77 | minute: this.modelCtrl.$modelValue.minute 78 | }); 79 | 80 | this.outerHours.forEach(h => { h.selected = false; }); 81 | this.innerHours.forEach(h => { h.selected = false; }); 82 | hour.selected = true; 83 | 84 | this.pickMinute(); 85 | }; 86 | 87 | this.selectMinute = (minute) => { 88 | this.modelCtrl.$setViewValue({ 89 | hour: this.modelCtrl.$modelValue.hour, 90 | minute: minute.realValue 91 | }); 92 | 93 | this.minutes.forEach(m => { m.selected = false; }); 94 | minute.selected = true; 95 | 96 | this.picking = false; 97 | }; 98 | 99 | this.timePattern = /^[0-2]?[0-9]:[0-5][0-9]$/; 100 | 101 | this.parse = () => { 102 | if (!this.viewValue) { return; } 103 | 104 | let [hour, minute] = this.viewValue.split(':'); 105 | 106 | this.modelCtrl.$setViewValue({ hour, minute }); 107 | }; 108 | 109 | this.time = { 110 | hour: () => moment(this.modelCtrl.$modelValue).format('HH'), 111 | minute: () => moment(this.modelCtrl.$modelValue).format('mm') 112 | }; 113 | }, 114 | controllerAs: 'T' 115 | }); 116 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let path = require('path'); 4 | 5 | let autoprefixer = require('autoprefixer'); 6 | let cssImport = require('postcss-import'); 7 | let cssNested = require('postcss-nested'); 8 | let cssVars = require('postcss-simple-vars'); 9 | 10 | let ExtractTextPlugin = require('extract-text-webpack-plugin'); 11 | let ExtractCSS = new ExtractTextPlugin(1, 'style.css'); 12 | 13 | module.exports = { 14 | context: path.resolve(__dirname, 'src'), 15 | entry: { 16 | main: './main.js', 17 | style: './main.css' 18 | }, 19 | output: { 20 | path: path.resolve(__dirname, 'dist'), 21 | filename: '[name].js' 22 | }, 23 | module: { 24 | preLoaders: [ 25 | { test: /\.js$/, loader: 'eslint-loader' } 26 | ], 27 | loaders: [ 28 | { 29 | test: /\.js$/, 30 | loaders: ['ng-annotate', 'babel?presets[]=es2015'] 31 | }, 32 | { 33 | test: /\.css$/, 34 | loader: ExtractCSS.extract('style', 'css!postcss') 35 | } 36 | ] 37 | }, 38 | eslint: { 39 | fix: true, 40 | configFile: './.eslintrc', 41 | emitWarning: true, 42 | emitError: true, 43 | failOnWarning: false, 44 | failOnError: true 45 | }, 46 | postcss: (webpack) => [ 47 | cssImport({ addDependencyTo: webpack }), 48 | cssNested(), 49 | cssVars(), 50 | autoprefixer 51 | ], 52 | plugins: [ExtractCSS], 53 | watchOptions: { 54 | poll: true, 55 | aggregateTimeout: 1000 56 | } 57 | }; 58 | --------------------------------------------------------------------------------