├── .gitignore
├── LICENSE.md
├── README.md
├── bower.json
├── demoApp
├── app.css
├── demoApp.js
└── index.html
├── dist
├── md-time-picker.css
└── md-time-picker.js
├── img
├── modal-hour-12.PNG
├── modal-hour-24.PNG
├── modal-minute.PNG
├── time-picker-12.PNG
└── time-picker-24.PNG
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # hidden files
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2017 Matthew Bajorek
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 | [](https://badge.fury.io/js/angular-material-time-picker)
2 |
3 | # Looking for maintainers
4 | There are people still using angular-material-time-picker in their projects and we have a few issues open + pull requests.
5 | This projects is open to new lead maintainers. Please contact the current maintainer.
6 |
7 | # Angular Material Time Picker (md-time-picker)
8 |
9 | Angular Material has a date picker, but no time picker. This module is an effort to implement a time picker in [Angular Material](https://material.angularjs.org/latest/).
10 |
11 | The modal popup is based off [mdPickers](https://github.com/alenaksu/mdPickers), but with modifications for 24 hour time, removal of dependency on momentjs, and improvements in usability.
12 |
13 | The "time input field" is made up of three individual text inputs for hour, minute, and meridiem. This allows better cross browser support, especially for older browsers that do not support time input.
14 |
15 | * [Screenshots](#screenshots)
16 | * [Demo](#demo)
17 | * [Requirements](#requirements)
18 | * [Installation](#installation)
19 | * [Usage](#usage)
20 | * [License](#license)
21 |
22 | ## Screenshots
23 |
24 | #### On page
25 |
26 | 12 hour | 24 hour
27 | --------|--------
28 |  | 
29 |
30 | #### Modal
31 |
32 | 12 Hour | 24 Hour | Minute
33 | --------|---------|--------
34 |  |  | 
35 |
36 | ## Demo
37 |
38 | A live [Codepen demo](http://codepen.io/mattbajorek/pen/OpGVyv). The same html/css file is also included in the `demoApp` folder.
39 |
40 | ## Requirements
41 |
42 | * 1.5.0 < AngularJS < 2 (angular-messages, angular-animate, angular-aria)
43 | * Angular Material > 1
44 | * Material Icons
45 |
46 | ## Installation
47 |
48 | #### Using npm
49 |
50 | ```
51 | npm install angular-material-time-picker --save
52 | ```
53 |
54 | You may use Webpack to inject this module into your application.
55 |
56 | #### ES5
57 | ```javascript
58 | require('angular-material-time-picker/dist/md-time-picker.css');
59 | var ngTimePicker = require('angular-material-time-picker');
60 | angular.module('myApp', [ngTimePicker]);
61 | ```
62 |
63 | #### ES6
64 | ```javascript
65 | import 'angular-material-time-picker/dist/md-time-picker.css';
66 | import ngTimePicker from 'angular-material-time-picker';
67 | angular.module('myApp', [ngTimePicker]);
68 | ```
69 |
70 | ## Usage
71 |
72 | **Example Controller**
73 |
74 | ```javascript
75 | angular.module('demoApp')
76 |
77 | .controller('demoAppController', ['$scope','$mdpTimePicker', function($scope) {
78 |
79 | // Model bound to input fields and modal
80 | $scope.time = new Date();
81 |
82 | // Optional message to display below each input field
83 | $scope.message = {
84 | hour: 'Hour is required',
85 | minute: 'Minute is required',
86 | meridiem: 'Meridiem is required'
87 | }
88 |
89 | $scope.readonly = false;
90 |
91 | $scope.required = true;
92 |
93 | }]);
94 | ```
95 |
96 | **Example Template**
97 |
98 | ```html
99 |
100 | ```
101 | **Optional Attributes**
102 | * **message** (takes an object with keys: hour, minute, and meridiem)
103 | * **no-meridiem** (changes time picker to 24 hour time, 12 hour time is default)
104 | * **no-auto-switch** (stops modal from switching to minutes automatically after an hour is pressed)
105 | * **read-only** (set read only on input. Accepts true or false)
106 | * **mandatory** (input will be validated as required if true. Accepts true or false).
107 |
108 | ## License
109 |
110 | This software is provided free of charge and without restriction under the [MIT License](LICENSE.md)
111 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-material-time-picker",
3 | "description": "Material design time picker for 12 hour and 24 hour time",
4 | "main": [
5 | "dist/md-time-picker.js",
6 | "dist/md-time-picker.css"
7 | ],
8 | "authors": [
9 | "Matt Bajorek"
10 | ],
11 | "license": "MIT",
12 | "keywords": [
13 | "angular",
14 | "material",
15 | "time",
16 | "picker",
17 | "material",
18 | "design"
19 | ],
20 | "homepage": "https://github.com/classlinkinc/angular-material-time-picker",
21 | "private": true,
22 | "ignore": [
23 | "**/.*",
24 | "node_modules",
25 | "bower_components",
26 | "test",
27 | "tests"
28 | ]
29 | }
--------------------------------------------------------------------------------
/demoApp/app.css:
--------------------------------------------------------------------------------
1 | body,
2 | md-content {
3 | background-color: #f5f5f5 !important;
4 | }
5 |
6 | body > md-toolbar {
7 | z-index: 3;
8 | }
9 |
10 | md-toolbar.md-table-toolbar.alternate {
11 | color: #1e88e5;
12 | background-color: #e3f2fd;
13 | }
14 |
15 | md-toolbar.md-table-toolbar.alternate .md-toolbar-tools {
16 | font-size: 16px;
17 | }
18 |
19 | md-card {
20 | padding: 8px 8px 8px 24px;
21 | }
22 |
--------------------------------------------------------------------------------
/demoApp/demoApp.js:
--------------------------------------------------------------------------------
1 | angular.module('demoApp', ['ngMaterial','md.time.picker'])
2 |
3 | .config(['$mdThemingProvider', function ($mdThemingProvider) {
4 | 'use strict';
5 |
6 | $mdThemingProvider.theme('default')
7 | .primaryPalette('blue');
8 | }])
9 |
10 | .controller('demoAppController', ['$scope','$mdpTimePicker', function($scope) {
11 |
12 | $scope.time = {
13 | twelve: new Date(),
14 | twentyfour: new Date()
15 | };
16 |
17 | $scope.message = {
18 | hour: 'Hour is required',
19 | minute: 'Minute is required',
20 | meridiem: 'Meridiem is required'
21 | }
22 |
23 | }]);
24 |
--------------------------------------------------------------------------------
/demoApp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Material Design Time Picker
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
30 |
31 | 12 Hour Time
32 |
33 | {{'Hours: ' + time.twelve.getHours()}}
34 | {{'Minutes: ' + time.twelve.getMinutes()}}
35 |
36 |
37 |
38 | 24 Hour Time
39 |
40 | {{'Hours: ' + time.twentyfour.getHours()}}
41 | {{'Minutes: ' + time.twentyfour.getMinutes()}}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/dist/md-time-picker.css:
--------------------------------------------------------------------------------
1 | ng-form[name=timeForm] {
2 | display: block;
3 | position: relative;
4 | vertical-align: middle;
5 | }
6 | ng-form[name=timeForm]>* {
7 | display: inline-block;
8 | }
9 | ng-form[name=timeForm] input,
10 | ng-form[name=timeForm] md-input-container {
11 | text-align: center;
12 | padding: 0;
13 | }
14 | ng-form[name=timeForm] md-input-container .md-errors-spacer {
15 | min-height: 0;
16 | }
17 | ng-form[name=timeForm] div.time-error-messages {
18 | position: absolute;
19 | top: 30px;
20 | }
21 | md-hours-minutes {
22 | width: 60px;
23 | }
24 | md-meridiem {
25 | max-width: 100px;
26 | }
27 | md-meridiem .md-select-value {
28 | padding: 2px;
29 | min-height: 25px;
30 | }
31 | md-meridiem .md-select-value.md-select-placeholder {
32 | padding-left: 5px;
33 | }
34 | md-meridiem .md-select-value .md-select-icon {
35 | width: 15px;
36 | }
37 | .time-colon {
38 | position: absolute;
39 | top: 23px;
40 | padding-left: 4px;
41 | }
42 | .md-up-arrow, .md-down-arrow {
43 | position: absolute;
44 | height: 0;
45 | top: 7px;
46 | right: 2px;
47 | color: rgba(0,0,0,0.38);
48 | -webkit-user-select: none; /* Chrome all / Safari all */
49 | -moz-user-select: none; /* Firefox all */
50 | -ms-user-select: none; /* IE 10+ */
51 | user-select: none; /* Likely future */
52 | }
53 | .md-up-arrow:focus,.md-down-arrow:focus {
54 | outline: none;
55 | }
56 | .md-up-arrow:hover,.md-down-arrow:hover {
57 | color: black;
58 | cursor: pointer;
59 | }
60 | .md-up-arrow:after, .md-down-arrow:after{
61 | display: block;
62 | position: relative;
63 | speak: none;
64 | font-size: 13px;
65 | transform: scaleY(.5) scaleX(1);
66 | }
67 | .md-up-arrow:after{
68 | content: '\25B2';
69 | top: -6px;
70 | }
71 | .md-down-arrow:after{
72 | content: '\25BC';
73 | top: 3px;
74 | }
75 |
76 |
77 | .mdp-animate-next {
78 | opacity: 0;
79 | -webkit-transform: translate3d(50%, 0, 1px);
80 | transform: translate3d(50%, 0, 1px);
81 | }
82 | .mdp-animate-next-remove {
83 | -webkit-transition: all 0.5s cubic-bezier(0.35, 0, 0.25, 1);
84 | transition: all 0.5s cubic-bezier(0.35, 0, 0.25, 1);
85 | opacity: 0;
86 | -webkit-transform: translate3d(50%, 0, 1px);
87 | transform: translate3d(50%, 0, 1px);
88 | }
89 | .mdp-animate-next-remove-active {
90 | opacity: 1;
91 | -webkit-transform: translate3d(0, 0, 1px);
92 | transform: translate3d(0, 0, 1px);
93 | }
94 | .mdp-animate-prev {
95 | opacity: 0;
96 | -webkit-transform: translate3d(-50%, 0, 1px);
97 | transform: translate3d(-50%, 0, 1px);
98 | }
99 | .mdp-animate-prev-remove {
100 | -webkit-transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1);
101 | transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1);
102 | opacity: 0;
103 | -webkit-transform: translate3d(-50%, 0, 1px);
104 | transform: translate3d(-50%, 0, 1px);
105 | }
106 | .mdp-animate-prev-remove-active {
107 | opacity: 1;
108 | -webkit-transform: translate3d(0, 0, 1px);
109 | transform: translate3d(0, 0, 1px);
110 | }
111 | @-webkit-keyframes mdp-animation-bounce {
112 | from {
113 | opacity: 0;
114 | -webkit-transform: scale(0.95);
115 | transform: scale(0.95);
116 | }
117 | 70% {
118 | opacity: 1;
119 | -webkit-transform: scale(1.05);
120 | transform: scale(1.05);
121 | }
122 | to {
123 | -webkit-transform: scale(1);
124 | transform: scale(1);
125 | }
126 | }
127 | @keyframes mdp-animation-bounce {
128 | from {
129 | opacity: 0;
130 | -webkit-transform: scale(0.95);
131 | transform: scale(0.95);
132 | }
133 | 70% {
134 | opacity: 1;
135 | -webkit-transform: scale(1.05);
136 | transform: scale(1.05);
137 | }
138 | to {
139 | -webkit-transform: scale(1);
140 | transform: scale(1);
141 | }
142 | }
143 | .mdp-animation-zoom.ng-enter {
144 | -webkit-transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1);
145 | transition: all 0.3s cubic-bezier(0.35, 0, 0.25, 1);
146 | -webkit-animation-duration: 0.3s;
147 | animation-duration: 0.3s;
148 | -webkit-animation-name: mdp-animation-bounce;
149 | animation-name: mdp-animation-bounce;
150 | }
151 | .mdp-datepicker {
152 | max-height: initial;
153 | min-width: 234px;
154 | }
155 | .mdp-datepicker * {
156 | outline: 0;
157 | }
158 | .mdp-datepicker .md-actions {
159 | width: 100%;
160 | padding: 0px 5px;
161 | }
162 | .mdp-calendar-week-days {
163 | font-size: 0.75rem;
164 | opacity: 0.6;
165 | }
166 | .mdp-calendar-week-days > * {
167 | width: 32px;
168 | height: 32px;
169 | line-height: 32px;
170 | margin: 0 5px;
171 | padding: 0;
172 | min-width: 0px;
173 | min-height: 0px;
174 | box-shadow: none !important;
175 | background-color: transparent;
176 | }
177 | .mdp-calendar-days {
178 | font-size: 1rem;
179 | max-width: 100%;
180 | }
181 | .mdp-calendar-days .md-button,
182 | .mdp-calendar-days .mdp-day-placeholder {
183 | width: 32px;
184 | height: 32px;
185 | margin: 0 5px;
186 | }
187 | .mdp-calendar-days .md-button {
188 | cursor: pointer;
189 | border-radius: 50%;
190 | font-size: 12px;
191 | width: 32px;
192 | height: 32px;
193 | line-height: 32px;
194 | padding: 0;
195 | min-width: 0px;
196 | min-height: 0px;
197 | box-shadow: none !important;
198 | background-color: transparent;
199 | }
200 | .mdp-calendar-days .md-button[disabled]:not(.md-accent) {
201 | background-color: transparent !important;
202 | }
203 | .mdp-calendar-monthyear {
204 | font-size: 0.8125rem;
205 | font-weight: bold;
206 | line-height: 32px;
207 | min-height: 32px;
208 | }
209 | .mdp-datepicker-date,
210 | .mdp-datepicker-day,
211 | .mdp-datepicker-dow,
212 | .mdp-datepicker-month {
213 | font-size: 1.8rem;
214 | opacity: 0.6;
215 | }
216 | .mdp-datepicker-date:not(.active),
217 | .mdp-datepicker-day:not(.active),
218 | .mdp-datepicker-dow:not(.active),
219 | .mdp-datepicker-month:not(.active) {
220 | cursor: pointer;
221 | }
222 | .mdp-datepicker-date.active,
223 | .mdp-datepicker-day.active,
224 | .mdp-datepicker-dow.active,
225 | .mdp-datepicker-month.active,
226 | .mdp-datepicker-date:hover,
227 | .mdp-datepicker-day:hover,
228 | .mdp-datepicker-dow:hover,
229 | .mdp-datepicker-month:hover {
230 | opacity: 1;
231 | }
232 | .mdp-datepicker-year {
233 | font-size: 0.9rem;
234 | opacity: 0.6;
235 | padding: 0;
236 | margin: 0;
237 | }
238 | .mdp-datepicker-year:not(.active) {
239 | cursor: pointer;
240 | }
241 | .mdp-datepicker-year.active,
242 | .mdp-datepicker-year:hover {
243 | opacity: 1;
244 | }
245 | .mdp-datepicker-select-year {
246 | height: 232px;
247 | }
248 | .mdp-datepicker-select-year .repeated-year {
249 | text-align: center;
250 | }
251 | .mdp-datepicker-select-year .repeated-year .md-button {
252 | width: 100%;
253 | margin: 0;
254 | border-radius: 0;
255 | padding: 0;
256 | font-size: 1.0rem;
257 | line-height: 42px;
258 | }
259 | .mdp-datepicker-select-year .repeated-year .md-button.current {
260 | font-size: 1.8rem;
261 | line-height: 42px;
262 | }
263 | .mdp-datepicker-select-year .md-virtual-repeat-container,
264 | .mdp-datepicker-select-year .md-virtual-repeat-offsetter,
265 | .mdp-datepicker-select-year .md-virtual-repeat-scroller {
266 | height: 100%;
267 | width: 100%;
268 | }
269 | mdp-date-picker > md-input-container.md-has-icon {
270 | padding-left: 40px;
271 | }
272 | mdp-date-picker .md-button.md-icon-button {
273 | margin: 18px 0 0 0;
274 | }
275 | .mdp-datepicker:not(.portrait) .mdp-datepicker-select-year {
276 | width: 309px;
277 | }
278 | .mdp-datepicker:not(.portrait) .mdp-calendar {
279 | margin-right: 5px;
280 | width: 294px;
281 | margin-left: 10px;
282 | }
283 | .mdp-datepicker:not(.portrait) .mdp-datepicker-date-wrapper {
284 | width: 150px;
285 | }
286 | .mdp-datepicker:not(.portrait) .mdp-datepicker-dow {
287 | width: 100%;
288 | display: block;
289 | }
290 | .mdp-datepicker:not(.portrait) .mdp-calendar-week-days > *,
291 | .mdp-datepicker:not(.portrait) .mdp-calendar-days > * {
292 | width: 42px;
293 | }
294 | .mdp-datepicker .mdp-datepicker-date-wrapper {
295 | padding: 16px 35px 16px 16px;
296 | }
297 | .mdp-datepicker md-dialog-content {
298 | overflow: hidden;
299 | padding: 0px;
300 | }
301 | .mdp-datepicker md-dialog-content .mdp-calendar {
302 | width: 294px;
303 | overflow-x: hidden;
304 | }
305 | .mdp-datepicker.portrait {
306 | max-width: 234px;
307 | }
308 | .mdp-datepicker.portrait .mdp-calendar {
309 | text-align: center;
310 | width: 100%;
311 | }
312 | .mdp-datepicker.portrait .mdp-datepicker-select-year {
313 | height: 252px;
314 | }
315 | .mdp-datepicker.portrait md-dialog-content > * {
316 | width: 100%;
317 | }
318 | .mdp-datepicker.portrait .mdp-calendar-week-days,
319 | .mdp-datepicker.portrait .mdp-calendar-days,
320 | .mdp-datepicker.portrait .md-actions {
321 | padding: 0 5px;
322 | }
323 | .mdp-datepicker.portrait .md-actions {
324 | margin-top: 20px;
325 | }
326 | .mdp-datepicker.portrait .mdp-calendar-week-days > *,
327 | .mdp-datepicker.portrait .mdp-calendar-days > * {
328 | width: 32px;
329 | }
330 | mdp-calendar {
331 | display: block;
332 | }
333 | .mdp-calendar-week-days {
334 | width: 100%;
335 | }
336 | .mdp-calendar-week-days > * {
337 | margin: 0;
338 | }
339 | .mdp-calendar-days .md-button,
340 | .mdp-calendar-days .mdp-day-placeholder {
341 | margin: 0;
342 | }
343 | .mdp-clock {
344 | width: 200px;
345 | height: 200px;
346 | border-radius: 50%;
347 | cursor: pointer;
348 | padding: 24px;
349 | background: #ededed;
350 | }
351 | .mdp-clock .md-button {
352 | box-shadow: none !important;
353 | background-color: transparent;
354 | display: block;
355 | position: absolute;
356 | min-height: 32px;
357 | width: 32px;
358 | height: 32px;
359 | font-size: 12px;
360 | line-height: 32px;
361 | margin: 0;
362 | padding: 0;
363 | -webkit-transform: translate(-50%, -50%);
364 | transform: translate(-50%, -50%);
365 | }
366 | .mdp-clock .mdp-clock-container {
367 | width: 100%;
368 | height: 100%;
369 | position: relative;
370 | display: block;
371 | }
372 | .mdp-clock .mdp-pointer {
373 | min-height: 0px;
374 | width: 1px;
375 | height: 50%;
376 | position: absolute;
377 | left: 0;
378 | right: 0;
379 | bottom: 0;
380 | margin: 0 auto;
381 | -webkit-transform-origin: top center;
382 | transform-origin: top center;
383 | z-index: 0;
384 | pointer-events: none;
385 | }
386 | .mdp-clock .mdp-clock-center {
387 | min-height: 0px;
388 | height: 6px;
389 | width: 6px;
390 | position: absolute;
391 | left: 0;
392 | right: 0;
393 | top: 0;
394 | bottom: 0;
395 | margin: auto;
396 | border-radius: 50%;
397 | }
398 | .mdp-clock .md-button.mdp-clock-selected {
399 | position: absolute;
400 | border-radius: 50%;
401 | width: 8px;
402 | height: 8px;
403 | bottom: -8px;
404 | left: 0px;
405 | min-width: 0;
406 | min-height: 0;
407 | pointer-events: none;
408 | }
409 | .mdp-timepicker .mdp-clock-switch-container {
410 | padding: 20px;
411 | width: 309px;
412 | }
413 | .mdp-timepicker .mdp-timepicker-time {
414 | padding: 15px;
415 | }
416 | .mdp-timepicker .mdp-timepicker-selected-time {
417 | font-size: 3rem;
418 | }
419 | .mdp-timepicker:not(.portrait) .mdp-timepicker-time {
420 | width: 160px;
421 | }
422 | .mdp-timepicker.portrait .mdp-timepicker-selected-time {
423 | font-size: 4rem;
424 | margin-right: 1.5rem;
425 | }
426 | mdp-time-picker md-input-container.md-has-icon {
427 | padding-left: 40px;
428 | }
429 | mdp-time-picker .md-button.md-icon-button {
430 | margin: 18px 0 0 0;
431 | }
432 | .mdp-timepicker-selected-time > span,
433 | .mdp-timepicker-selected-ampm > span {
434 | outline: 0;
435 | opacity: 0.6;
436 | }
437 | .mdp-timepicker-selected-time > span:not(.active),
438 | .mdp-timepicker-selected-ampm > span:not(.active) {
439 | cursor: pointer;
440 | }
441 | .mdp-timepicker-selected-time > span.active,
442 | .mdp-timepicker-selected-ampm > span.active {
443 | opacity: 1;
444 | }
445 | .mdp-clock-deg0 {
446 | top: 0%;
447 | left: 50%;
448 | }
449 | .mdp-clock-deg15 {
450 | top: 1.70370869%;
451 | left: 62.94095226%;
452 | }
453 | .mdp-clock-deg30 {
454 | top: 6.69872981%;
455 | left: 75%;
456 | }
457 | .mdp-clock-deg45 {
458 | top: 14.64466094%;
459 | left: 85.35533905%;
460 | }
461 | .mdp-clock-deg60 {
462 | top: 25%;
463 | left: 93.30127019%;
464 | }
465 | .mdp-clock-deg75 {
466 | top: 37.05904774%;
467 | left: 98.29629131%;
468 | }
469 | .mdp-clock-deg90 {
470 | top: 50%;
471 | left: 100%;
472 | }
473 | .mdp-clock-deg105 {
474 | top: 62.94095226%;
475 | left: 98.29629131%;
476 | }
477 | .mdp-clock-deg120 {
478 | top: 75%;
479 | left: 93.30127019%;
480 | }
481 | .mdp-clock-deg135 {
482 | top: 85.35533906%;
483 | left: 85.35533906%;
484 | }
485 | .mdp-clock-deg150 {
486 | top: 93.30127019%;
487 | left: 75%;
488 | }
489 | .mdp-clock-deg165 {
490 | top: 98.29629131%;
491 | left: 62.94095226%;
492 | }
493 | .mdp-clock-deg180 {
494 | top: 100%;
495 | left: 50%;
496 | }
497 | .mdp-clock-deg195 {
498 | top: 98.29629131%;
499 | left: 37.05904774%;
500 | }
501 | .mdp-clock-deg210 {
502 | top: 93.30127019%;
503 | left: 25%;
504 | }
505 | .mdp-clock-deg225 {
506 | top: 85.35533906%;
507 | left: 14.64466094%;
508 | }
509 | .mdp-clock-deg240 {
510 | top: 75%;
511 | left: 6.69872981%;
512 | }
513 | .mdp-clock-deg255 {
514 | top: 62.94095226%;
515 | left: 1.703708686%;
516 | }
517 | .mdp-clock-deg270 {
518 | top: 50%;
519 | left: 0%;
520 | }
521 | .mdp-clock-deg285 {
522 | top: 37.05904774%;
523 | left: 1.703708686%;
524 | }
525 | .mdp-clock-deg300 {
526 | top: 25%;
527 | left: 6.69872981%;
528 | }
529 | .mdp-clock-deg315 {
530 | top: 14.64466094%;
531 | left: 14.64466094%;
532 | }
533 | .mdp-clock-deg330 {
534 | top: 6.69872981%;
535 | left: 25%;
536 | }
537 | .mdp-clock-deg345 {
538 | top: 1.703708686%;
539 | left: 37.05904774%;
540 | }
541 | .mdp-clock-deg360 {
542 | top: 0%;
543 | left: 50%;
544 | }
545 |
--------------------------------------------------------------------------------
/dist/md-time-picker.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Material Time Picker
3 | * https://github.com/classlinkinc/angular-material-time-picker
4 | * @license MIT
5 | * v1.0.1
6 | */
7 | (function(window, angular, undefined) {
8 | 'use strict';
9 |
10 | function increase(value, min, max, type) {
11 | var num = parseInt(value);
12 | if (isNaN(num) || num === max)
13 | num = min;
14 | else
15 | num++;
16 | if (type === 'MM')
17 | return format(num);
18 | return String(num);
19 | }
20 |
21 | function decrease(value, min, max, type) {
22 | var num = parseInt(value);
23 | if (isNaN(num) || num === min)
24 | num = max;
25 | else
26 | num--;
27 | if (type === 'MM')
28 | return format(num);
29 | return String(num);
30 | }
31 |
32 | function format(num) {
33 | if (num < 10)
34 | return '0' + String(num);
35 | return String(num);
36 | }
37 |
38 | function handleInput(value, max, blur, type) {
39 | var num = parseInt(value);
40 | if (type === 'HH' && num === 0) {
41 | if (num === 0) {
42 | return String(num);
43 | }
44 | return;
45 | }
46 | if (num > max)
47 | return String(num)[0];
48 | else if (!isNaN(num)) {
49 | if (value.length === 2 || (blur && type === 'MM'))
50 | return format(num);
51 | return String(num);
52 | }
53 | }
54 |
55 | angular.module('md.time.picker', ['ngMessages'])
56 |
57 | .directive('mdHoursMinutes', function() {
58 |
59 | return {
60 |
61 | restrict: 'E',
62 | scope: {
63 | type: '@',
64 | message: '@',
65 | ngModel: '=',
66 | readOnly: '<', // true or false
67 | mandatory: '<' // true or false
68 | },
69 | template: '' +
70 | '' +
80 | '' +
81 | '' +
82 | '' +
83 | '
{{message}}
' +
84 | '
' +
85 | '',
86 | controller: ["$scope", "$rootScope", function($scope, $rootScope) {
87 |
88 | if ($scope.type === "HH") {
89 | if ($scope.$parent.noMeridiem) {
90 | $scope.min = 0;
91 | $scope.max = 23;
92 | } else {
93 | $scope.min = 1;
94 | $scope.max = 12;
95 | }
96 | } else {
97 | $scope.min = 0;
98 | $scope.max = 59;
99 | }
100 |
101 | function setTime() {
102 | if ($scope.type === "HH") {
103 | var hours = '';
104 | try {
105 | hours = $scope.$parent.ngModel.getHours();
106 | } catch (e) {
107 | // leave hours empty to allow empty values
108 | }
109 |
110 | if (!$scope.$parent.noMeridiem) {
111 | if (hours > 12)
112 | hours -= 12;
113 | else if (hours === 0)
114 | hours += 12;
115 | }
116 | $scope.time.HH = String(hours);
117 | } else
118 | if ($scope.$parent.ngModel) {
119 | $scope.time.MM = format($scope.$parent.ngModel.getMinutes());
120 | } else {
121 | // leave MM empty to allow empty values
122 | $scope.time.MM = '';
123 | }
124 | }
125 |
126 | $scope.time = {};
127 |
128 | // make sure we update our variables if new values
129 | $scope.$watch("ngModel", function() {
130 | setTime();
131 | });
132 |
133 | var removeListener = $scope.$on('mdpTimePickerModalUpdated', setTime);
134 | $scope.$on('$destroy', removeListener);
135 |
136 | function updateTime(next) {
137 | // prevent NaN value in input field
138 | if (isNaN(next))
139 | return;
140 |
141 | // if $scope.ngModel is undefined, create new date object. else leave as is, which means user has specified date object
142 | // Set hours, minutes, seconds and milliseconds to 0 in order for the user to be able to set own values
143 | if (angular.isDate($scope.ngModel)) {
144 | if (isNaN($scope.ngModel.getTime())) {
145 | $scope.ngModel = new Date(2017, 0, 0, 0, 0, 0, 0);
146 | } else {
147 | // continue
148 | }
149 | } else {
150 | $scope.ngModel = new Date(2017, 0, 0, 0, 0, 0, 0);
151 | }
152 | if ($scope.type === 'MM') {
153 | $scope.ngModel.setMinutes(next);
154 | return;
155 | } else if (!$scope.$parent.noMeridiem) {
156 | var hours = $scope.ngModel.getHours();
157 | if (hours >= 12 && next != 12)
158 | next += 12;
159 | else if (hours < 12 && next == 12)
160 | next = 0;
161 | }
162 | $scope.ngModel.setHours(next);
163 | }
164 |
165 | $scope.increase = function() {
166 | var next = increase($scope.time[$scope.type], $scope.min, $scope.max, $scope.type)
167 | $scope.time[$scope.type] = next;
168 | updateTime(parseInt(next));
169 | $rootScope.$emit('mdpTimePickerUpdated');
170 | }
171 |
172 | $scope.decrease = function() {
173 | var next = decrease($scope.time[$scope.type], $scope.min, $scope.max, $scope.type);
174 | $scope.time[$scope.type] = next;
175 | updateTime(parseInt(next));
176 | $rootScope.$emit('mdpTimePickerUpdated');
177 | }
178 |
179 | $scope.handleInput = function(blur) {
180 | var next = handleInput($scope.time[$scope.type], $scope.max, blur, $scope.type);
181 | $scope.time[$scope.type] = next;
182 | updateTime(parseInt(next));
183 | $rootScope.$emit('mdpTimePickerUpdated');
184 | }
185 |
186 | $scope.handleKeypress = function(ev) {
187 | if (ev.keyCode === 38) $scope.increase();
188 | else if (ev.keyCode === 40) $scope.decrease();
189 | }
190 |
191 | }]
192 | }
193 |
194 | })
195 |
196 | .directive('mdMeridiem', function() {
197 |
198 | return {
199 |
200 | restrict: 'E',
201 | scope: {
202 | message: '@',
203 | readOnly: '<', // true or false
204 | ngModel: '=',
205 | mandatory: '<' // true or false
206 | },
207 | template: '' +
208 | '' +
216 | 'AM' +
217 | 'PM' +
218 | '' +
219 | '' +
220 | '
{{message}}
' +
221 | '
' +
222 | '',
223 | controller: ["$scope", "$rootScope", function($scope, $rootScope) {
224 |
225 | function setMeridiem() {
226 | var hours = '';
227 | try {
228 | hours = $scope.$parent.$parent.ngModel.getHours();
229 | } catch (e) {
230 | // leave hours empty
231 | }
232 | $scope.meridiem = hours >= 0 && hours < 12 ? 'AM' : 'PM';
233 | }
234 |
235 | // update meridiem on load of view and when model is changing
236 | $scope.$watch("ngModel", function() {
237 | setMeridiem();
238 | });
239 |
240 | $scope.updateTime = function() {
241 | var hours = $scope.$parent.$parent.ngModel.getHours();
242 | if ($scope.meridiem === 'AM') $scope.$parent.$parent.ngModel.setHours(hours-12);
243 | else $scope.$parent.$parent.ngModel.setHours(hours+12);
244 | $rootScope.$emit('mdpTimePickerUpdated');
245 | }
246 |
247 | var removeListener = $scope.$on('mdpTimePickerModalUpdated', setMeridiem);
248 | $scope.$on('$destroy', removeListener);
249 |
250 | }]
251 |
252 | }
253 |
254 | })
255 |
256 | .directive('mdTimePicker', function() {
257 |
258 | return {
259 |
260 | restrict: 'E',
261 | scope: {
262 | message: '<',
263 | ngModel: '=',
264 | readOnly: '<', // true or false
265 | mandatory: '<' // true or false
266 | },
267 | template: '' +
268 | '' +
274 | '' +
275 | ':' +
276 | '' +
277 | '' +
278 | '',
279 | controller: ["$scope", "$rootScope", "$mdpTimePicker", "$attrs", function($scope, $rootScope, $mdpTimePicker, $attrs) {
280 |
281 | $scope.showPicker = function(ev) {
282 |
283 | $mdpTimePicker($scope.ngModel, {
284 | targetEvent: ev,
285 | noMeridiem: $scope.noMeridiem,
286 | autoSwitch: !$scope.noAutoSwitch
287 | }).then(function(time) {
288 | // if $scope.ngModel is not a valid date, create new date object.
289 | // Set hours, minutes, seconds and milliseconds to 0 in order for the user to be able to set own values
290 | if (angular.isDate($scope.ngModel)) {
291 | if (isNaN($scope.ngModel.getTime())) {
292 | $scope.ngModel = new Date(2017, 0, 0, 0, 0, 0, 0);
293 | }
294 | } else {
295 | $scope.ngModel = new Date(2017, 0, 0, 0, 0, 0, 0);
296 | }
297 | $scope.ngModel.setHours(time.getHours());
298 | $scope.ngModel.setMinutes(time.getMinutes());
299 | $scope.$broadcast('mdpTimePickerModalUpdated');
300 | $rootScope.$emit('mdpTimePickerUpdated');
301 | });
302 |
303 | }
304 |
305 | }],
306 | compile: function(tElement, tAttrs) {
307 | return {
308 | pre: function preLink(scope) {
309 | scope.noMeridiem = tAttrs.noMeridiem === "" ? true : false;
310 | scope.noAutoSwitch = tAttrs.noAutoSwitch === "" ? true : false;
311 | }
312 | }
313 | }
314 |
315 | }
316 |
317 | })
318 |
319 | .provider("$mdpTimePicker", function() {
320 | var LABEL_OK = "OK",
321 | LABEL_CANCEL = "Cancel";
322 |
323 | this.setOKButtonLabel = function(label) {
324 | LABEL_OK = label;
325 | };
326 |
327 | this.setCancelButtonLabel = function(label) {
328 | LABEL_CANCEL = label;
329 | };
330 |
331 | this.$get = ["$mdDialog", function($mdDialog) {
332 | var timePicker = function(time, options) {
333 |
334 | return $mdDialog.show({
335 | controller: ['$scope', '$mdDialog', '$mdMedia', function ($scope, $mdDialog, $mdMedia) {
336 | var self = this;
337 |
338 | // check if time is valid date. Create new date object if not.
339 | if (angular.isDate(time)) {
340 | if (isNaN(time.getTime())) {
341 | time = new Date(2017, 0, 0, 0, 0, 0, 0);
342 | } else {
343 | // continue
344 | }
345 | } else {
346 | time = new Date(2017, 0, 0, 0, 0, 0, 0);
347 | }
348 |
349 | this.time = new Date(time.getTime());
350 | this.noMeridiem = options.noMeridiem;
351 | if (!self.noMeridiem)
352 | this.meridiem = time.getHours() < 12 ? 'AM' : 'PM';
353 |
354 | this.VIEW_HOURS = 1;
355 | this.VIEW_MINUTES = 2;
356 | this.currentView = this.VIEW_HOURS;
357 | this.autoSwitch = !!options.autoSwitch;
358 |
359 | $scope.$mdMedia = $mdMedia;
360 |
361 | this.switchView = function() {
362 | self.currentView = self.currentView == self.VIEW_HOURS ? self.VIEW_MINUTES : self.VIEW_HOURS;
363 | };
364 |
365 | this.hours = function() {
366 | var hours = self.time.getHours();
367 | if (self.noMeridiem) return hours;
368 | if (hours > 12) return hours-12;
369 | else if (hours === 0) return 12;
370 | return hours;
371 | }
372 |
373 | this.minutes = function() {
374 | return format(self.time.getMinutes());
375 | }
376 |
377 | this.setAM = function() {
378 | var hours = self.time.getHours();
379 | if (hours >= 12) {
380 | self.time.setHours(hours - 12);
381 | self.meridiem = 'AM';
382 | }
383 | };
384 |
385 | this.setPM = function() {
386 | var hours = self.time.getHours();
387 | if (hours < 12) {
388 | self.time.setHours(hours + 12);
389 | self.meridiem = 'PM';
390 | }
391 | };
392 |
393 | this.cancel = function() {
394 | $mdDialog.cancel();
395 | };
396 |
397 | this.confirm = function() {
398 | $mdDialog.hide(this.time);
399 | };
400 | }],
401 | controllerAs: 'timepicker',
402 | clickOutsideToClose: true,
403 | template: '' +
404 | '' +
405 | '' +
406 | '' +
407 | '{{ timepicker.hours() }}:' +
408 | '{{ timepicker.minutes() }}' +
409 | '
' +
410 | '' +
411 | 'AM' +
412 | 'PM' +
413 | '
' +
414 | '' +
415 | '' +
416 | '
' +
417 | '' +
418 | '' +
419 | '
' +
420 |
421 | '
' +
422 | '' +
423 | '' + LABEL_CANCEL + '' +
424 | '' + LABEL_OK + '' +
425 | '' +
426 | '
' +
427 | '' +
428 | '',
429 | targetEvent: options.targetEvent,
430 | locals: {
431 | time: time,
432 | noMeridiem: options.noMeridiem,
433 | autoSwitch: options.autoSwitch
434 | },
435 | skipHide: true,
436 | multiple: true
437 | });
438 | };
439 |
440 | return timePicker;
441 | }];
442 | })
443 |
444 | .directive("mdpClock", ["$animate", "$timeout", function($animate, $timeout) {
445 | return {
446 | restrict: 'E',
447 | bindToController: {
448 | 'type': '@?',
449 | 'time': '=',
450 | 'autoSwitch': '=?'
451 | },
452 | replace: true,
453 | template: '' +
454 | '
' +
455 | '' +
456 | '' +
457 | '' +
458 | '' +
459 | '{{ step }}' +
460 | '{{ step }}' +
461 | '
' +
462 | '
',
463 | controller: ["$scope", function ($scope) {
464 | var TYPE_HOURS = "hours";
465 | var TYPE_MINUTES = "minutes";
466 | var self = this;
467 |
468 | this.noMeridiem = $scope.$parent.timepicker.noMeridiem;
469 |
470 | this.STEP_DEG = this.noMeridiem ? 360/24 : 360/12;
471 | this.STEP_DEG_MINUTES = 360/12;
472 | this.steps = [];
473 |
474 | this.CLOCK_TYPES = {
475 | "hours": {
476 | range: this.noMeridiem ? 24 : 12,
477 | },
478 | "minutes": {
479 | range: 60,
480 | }
481 | }
482 |
483 | this.getPointerStyle = function() {
484 | var divider = 1;
485 | switch (self.type) {
486 | case TYPE_HOURS:
487 | divider = self.noMeridiem ? 24 : 12;
488 | break;
489 | case TYPE_MINUTES:
490 | divider = 60;
491 | break;
492 | }
493 | var degrees = Math.round(self.selected * (360 / divider)) - 180;
494 | return {
495 | "-webkit-transform": "rotate(" + degrees + "deg)",
496 | "-ms-transform": "rotate(" + degrees + "deg)",
497 | "transform": "rotate(" + degrees + "deg)"
498 | }
499 | };
500 |
501 | this.setTimeByDeg = function(deg) {
502 |
503 | var divider = 0;
504 | switch (self.type) {
505 | case TYPE_HOURS:
506 | divider = self.noMeridiem ? 24 : 12;
507 | break;
508 | case TYPE_MINUTES:
509 | divider = 60;
510 | break;
511 | }
512 |
513 | var time = Math.round(divider / 360 * deg);
514 | if (!self.noMeridiem && self.type === "hours" && time === 0)
515 | time = 12;
516 | else if (self.type === "minutes" && time === 60)
517 | time = 0;
518 | self.setTime(time);
519 | };
520 |
521 | this.setTime = function(time) {
522 |
523 | this.selected = time;
524 |
525 | switch (self.type) {
526 | case TYPE_HOURS:
527 | if (!self.noMeridiem) {
528 | var PM = this.time.getHours() >= 12 ? true : false;
529 | if (PM && time != 12)
530 | time += 12;
531 | else if (!PM && time === 12)
532 | time = 0;
533 | }
534 | this.time.setHours(time);
535 | break;
536 | case TYPE_MINUTES:
537 | this.time.setMinutes(time);
538 | break;
539 | }
540 |
541 | };
542 |
543 | this.$onInit = function() {
544 |
545 | self.type = self.type || "hours";
546 |
547 | switch (self.type) {
548 | case TYPE_HOURS:
549 | if (self.noMeridiem) {
550 | for (var i = 1; i <= 23; i++)
551 | self.steps.push(i);
552 | self.steps.push(0);
553 | self.selected = self.time.getHours() || 0;
554 | }
555 | else {
556 | for (var i = 1; i <= 12; i++)
557 | self.steps.push(i);
558 | self.selected = self.time.getHours() || 0;
559 | if (self.selected > 12) self.selected -= 12;
560 | }
561 |
562 | break;
563 | case TYPE_MINUTES:
564 | for (var i = 5; i <= 55; i += 5)
565 | self.steps.push(i);
566 | self.steps.push(0);
567 |
568 | self.selected = self.time.getMinutes() || 0;
569 |
570 | break;
571 | }
572 | };
573 | // Prior to v1.5, we need to call `$onInit()` manually.
574 | // (Bindings will always be pre-assigned in these versions.)
575 | if (angular.version.major === 1 && angular.version.minor < 5) {
576 | this.$onInit();
577 | }
578 | }],
579 | controllerAs: "clock",
580 | link: function(scope, element, attrs, ctrl) {
581 | var pointer = angular.element(element[0].querySelector(".mdp-pointer")),
582 | timepickerCtrl = scope.$parent.timepicker;
583 |
584 | var onEvent = function(event) {
585 | var containerCoords = event.currentTarget.getClientRects()[0];
586 | var x = ((event.currentTarget.offsetWidth / 2) - (event.pageX - containerCoords.left)),
587 | y = ((event.pageY - containerCoords.top) - (event.currentTarget.offsetHeight / 2));
588 |
589 | var deg = Math.round((Math.atan2(x, y) * (180 / Math.PI)));
590 | $timeout(function() {
591 | ctrl.setTimeByDeg(deg + 180);
592 | if (ctrl.type === 'hours' && ctrl.autoSwitch) timepickerCtrl.switchView();
593 | });
594 | };
595 |
596 | element.on("click", onEvent);
597 | scope.$on("$destroy", function() {
598 | element.off("click", onEvent);
599 | });
600 |
601 | }
602 | }
603 | }]);
604 |
605 | })(window, angular);
606 |
--------------------------------------------------------------------------------
/img/modal-hour-12.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classlinkinc/angular-material-time-picker/f539e7cca6127505aba29865a72523a606ba743a/img/modal-hour-12.PNG
--------------------------------------------------------------------------------
/img/modal-hour-24.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classlinkinc/angular-material-time-picker/f539e7cca6127505aba29865a72523a606ba743a/img/modal-hour-24.PNG
--------------------------------------------------------------------------------
/img/modal-minute.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classlinkinc/angular-material-time-picker/f539e7cca6127505aba29865a72523a606ba743a/img/modal-minute.PNG
--------------------------------------------------------------------------------
/img/time-picker-12.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classlinkinc/angular-material-time-picker/f539e7cca6127505aba29865a72523a606ba743a/img/time-picker-12.PNG
--------------------------------------------------------------------------------
/img/time-picker-24.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/classlinkinc/angular-material-time-picker/f539e7cca6127505aba29865a72523a606ba743a/img/time-picker-24.PNG
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('angular-material');
2 | require('./dist/md-time-picker');
3 |
4 | module.exports = 'md.time.picker';
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-material-time-picker",
3 | "version": "1.0.8",
4 | "description": "Material design time picker for 12 hour and 24 hour time",
5 | "main": "index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git://github.com/classlinkinc/angular-material-time-picker.git"
9 | },
10 | "keywords": [
11 | "angular",
12 | "material",
13 | "time",
14 | "picker",
15 | "material",
16 | "design"
17 | ],
18 | "author": "Matt Bajorek",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/classlinkinc/angular-material-time-picker/issues"
22 | },
23 | "homepage": "https://github.com/classlinkinc/angular-material-time-picker#readme",
24 | "peerDependencies": {
25 | "angular": "^1.5.0",
26 | "angular-material": "^1.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------