├── .editorconfig ├── .gitattributes ├── .gitignore ├── .jshintrc ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── bower.json ├── build ├── angular-datepicker.js └── themes │ ├── classic.css │ ├── classic.date.css │ ├── classic.time.css │ ├── default.css │ ├── default.date.css │ ├── default.time.css │ └── rtl.css ├── package.json └── src ├── directives.js ├── legacy.js ├── picker.date.js ├── picker.js ├── picker.time.js └── themes ├── _variables.less ├── base.date.less ├── base.less ├── base.time.less ├── classic.date.less ├── classic.less ├── classic.time.less ├── default.date.less ├── default.less ├── default.time.less └── rtl.less /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = true 10 | indent_style = tab 11 | indent_style = space 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.DS_Store 3 | 4 | *.sublime-project 5 | 6 | *.sublime-workspace 7 | 8 | .idea 9 | 10 | node_modules/* 11 | 12 | bower_components/* 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "debug": true, 3 | "devel": true, 4 | "browser": true, 5 | "asi": true, 6 | "unused": true, 7 | "eqnull": true 8 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Want to help contribute something to the project? Awesome! :smile: 4 | 5 | Please take a moment to review this doc to make contributions easy and effective for everyone. 6 | 7 | 8 | 9 |
10 | 11 | ## Bug reports 12 | 13 | If you believe you’ve found a bug within the repository code: 14 | 15 | - Search the existing issues to avoid duplicates and to check if it has already been solved. 16 | - Make sure you’re using the latest build. 17 | - Isolate the problem and create a [reduced test case](http://css-tricks.com/6263-reduced-test-cases/) - preferably supported with a live example. 18 | - Try to be as detailed as possible in the report (OS, browser, expected outcome vs actual outcome, etc). 19 | - Please **do not** use the issue tracker for personal support requests. Instead try [Stack Overflow](http://stackoverflow.com) or the likes. 20 | 21 | 22 | 23 | 24 |
25 | 26 | ## Pull requests 27 | 28 | If you’re submitting a pull request, please respect the coding standards used (indentations, comments, semi-colons, etc) as per the **Golden Rule**: 29 | 30 | > All code in any code base should look like a single person typed it, no matter how many people contributed. 31 | 32 | A few other things to keep in mind: 33 | 34 | - Make sure the changes are suitable within the scope of this project. 35 | - Discuss any significant features before endeavoring into developing them. I’d hate to have anyone spend effort on something only for me to not merge it into the main project. 36 | - Include the relevant test coverage if any JavaScript files are involved. 37 | - Compile the project using `grunt --verbose` to make sure everything passes with a green flag. 38 | - Use the Semantic Versioning guide, as mentioned in the [readme file](/blob/v3.0.0/README.md), in the case that a version bump is due. 39 | 40 | 41 | #### All pull requests should be submitted to the `dev` branch. 42 | 43 | 44 | 45 | 46 |
47 | 48 | ## Feature requests 49 | 50 | Feature requests are welcome. But take a moment to find out whether your idea fits within the scope and aims of this project. It’s up to *you* to make a strong case to the merits of this feature. Please provide as much detail and context as possible. 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * This Gruntfile is used to build the project files. 4 | */ 5 | 6 | /*jshint 7 | node: true 8 | */ 9 | 10 | 11 | module.exports = function( grunt ) { 12 | 13 | // Read the package manifest. 14 | var packageJSON = grunt.file.readJSON( 'package.json' ) 15 | 16 | 17 | // Add the “curly” template delimiters. 18 | grunt.template.addDelimiters( 'curly', '{%', '%}' ) 19 | 20 | 21 | // Load the NPM tasks. 22 | grunt.loadNpmTasks( 'grunt-contrib-concat' ) 23 | grunt.loadNpmTasks( 'grunt-contrib-watch' ) 24 | grunt.loadNpmTasks( 'grunt-contrib-copy' ) 25 | grunt.loadNpmTasks( 'grunt-contrib-less' ) 26 | grunt.loadNpmTasks( 'grunt-contrib-clean' ) 27 | grunt.loadNpmTasks( 'grunt-contrib-cssmin' ) 28 | grunt.loadNpmTasks( 'grunt-contrib-uglify' ) 29 | 30 | 31 | // Setup the initial configurations. 32 | grunt.initConfig({ 33 | 34 | 35 | // Add the package data. 36 | pkg: packageJSON, 37 | 38 | 39 | // Set up the directories. 40 | dirs: { 41 | src: { 42 | pickers: 'src', 43 | themes: 'src/themes', 44 | //translations: 'src/translations' 45 | }, 46 | min: { 47 | pickers: 'build/', 48 | themes: 'build/themes', 49 | //translations: 'lib/compressed/translations' 50 | } 51 | }, 52 | 53 | // Copy over files to destination directions. 54 | copy: { 55 | /* 56 | translations: { 57 | expand: true, 58 | cwd: '<%= dirs.src.translations %>', 59 | src: [ '*' ], 60 | dest: '<%= dirs.min.translations %>' 61 | },*/ 62 | }, 63 | 64 | 65 | // Compile LESS into CSS. 66 | less: { 67 | options: { 68 | style: 'expanded' 69 | }, 70 | themes: { 71 | files: { 72 | '<%= dirs.min.themes %>/default.css': [ '<%= dirs.src.themes %>/base.less', '<%= dirs.src.themes %>/default.less' ], 73 | '<%= dirs.min.themes %>/classic.css': [ '<%= dirs.src.themes %>/base.less', '<%= dirs.src.themes %>/classic.less' ], 74 | '<%= dirs.min.themes %>/default.date.css': [ '<%= dirs.src.themes %>/base.date.less', '<%= dirs.src.themes %>/default.date.less' ], 75 | '<%= dirs.min.themes %>/default.time.css': [ '<%= dirs.src.themes %>/base.time.less', '<%= dirs.src.themes %>/default.time.less' ], 76 | '<%= dirs.min.themes %>/classic.date.css': [ '<%= dirs.src.themes %>/base.date.less', '<%= dirs.src.themes %>/classic.date.less' ], 77 | '<%= dirs.min.themes %>/classic.time.css': [ '<%= dirs.src.themes %>/base.time.less', '<%= dirs.src.themes %>/classic.time.less' ], 78 | '<%= dirs.min.themes %>/rtl.css': [ '<%= dirs.src.themes %>/rtl.less' ] 79 | } 80 | } 81 | }, 82 | 83 | 84 | // Concatenate the files and add the banner. 85 | concat: { 86 | options: { 87 | process: function( content ) { 88 | return grunt.template.process( content, { delimiters: 'curly' } ) 89 | } 90 | }, 91 | pickers: { 92 | src: [ '<%= dirs.min.pickers %>/picker.js', '<%= dirs.min.pickers %>/picker.js', '<%= dirs.min.pickers %>/picker.date.js', '<%= dirs.min.pickers %>/picker.time.js', '<%= dirs.min.pickers %>/legacy.js', '<%= dirs.min.pickers %>/directives.js'], 93 | dest: '<%= dirs.min.pickers %>/angular-datepicker.js' 94 | }, 95 | devPickers: { 96 | src: [ 'src/picker.js', 'src/picker.date.js', 'src/picker.time.js', 'src/legacy.js', 'src/directives.js'], 97 | dest: '<%= dirs.min.pickers %>/angular-datepicker.js' 98 | } 99 | }, 100 | 101 | // Minify all the things! 102 | uglify: { 103 | options: { 104 | preserveComments: 'some' 105 | }, 106 | pickers: { 107 | files: [ 108 | { 109 | expand : true, 110 | cwd : '<%= dirs.src.pickers %>', 111 | src : [ '**/*.js' ], 112 | dest : '<%= dirs.min.pickers %>' 113 | } 114 | ] 115 | } 116 | }, 117 | cssmin: { 118 | pickers: { 119 | expand: true, 120 | cwd: '<%= dirs.min.pickers %>', 121 | src: [ '**/*.css' ], 122 | dest: '<%= dirs.min.pickers %>' 123 | } 124 | }, 125 | clean: { 126 | pickers: [ '<%= dirs.min.pickers %>/*.js', '!<%= dirs.min.pickers %>/angular-datepicker.js' ], 127 | }, 128 | 129 | }) //grunt.initConfig 130 | 131 | 132 | // Register the tasks. 133 | grunt.registerTask( 'picker', [ 'less:themes', 'uglify:pickers', 'cssmin:pickers', 'uglify:pickers', 'concat:pickers', 'clean:pickers' ] ) 134 | grunt.registerTask( 'dev', [ 'less:themes', 'cssmin:pickers', 'concat:devPickers', 'clean:pickers' ] ) 135 | } //module.exports 136 | 137 | 138 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2014 Alon Gubkin, Amsul, http://amsul.ca 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-datepicker 2 | 3 | **Warning**: This project is no longer maintained. Use at your own risk! 4 | 5 | The mobile-friendly, responsive, and lightweight Angular.js date & time input picker. Perfect for Ionic apps! 6 | 7 | ![datepicker](https://dl.dropboxusercontent.com/u/16304603/datepicker.PNG), ![datepicker](https://dl.dropboxusercontent.com/u/16304603/timepicker.PNG) 8 | 9 | This is basically a [pickadate.js](https://github.com/amsul/pickadate.js) fork that completely removes the jQuery dependency and adds Angular.js directives. 10 | 11 | ### Bower 12 | 13 | `bower install angular-native-picker` 14 | 15 | ### Usage 16 | 17 | Include `build/angular-datepicker.js` in your application: 18 | 19 | ```HTML 20 | 21 | ``` 22 | 23 | Include CSS files in your application: 24 | 25 | ```HTML 26 | 27 | 28 | 29 | ``` 30 | 31 | Note: for usage within a modal the default (not classic) CSS files are recommended. 32 | 33 | Add the module `angular-datepicker` as a dependency to your app module: 34 | 35 | ```JavaScript 36 | var myapp = angular.module('myapp', ['angular-datepicker']); 37 | ``` 38 | 39 | To create a date or time picker, add the `pick-a-date` or `pick-a-time` directive to your input tag: 40 | 41 | ```HTML 42 | {{ date }} 43 | {{ time }} 44 | ``` 45 | 46 | You can also provide an options object to the directive which will be passed 47 | into the `pickadate` or `pickatime` constructor. 48 | 49 | ```javascript 50 | // somewhere in your controller 51 | $scope.options = { 52 | format: 'yyyy-mm-dd', // ISO formatted date 53 | onClose: function(e) { 54 | // do something when the picker closes 55 | } 56 | } 57 | ``` 58 | 59 | ```HTML 60 | {{ date }} 61 | {{ time }} 62 | ``` 63 | 64 | If you don't need to provide any callbacks (like `onClose`) you can specify the 65 | options object as an angular expression: 66 | 67 | ```HTML 68 | 69 | ``` 70 | 71 | For a full list of available options please refer to the pickadate documentation 72 | for [datepicker options](http://amsul.ca/pickadate.js/date.htm) and 73 | [timepicker options](http://amsul.ca/pickadate.js/time.htm). 74 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-native-datepicker", 3 | "version": "1.0.2", 4 | "main": [ 5 | "./build/themes/default.css", 6 | "./build/themes/default.date.css", 7 | "./build/themes/default.time.css", 8 | "./build/angular-datepicker.js" 9 | ], 10 | "ignore": [ 11 | "CONTRIBUTING.md", 12 | "Gruntfile.js" 13 | ], 14 | "dependencies": { 15 | "angular": "latest" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /build/angular-datepicker.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * pickadate.js v3.4.0, 2014/02/15 3 | * By Amsul, http://amsul.ca 4 | * Hosted on http://amsul.github.io/pickadate.js 5 | * Licensed under MIT 6 | */ 7 | !function(a){"function"==typeof define&&define.amd?define("picker",["angular"],a):this.Picker=a(angular)}(function(a){function b(a,d,e,g){function h(){return b._.node("div",b._.node("div",b._.node("div",b._.node("div",r.component.nodes(o.open),n.box),n.wrap),n.frame),n.holder)}function i(){p.data(d,r),p.addClass(n.input),p[0].value=p.attr("data-value")?r.get("select",m.format):a.value,angular.element(document.querySelectorAll("#"+o.id)).off("focus",l),angular.element(document.querySelectorAll("#"+o.id)).on("focus",l),m.editable||angular.element(document.querySelectorAll("#"+o.id)).on("keydown",function(a){var b=a.keyCode,c=/^(8|46)$/.test(b);return 27==b?(r.close(),!1):void((32==b||c||!o.open&&r.component.key[b])&&(a.preventDefault(),a.stopPropagation(),c?r.clear().close():r.open()))}),c(a,{haspopup:!0,expanded:!1,readonly:!1,owns:a.id+"_root"+(r._hidden?" "+r._hidden.id:"")})}function j(){r.$root.on("focusin",function(a){r.$root.removeClass(n.focused),c(r.$root[0],"selected",!1),a.stopPropagation()}),r.$root.on("mousedown click",function(b){var c=b.target;c!=r.$root.children()[0]?(b.stopPropagation(),"mousedown"==b.type&&"input"!==angular.element(c)[0].tagName&&"SELECT"!=c.nodeName&&"OPTION"!=c.nodeName&&(b.preventDefault(),a.focus())):"click"==b.type&&r.get("open")&&r.close()}),r.attachLiveEvents=function(){angular.element(r.$root[0].querySelectorAll("[data-pick], [data-nav], [data-clear], [data-close]")).off("click").on("click",function(){var c=angular.element(this),d=c.hasClass(n.navDisabled)||c.hasClass(n.disabled),e=document.activeElement;if(e=e&&(e.type||e.href)&&e,d||e&&!r.$root[0].contains(e)){a.focus(),angular.element(a).on("click",l);var f=function(){angular.element(a).off("click",l),angular.element(a).off("blur",f)};angular.element(a).off("blur",f),angular.element(a).on("blur",f)}c.attr("data-nav")&&!d?(r.set("highlight",r.component.item.highlight,{nav:parseInt(c.attr("data-nav"))}),r.attachLiveEvents()):b._.isInteger(parseInt(c.attr("data-pick")))&&!d?(r.set("select",parseInt(c.attr("data-pick"))).close(!0),r.attachLiveEvents()):c.attr("data-clear")?(r.clear().close(!0),r.attachLiveEvents()):c.attr("data-close")&&(r.close(!0),r.attachLiveEvents())})},c(r.$root[0],"hidden",!0)}function k(){var b=["string"==typeof m.hiddenPrefix?m.hiddenPrefix:"","string"==typeof m.hiddenSuffix?m.hiddenSuffix:"_submit"];r._hidden=angular.element('")[0],p.on("change."+o.id,function(){r._hidden.value=a.value?r.get("select",m.formatSubmit):""}).after(r._hidden)}function l(a){a.stopPropagation(),"focus"==a.type&&(r.$root.addClass(n.focused),c(r.$root[0],"selected",!0)),r.open()}if(!a)return b;var m;e?(m=e.defaults,angular.extend(m,g)):m=g||{};var n=b.klasses();angular.extend(n,m.klass);var o={id:a.id||"P"+Math.abs(~~(Math.random()*new Date))},p=angular.element(a),q=function(){return this.start()},r=q.prototype={constructor:q,$node:p,start:function(){return o&&o.start?r:(o.methods={},o.start=!0,o.open=!1,o.type=a.type,a.autofocus=a==document.activeElement,a.type="text",a.readOnly=!m.editable,a.id=a.id||o.id,r.component=new e(r,m),r.$root=angular.element(b._.node("div",h(),n.picker,'id="'+a.id+'_root"')),j(),m.formatSubmit&&k(),i(),m.container?angular.element(m.container).append(r.$root):p.after(r.$root),r.on({start:r.component.onStart,render:r.component.onRender,stop:r.component.onStop,open:r.component.onOpen,close:r.component.onClose,set:r.component.onSet}).on({start:m.onStart,render:m.onRender,stop:m.onStop,open:m.onOpen,close:m.onClose,set:m.onSet}),a.autofocus&&r.open(),r.trigger("start").trigger("render"))},changeSettings:function(a){angular.extend(m,a),this.stop(),this.start()},render:function(a){return a?r.$root.html(h()):angular.element(r.$root[0].querySelectorAll("."+n.box)).html(r.component.nodes(o.open)),r.attachLiveEvents(),r.trigger("render")},stop:function(){return o.start?(r.close(),r._hidden&&r._hidden.parentNode.removeChild(r._hidden),r.$root.remove(),p.removeClass(n.input).removeData(d),setTimeout(function(){p.off("."+o.id)},0),a.type=o.type,a.readOnly=!1,r.trigger("stop"),o.methods={},o.start=!1,r):r},open:function(d){return o.open?r:(p.addClass(n.active),c(a,"expanded",!0),r.$root.addClass(n.opened),c(r.$root[0],"hidden",!1),d!==!1&&(o.open=!0,p.triggerHandler("focus"),angular.element(document.querySelectorAll("#"+o.id)).off("click focusin").on("click focusin",function(b){var c=b.target;c!=a&&c!=document&&3!=b.which&&r.close(c===r.$root.children()[0])}),angular.element(document.querySelectorAll("#"+o.id)).off("keydown").on("keydown",function(c){var d=c.keyCode,e=r.component.key[d],f=c.target;27==d?r.close(!0):f!=a||!e&&13!=d?r.$root[0].contains(f)&&13==d&&(c.preventDefault(),f.click()):(c.preventDefault(),e?b._.trigger(r.component.key.go,r,[b._.trigger(e)]):angular.element(r.$root[0].querySelectorAll("."+n.highlighted)).hasClass(n.disabled)||r.set("select",r.component.item.highlight).close())})),r.trigger("open"))},close:function(b){return b&&(p.off("focus."+o.id),p.triggerHandler("focus"),setTimeout(function(){angular.element(document.querySelectorAll("#"+o.id)).off("focus",l),angular.element(document.querySelectorAll("#"+o.id)).on("focus",l)},0)),p.removeClass(n.active),c(a,"expanded",!1),r.$root.removeClass(n.opened+" "+n.focused),c(r.$root[0],"hidden",!0),c(r.$root[0],"selected",!1),o.open?(setTimeout(function(){o.open=!1},0),f.off("."+o.id),r.trigger("close")):r},clear:function(){return r.set("clear")},set:function(a,b,c){var d,e,f=angular.isObject(a),g=f?a:{};if(c=f&&angular.isObject(b)?b:c||{},a){f||(g[a]=b);for(d in g)e=g[d],d in r.component.item&&r.component.set(d,e,c),("select"==d||"clear"==d)&&(p[0].value="clear"==d?"":r.get(d,m.format),p.triggerHandler("change"));r.render()}return c.muted?r:r.trigger("set",g)},get:function(c,d){return c=c||"value",null!=o[c]?o[c]:"value"==c?a.value:c in r.component.item?"string"==typeof d?b._.trigger(r.component.formats.toString,r.component,[d,r.component.get(c)]):r.component.get(c):void 0},on:function(a,b){var c,d,e=angular.isObject(a),f=e?a:{};if(a){e||(f[a]=b);for(c in f)d=f[c],o.methods[c]=o.methods[c]||[],o.methods[c].push(d)}return r},off:function(){var a,b,c=arguments;for(a=0,namesCount=c.length;a"+c+""):""},lead:function(a){return(10>a?"0":"")+a},trigger:function(a,b,c){return"function"==typeof a?a.apply(b,c||[]):a},digits:function(a){return/\d/.test(a[1])?2:1},isDate:function(a){return{}.toString.call(a).indexOf("Date")>-1&&this.isInteger(a.getDate())},isInteger:function(a){return{}.toString.call(a).indexOf("Number")>-1&&a%1===0},ariaAttr:e},b.extend=function(a,c){angular.element.prototype[a]=function(d,e){var f=this.data(a);if("picker"==d)return f;if(f&&"string"==typeof d)return b._.trigger(f[d],f,[e]),this;for(var g=0;g=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b,c){return b=new Date,c&&c.rel&&b.setDate(b.getDate()+c.rel),this.normalize(b,c)},c.prototype.navigate=function(a,c,d){var e,f,g,h,i=b.isArray(c),j=b.isObject(c),k=this.item.view;if(i||j){for(i?(f=c[0],g=c[1],h=c[2]):(f=c.year,g=c.month,h=c.date),d&&d.nav&&k&&k.month!==g&&(f=k.year,g=k.month),e=new Date(f,parseInt(g)+parseInt(d&&d.nav?d.nav:0),1),f=e.getFullYear(),g=e.getMonth();new Date(f,g,h).getMonth()!==g;)h-=1;c=[f,g,h]}return c},c.prototype.normalize=function(a){return a.setHours(0,0,0,0),a},c.prototype.measure=function(a,b){var c=this;return b?f.isInteger(b)&&(b=c.now(a,b,{rel:b})):b="min"==a?-(1/0):1/0,b},c.prototype.viewset=function(a,b){return this.create([b.year,b.month,1])},c.prototype.validate=function(a,c,d){var e,g,h,i,j=this,k=c,l=d&&d.interval?d.interval:1,m=-1===j.item.enable,n=j.item.min,o=j.item.max,p=m&&j.item.disable.filter(function(a){if(b.isArray(a)){var d=j.create(a).pick;dc.pick&&(g=!0)}return f.isInteger(a)}).length;if((!d||!d.nav)&&(!m&&j.disabled(c)||m&&j.disabled(c)&&(p||e||g)||!m&&(c.pick<=n.pick||c.pick>=o.pick)))for(m&&!p&&(!g&&l>0||!e&&0>l)&&(l*=-1);j.disabled(c)&&(Math.abs(l)>1&&(c.monthk.month)&&(c=k,l=l>0?1:-1),c.pick<=n.pick?(h=!0,l=1,c=j.create([n.year,n.month,n.date-1])):c.pick>=o.pick&&(i=!0,l=-1,c=j.create([o.year,o.month,o.date+1])),!h||!i);)c=j.create([c.year,c.month,c.date+l]);return c},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return f.isInteger(d)?a.day===(c.settings.firstDay?d:d-1)%7:b.isArray(d)||f.isDate(d)?a.pick===c.create(d).pick:b.isObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[3]||b.isObject(a)&&a.inverted}).length,-1===c.item.enable?!d:d||a.pickc.item.max.pick},c.prototype.parse=function(a,c,d){var e,g=this,h={};return!c||f.isInteger(c)||b.isArray(c)||f.isDate(c)||b.isObject(c)&&f.isInteger(c.pick)?c:(d&&d.format||(d=d||{},d.format=g.settings.format),e="string"!=typeof c||d.fromValue?0:1,g.formats.toArray(d.format).map(function(a){var b=g.formats[a],d=b?f.trigger(b,g,[c,h]):a.replace(/^!/,"").length;b&&(h[a]=c.substr(0,d)),c=c.substr(d)}),[h.yyyy||h.yy,+(h.mm||h.m)-e,h.dd||h.d])},c.prototype.formats=function(){function a(a,b,c){var d=a.match(/\w+/)[0];return c.mm||c.m||(c.m=b.indexOf(d)),d.length}function b(a){return a.match(/\w+/)[0].length}return{d:function(a,b){return a?f.digits(a):b.date},dd:function(a,b){return a?2:f.lead(b.date)},ddd:function(a,c){return a?b(a):this.settings.weekdaysShort[c.day]},dddd:function(a,c){return a?b(a):this.settings.weekdaysFull[c.day]},m:function(a,b){return a?f.digits(a):b.month+1},mm:function(a,b){return a?2:f.lead(b.month+1)},mmm:function(b,c){var d=this.settings.monthsShort;return b?a(b,d,c):d[c.month]},mmmm:function(b,c){var d=this.settings.monthsFull;return b?a(b,d,c):d[c.month]},yy:function(a,b){return a?2:(""+b.year).slice(2)},yyyy:function(a,b){return a?4:b.year},toArray:function(a){return a.split(/(d{1,4}|m{1,4}|y{4}|yy|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return f.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}}}(),c.prototype.isDateExact=function(a,c){var d=this;return f.isInteger(a)&&f.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(f.isDate(a)||b.isArray(a))&&(f.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:b.isObject(a)&&b.isObject(c)?d.isDateExact(a.from,c.from)&&d.isDateExact(a.to,c.to):!1},c.prototype.isDateOverlap=function(a,c){var d=this;return f.isInteger(a)&&(f.isDate(c)||b.isArray(c))?a===d.create(c).day+1:f.isInteger(c)&&(f.isDate(a)||b.isArray(a))?c===d.create(a).day+1:b.isObject(a)&&b.isObject(c)?d.overlapRanges(a,c):!1},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(-1==b.enable?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,g=0;gi;i+=1){if(h=e[i],d.isDateExact(h,a)){c=e[i]=null,j=!0;break}if(d.isDateOverlap(h,a)){b.isObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[3]||c.push("inverted")):f.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;g>i;i+=1)if(d.isDateExact(e[i],a)){e[i]=null;break}if(j)for(i=0;g>i;i+=1)if(d.isDateOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.nodes=function(a){var b=this,c=b.settings,g=b.item,h=g.now,i=g.select,j=g.highlight,k=g.view,l=g.disable,m=g.min,n=g.max,o=function(a){return c.firstDay&&a.push(a.shift()),f.node("thead",f.node("tr",f.group({min:0,max:d-1,i:1,node:"th",item:function(b){return[a[b],c.klass.weekdays]}})))}((c.showWeekdaysFull?c.weekdaysFull:c.weekdaysShort).slice(0)),p=function(a){return f.node("div"," ",c.klass["nav"+(a?"Next":"Prev")]+(a&&k.year>=n.year&&k.month>=n.month||!a&&k.year<=m.year&&k.month<=m.month?" "+c.klass.navDisabled:""),"data-nav="+(a||-1))},q=function(b){return c.selectMonths?f.node("select",f.group({min:0,max:11,i:1,node:"option",item:function(a){return[b[a],0,"value="+a+(k.month==a?" selected":"")+(k.year==m.year&&an.month?" disabled":"")]}}),c.klass.selectMonth,a?"":"disabled"):f.node("div",b[k.month],c.klass.month)},r=function(){var b=k.year,d=c.selectYears===!0?5:~~(c.selectYears/2);if(d){var e=m.year,g=n.year,h=b-d,i=b+d;if(e>h&&(i+=e-h,h=e),i>g){var j=h-e,l=i-g;h-=j>l?l:j,i=g}return f.node("select",f.group({min:h,max:i,i:1,node:"option",item:function(a){return[a,0,"value="+a+(b==a?" selected":"")]}}),c.klass.selectYear,a?"":"disabled")}return f.node("div",b,c.klass.year)};return f.node("div",p()+p(1)+q(c.showMonthsShort?c.monthsShort:c.monthsFull)+r(),c.klass.header)+f.node("table",o+f.node("tbody",f.group({min:0,max:e-1,i:1,node:"tr",item:function(a){var e=c.firstDay&&0===b.create([k.year,k.month,1]).day?-7:0;return[f.group({min:d*a-k.day+e+1,max:function(){return this.min+d-1},i:1,node:"td",item:function(a){a=b.create([k.year,k.month,a+(c.firstDay?1:0)]);var d=i&&i.pick==a.pick,e=j&&j.pick==a.pick,g=l&&b.disabled(a)||a.pickn.pick;return[f.node("div",a.date,function(b){return b.push(k.month==a.month?c.klass.infocus:c.klass.outfocus),h.pick==a.pick&&b.push(c.klass.now),d&&b.push(c.klass.selected),e&&b.push(c.klass.highlighted),g&&b.push(c.klass.disabled),b.join(" ")}([c.klass.day]),"data-pick="+a.pick+" "+f.ariaAttr({role:"button",controls:b.$node[0].id,checked:d&&b.$node[0].value===f.trigger(b.formats.toString,b,[c.format,a])?!0:null,activedescendant:e?!0:null,disabled:g?!0:null}))]}})]}})),c.klass.table)+f.node("div",f.node("button",c.today,c.klass.buttonToday,"type=button data-pick="+h.pick+(a?"":" disabled"))+f.node("button",c.clear,c.klass.buttonClear,"type=button data-clear=1"+(a?"":" disabled"))+f.node("button",c.close,c.klass.buttonClose,"type=button data-close=true "+(a?"":" disabled")),c.klass.footer)},c.defaults=function(a){return{monthsFull:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],weekdaysFull:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],today:"Today",clear:"Clear",close:"Close",format:"d mmmm, yyyy",klass:{table:a+"table",header:a+"header",navPrev:a+"nav--prev",navNext:a+"nav--next",navDisabled:a+"nav--disabled",month:a+"month",year:a+"year",selectMonth:a+"select--month",selectYear:a+"select--year",weekdays:a+"weekday",day:a+"day",disabled:a+"day--disabled",selected:a+"day--selected",highlighted:a+"day--highlighted",now:a+"day--today",infocus:a+"day--infocus",outfocus:a+"day--outfocus",footer:a+"footer",buttonClear:a+"button--clear",buttonClose:a+"button--close",buttonToday:a+"button--today"}}}(a.klasses().picker+"__"),a.extend("pickadate",c)}); 13 | /*! 14 | * Time picker for pickadate.js v3.4.0 15 | * http://amsul.github.io/pickadate.js/time.htm 16 | */ 17 | !function(a){"function"==typeof define&&define.amd?define(["picker","angular"],a):a(Picker,angular)}(function(a,b){function c(a,b){var c=this,d=a.$node[0].value,e=a.$node.data("value"),f=e||d,g=e?b.formatSubmit:b.format;c.settings=b,c.$node=a.$node,c.queue={interval:"i",min:"measure create",max:"measure create",now:"now create",select:"parse create validate",highlight:"parse create validate",view:"parse create validate",disable:"deactivate",enable:"activate"},c.item={},c.item.interval=b.interval||30,c.item.disable=(b.disable||[]).slice(0),c.item.enable=-function(a){return a[0]===!0?a.shift():-1}(c.item.disable),c.set("min",b.min).set("max",b.max).set("now"),f?c.set("select",f,{format:g,fromValue:!!d}):c.set("select",null).set("highlight",c.item.now),c.key={40:1,38:-1,39:1,37:-1,go:function(a){c.set("highlight",c.item.highlight.pick+a*c.item.interval,{interval:a*c.item.interval}),this.render()}},a.on("render",function(){var c=a.$root.children(),d=c.find("."+b.klass.viewset);d.length&&(c[0].scrollTop=~~d.position().top-2*d[0].clientHeight)}).on("open",function(){a.$root.find("button").attr("disable",!1)}).on("close",function(){a.$root.find("button").attr("disable",!0)})}var d=24,e=60,f=12,g=d*e,h=a._;c.prototype.set=function(a,b,c){var d=this,e=d.item;return null===b?(e[a]=b,d):(e["enable"==a?"disable":"flip"==a?"enable":a]=d.queue[a].split(" ").map(function(e){return b=d[e](a,b,c)}).pop(),"select"==a?d.set("highlight",e.select,c):"highlight"==a?d.set("view",e.highlight,c):"interval"==a?d.set("min",e.min,c).set("max",e.max,c):a.match(/^(flip|min|max|disable|enable)$/)&&("min"==a&&d.set("max",e.max,c),e.select&&d.disabled(e.select)&&d.set("select",e.select,c),e.highlight&&d.disabled(e.highlight)&&d.set("highlight",e.highlight,c)),d)},c.prototype.get=function(a){return this.item[a]},c.prototype.create=function(a,c,f){var i=this;return c=void 0===c?a:c,h.isDate(c)&&(c=[c.getHours(),c.getMinutes()]),b.isObject(c)&&h.isInteger(c.pick)?c=c.pick:b.isArray(c)?c=+c[0]*e+ +c[1]:h.isInteger(c)||(c=i.now(a,c,f)),"max"==a&&c=a.from.pick&&b.pick<=a.to.pick},c.prototype.overlapRanges=function(a,b){var c=this;return a=c.createRange(a.from,a.to),b=c.createRange(b.from,b.to),c.withinRange(a,b.from)||c.withinRange(a,b.to)||c.withinRange(b,a.from)||c.withinRange(b,a.to)},c.prototype.now=function(a,b){var c,d=this.item.interval,f=new Date,g=f.getHours()*e+f.getMinutes(),i=h.isInteger(b);return g-=g%d,c=0>b&&-d>=d*b+g,g+="min"==a&&c?0:d,i&&(g+=d*(c&&"max"!=a?b+1:b)),g},c.prototype.normalize=function(a,b){var c=this.item.interval,d=this.item.min&&this.item.min.pick||0;return b-="min"==a?0:(b-d)%c},c.prototype.measure=function(a,c,f){var g=this;return c?c===!0||h.isInteger(c)?c=g.now(a,c,f):b.isObject(c)&&h.isInteger(c.pick)&&(c=g.normalize(a,c.pick,f)):c="min"==a?[0,0]:[d-1,e-1],c},c.prototype.validate=function(a,b,c){var d=this,e=c&&c.interval?c.interval:d.item.interval;return d.disabled(b)&&(b=d.shift(b,e)),b=d.scope(b),d.disabled(b)&&(b=d.shift(b,-1*e)),b},c.prototype.disabled=function(a){var c=this,d=c.item.disable.filter(function(d){return h.isInteger(d)?a.hour==d:b.isArray(d)||h.isDate(d)?a.pick==c.create(d).pick:b.isObject(d)?c.withinRange(d,a):void 0});return d=d.length&&!d.filter(function(a){return b.isArray(a)&&"inverted"==a[2]||b.isObject(a)&&a.inverted}).length,-1===c.item.enable?!d:d||a.pickc.item.max.pick},c.prototype.shift=function(a,b){var c=this,d=c.item.min.pick,e=c.item.max.pick;for(b=b||c.item.interval;c.disabled(a)&&(a=c.create(a.pick+=b),!(a.pick<=d||a.pick>=e)););return a},c.prototype.scope=function(a){var b=this.item.min.pick,c=this.item.max.pick;return this.create(a.pick>c?c:a.pickb.time%g?"a.m.":"p.m."},A:function(a,b){return a?2:g/2>b.time%g?"AM":"PM"},toArray:function(a){return a.split(/(h{1,2}|H{1,2}|i|a|A|!.)/g)},toString:function(a,b){var c=this;return c.formats.toArray(a).map(function(a){return h.trigger(c.formats[a],c,[0,b])||a.replace(/^!/,"")}).join("")}},c.prototype.isTimeExact=function(a,c){var d=this;return h.isInteger(a)&&h.isInteger(c)||"boolean"==typeof a&&"boolean"==typeof c?a===c:(h.isDate(a)||b.isArray(a))&&(h.isDate(c)||b.isArray(c))?d.create(a).pick===d.create(c).pick:b.isObject(a)&&b.isObject(c)?d.isTimeExact(a.from,c.from)&&d.isTimeExact(a.to,c.to):!1},c.prototype.isTimeOverlap=function(a,c){var d=this;return h.isInteger(a)&&(h.isDate(c)||b.isArray(c))?a===d.create(c).hour:h.isInteger(c)&&(h.isDate(a)||b.isArray(a))?c===d.create(a).hour:b.isObject(a)&&b.isObject(c)?d.overlapRanges(a,c):!1},c.prototype.flipEnable=function(a){var b=this.item;b.enable=a||(-1==b.enable?1:-1)},c.prototype.deactivate=function(a,c){var d=this,e=d.item.disable.slice(0);return"flip"==c?d.flipEnable():c===!1?(d.flipEnable(1),e=[]):c===!0?(d.flipEnable(-1),e=[]):c.map(function(a){for(var c,f=0;fi;i+=1){if(g=e[i],d.isTimeExact(g,a)){c=e[i]=null,j=!0;break}if(d.isTimeOverlap(g,a)){b.isObject(a)?(a.inverted=!0,c=a):b.isArray(a)?(c=a,c[2]||c.push("inverted")):h.isDate(a)&&(c=[a.getFullYear(),a.getMonth(),a.getDate(),"inverted"]);break}}if(c)for(i=0;f>i;i+=1)if(d.isTimeExact(e[i],a)){e[i]=null;break}if(j)for(i=0;f>i;i+=1)if(d.isTimeOverlap(e[i],a)){e[i]=null;break}c&&e.push(c)}),e.filter(function(a){return null!=a})},c.prototype.i=function(a,b){return h.isInteger(b)&&b>0?b:this.item.interval},c.prototype.nodes=function(a){var b=this,c=b.settings,d=b.item.select,e=b.item.highlight,f=b.item.view,g=b.item.disable;return h.node("ul",h.group({min:b.item.min.pick,max:b.item.max.pick,i:b.item.interval,node:"li",item:function(a){a=b.create(a);var i=a.pick,j=d&&d.pick==i,k=e&&e.pick==i,l=g&&b.disabled(a);return[h.trigger(b.formats.toString,b,[h.trigger(c.formatLabel,b,[a])||c.format,a]),function(a){return j&&a.push(c.klass.selected),k&&a.push(c.klass.highlighted),f&&f.pick==i&&a.push(c.klass.viewset),l&&a.push(c.klass.disabled),a.join(" ")}([c.klass.listItem]),"data-pick="+a.pick+" "+h.ariaAttr({role:"button",controls:b.$node[0].id,checked:j&&b.$node.val()===h.trigger(b.formats.toString,b,[c.format,a])?!0:null,activedescendant:k?!0:null,disabled:l?!0:null})]}})+h.node("li",h.node("button",c.clear,c.klass.buttonClear,"type=button data-clear=1"+(a?"":" disable"))),c.klass.list)},c.defaults=function(a){return{clear:"Clear",format:"h:i A",interval:30,klass:{picker:a+" "+a+"--time",holder:a+"__holder",list:a+"__list",listItem:a+"__list-item",disabled:a+"__list-item--disabled",selected:a+"__list-item--selected",highlighted:a+"__list-item--highlighted",viewset:a+"__list-item--viewset",now:a+"__list-item--now",buttonClear:a+"__button--clear"}}}(a.klasses().picker),a.extend("pickatime",c)}); 18 | /*! 19 | * Legacy browser support 20 | */ 21 | [].map||(Array.prototype.map=function(a,b){for(var c=this,d=c.length,e=new Array(d),f=0;d>f;f++)f in c&&(e[f]=a.call(b,c[f],f,c));return e}),[].filter||(Array.prototype.filter=function(a){if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if("function"!=typeof a)throw new TypeError;for(var d=[],e=arguments[1],f=0;c>f;f++)if(f in b){var g=b[f];a.call(e,g,f,b)&&d.push(g)}return d}),[].indexOf||(Array.prototype.indexOf=function(a){if(null==this)throw new TypeError;var b=Object(this),c=b.length>>>0;if(0===c)return-1;var d=0;if(arguments.length>1&&(d=Number(arguments[1]),d!=d?d=0:0!==d&&d!=1/0&&d!=-(1/0)&&(d=(d>0||-1)*Math.floor(Math.abs(d)))),d>=c)return-1;for(var e=d>=0?d:Math.max(c-Math.abs(d),0);c>e;e++)if(e in b&&b[e]===a)return e;return-1});/*! 22 | * Cross-Browser Split 1.1.1 23 | * Copyright 2007-2012 Steven Levithan 24 | * Available under the MIT License 25 | * http://blog.stevenlevithan.com/archives/cross-browser-split 26 | */ 27 | var nativeSplit=String.prototype.split,compliantExecNpcg=void 0===/()??/.exec("")[1];String.prototype.split=function(a,b){var c=this;if("[object RegExp]"!==Object.prototype.toString.call(a))return nativeSplit.call(c,a,b);var d,e,f,g,h=[],i=(a.ignoreCase?"i":"")+(a.multiline?"m":"")+(a.extended?"x":"")+(a.sticky?"y":""),j=0;for(a=new RegExp(a.source,i+"g"),c+="",compliantExecNpcg||(d=new RegExp("^"+a.source+"$(?!\\s)",i)),b=void 0===b?-1>>>0:b>>>0;(e=a.exec(c))&&(f=e.index+e[0].length,!(f>j&&(h.push(c.slice(j,e.index)),!compliantExecNpcg&&e.length>1&&e[0].replace(d,function(){for(var a=1;a1&&e.index=b)));)a.lastIndex===e.index&&a.lastIndex++;return j===c.length?(g||!a.test(""))&&h.push(""):h.push(c.slice(j)),h.length>b?h.slice(0,b):h}; 28 | angular.module("angular-datepicker",[]).directive("pickADate",function(){return{restrict:"A",scope:{pickADate:"=",pickADateOptions:"="},link:function(a,b,c){function d(){function c(c){if("function"==typeof g&&g.apply(this,arguments),!a.$$phase&&!a.$root.$$phase){var d=b.pickadate("picker").get("select");d&&a.$apply(function(){return c.hasOwnProperty("clear")?void(a.pickADate=null):(a.pickADate&&"string"!=typeof a.pickADate||(a.pickADate=new Date(0)),a.pickADate.setYear(d.obj.getFullYear()),a.pickADate.setMonth(d.obj.getMonth()),void a.pickADate.setDate(d.obj.getDate()))})}}function d(c){"function"==typeof h&&h.apply(this,arguments);var d=a.pickADate?a.pickADate:b.val();b.pickadate("picker").set("select",d,{format:f.format})}function e(a){if("function"==typeof i&&i.apply(this,arguments),"undefined"!=typeof cordova&&cordova.plugins&&cordova.plugins.Keyboard){var b=function(){cordova.plugins.Keyboard.close(),window.removeEventListener("native.keyboardshow",this)};window.addEventListener("native.keyboardshow",b),setTimeout(function(){window.removeEventListener("native.keyboardshow",b)},500)}}f=a.pickADateOptions?angular.copy(a.pickADateOptions):{};var g=f.onSet,h=f.onOpen,i=f.onClose;f=angular.extend(f,{onOpen:d,onSet:c,onClose:e,container:document.body}),b.pickadate(f),a.pickADateOptions.container=document.body}function e(){d(),b.pickadate("picker")&&(b.pickadate("picker").changeSettings(f),a.pickADate&&b.pickadate("picker").set("select",a.pickADate))}var f={},g="";for(var h in a.pickADateOptions)"container"!=h&&(g+=g?" + pickADateOptions."+h:"pickADateOptions."+h);a.$watch(g,e)}}}).directive("pickATime",function(){return{restrict:"A",scope:{pickATime:"=",pickATimeOptions:"="},link:function(a,b,c){function d(){function c(c){if("function"==typeof g&&g.apply(this,arguments),!a.$$phase&&!a.$root.$$phase){var d=b.pickatime("picker").get("select");d&&a.$apply(function(){return c.hasOwnProperty("clear")?void(a.pickATime=null):(a.pickATime&&"string"!=typeof a.pickATime||(a.pickATime=new Date),a.pickATime.setHours(d.hour),a.pickATime.setMinutes(d.mins),a.pickATime.setSeconds(0),void a.pickATime.setMilliseconds(0))})}}function d(c){"function"==typeof h&&h.apply(this,arguments);var d=a.pickATime?a.pickATime:b.val();b.pickatime("picker").set("select",d,{format:f.format})}function e(a){if("function"==typeof i&&i.apply(this,arguments),"undefined"!=typeof cordova&&cordova.plugins&&cordova.plugins.Keyboard){var b=function(){cordova.plugins.Keyboard.close(),window.removeEventListener("native.keyboardshow",this)};window.addEventListener("native.keyboardshow",b),setTimeout(function(){window.removeEventListener("native.keyboardshow",b)},500)}}f=a.pickATimeOptions?angular.copy(a.pickATimeOptions):{};var g=f.onSet,h=f.onOpen,i=f.onClose;b.pickatime(angular.extend(f,{onOpen:d,onSet:c,onClose:e,container:document.body})),setTimeout(function(){a.pickATime&&b.pickatime("picker").set("select",a.pickATime)},0)}function e(){d(),b.pickatime("picker")&&(b.pickatime("picker").changeSettings(f),a.pickATime&&b.pickatime("picker").set("select",a.pickATime))}var f={},g="";for(var h in a.pickATimeOptions)"container"!=h&&(g+=g?" + pickATimeOptions."+h:"pickATimeOptions."+h);a.$watch(g,e)}}}); -------------------------------------------------------------------------------- /build/themes/classic.css: -------------------------------------------------------------------------------- 1 | .picker{font-size:16px;text-align:left;line-height:1.2;color:#000;position:absolute;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}/*! 2 | * Classic picker styling for pickadate.js 3 | * Demo: http://amsul.github.io/pickadate.js 4 | */.picker{width:100%}.picker__holder{position:absolute;background:#fff;border:1px solid #aaa;border-top-width:0;border-bottom-width:0;-webkit-border-radius:0 0 5px 5px;-moz-border-radius:0 0 5px 5px;border-radius:0 0 5px 5px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;min-width:176px;max-width:466px;max-height:0;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;opacity:0;-webkit-transform:translateY(-1em) perspective(600px) rotateX(10deg);-moz-transform:translateY(-1em) perspective(600px) rotateX(10deg);transform:translateY(-1em) perspective(600px) rotateX(10deg);-webkit-transition:all .15s ease-out,max-height 0 .15s,border-width 0 .15s;-moz-transition:all .15s ease-out,max-height 0 .15s,border-width 0 .15s;transition:all .15s ease-out,max-height 0 .15s,border-width 0 .15s}.picker__frame{padding:1px}.picker__wrap{margin:-1px}.picker--opened .picker__holder{max-height:25em;-ms-filter:"alpha(Opacity=100)";filter:alpha(opacity=100);-moz-opacity:1;opacity:1;border-top-width:1px;border-bottom-width:1px;-webkit-transform:translateY(0) perspective(600px) rotateX(0);-moz-transform:translateY(0) perspective(600px) rotateX(0);transform:translateY(0) perspective(600px) rotateX(0);-webkit-transition:all .15s ease-out,max-height 0,border-width 0;-moz-transition:all .15s ease-out,max-height 0,border-width 0;transition:all .15s ease-out,max-height 0,border-width 0;-webkit-box-shadow:0 6px 18px 1px rgba(0,0,0,.12);-moz-box-shadow:0 6px 18px 1px rgba(0,0,0,.12);box-shadow:0 6px 18px 1px rgba(0,0,0,.12)} -------------------------------------------------------------------------------- /build/themes/classic.date.css: -------------------------------------------------------------------------------- 1 | .picker__box{padding:0 1em}.picker__header{text-align:center;position:relative;margin-top:.75em}.picker__month,.picker__year{font-weight:500;display:inline-block;margin-left:.25em;margin-right:.25em}.picker__year{color:#999;font-size:.8em;font-style:italic}.picker__select--month,.picker__select--year{border:1px solid #b7b7b7;height:2.5em;padding:.5em .25em;margin-left:.25em;margin-right:.25em;font-size:.6em}@media (min-width:24.5em){.picker__select--month,.picker__select--year{font-size:.8em;margin-top:-.5em}}.picker__select--month{width:35%}.picker__select--year{width:22.5%}.picker__select--month:focus,.picker__select--year:focus{border-color:#0089ec}.picker__nav--next,.picker__nav--prev{position:absolute;padding:.5em 1.25em;width:1em;height:1em;top:-.25em}@media (min-width:24.5em){.picker__nav--next,.picker__nav--prev{top:-.33em}}.picker__nav--prev{left:-1em;padding-right:1.25em}@media (min-width:24.5em){.picker__nav--prev{padding-right:1.5em}}.picker__nav--next{right:-1em;padding-left:1.25em}@media (min-width:24.5em){.picker__nav--next{padding-left:1.5em}}.picker__nav--next:before,.picker__nav--prev:before{content:" ";border-top:.5em solid transparent;border-bottom:.5em solid transparent;border-right:.75em solid #000;width:0;height:0;display:block;margin:0 auto}.picker__nav--next:before{border-right:0;border-left:.75em solid #000}.picker__nav--next:hover,.picker__nav--prev:hover{cursor:pointer;color:#000;background:#b1dcfb}.picker__nav--disabled,.picker__nav--disabled:before,.picker__nav--disabled:before:hover,.picker__nav--disabled:hover{cursor:default;background:0 0;border-right-color:#f5f5f5;border-left-color:#f5f5f5}.picker__table{text-align:center;border-collapse:collapse;border-spacing:0;table-layout:fixed;font-size:inherit;width:100%;margin-top:.75em;margin-bottom:.5em}@media (min-height:33.875em){.picker__table{margin-bottom:.75em}}.picker__table td{margin:0;padding:0}.picker__weekday{width:14.285714286%;font-size:.75em;padding-bottom:.25em;color:#999;font-weight:500}@media (min-height:33.875em){.picker__weekday{padding-bottom:.5em}}.picker__day{padding:.3125em 0;font-weight:200;border:1px solid transparent}.picker__day--today{color:#0089ec;position:relative}.picker__day--today:before{content:" ";position:absolute;top:2px;right:2px;width:0;height:0;border-top:.5em solid #0059bc;border-left:.5em solid transparent}.picker__day--selected,.picker__day--selected:hover{border-color:#0089ec}.picker__day--highlighted{background:#b1dcfb}.picker__day--disabled:before{border-top-color:#aaa}.picker__day--outfocus{color:#ddd}.picker__day--infocus:hover,.picker__day--outfocus:hover{cursor:pointer;color:#000;background:#b1dcfb}.picker--focused .picker__day--highlighted,.picker__day--highlighted:hover{background:#0089ec;color:#fff}.picker__day--disabled,.picker__day--disabled:hover{background:#f5f5f5;border-color:#f5f5f5;color:#ddd;cursor:default}.picker__day--highlighted.picker__day--disabled,.picker__day--highlighted.picker__day--disabled:hover{background:#bbb}.picker__footer{text-align:center}.picker__button--clear,.picker__button--close,.picker__button--today{border:1px solid #fff;background:#fff;font-size:.8em;padding:.66em 0;font-weight:700;width:33%;display:inline-block;vertical-align:bottom}.picker__button--clear:hover,.picker__button--close:hover,.picker__button--today:hover{cursor:pointer;color:#000;background:#b1dcfb;border-bottom-color:#b1dcfb}.picker__button--clear:focus,.picker__button--close:focus,.picker__button--today:focus{background:#b1dcfb;border-color:#0089ec;outline:0}.picker__button--clear:before,.picker__button--close:before,.picker__button--today:before{position:relative;display:inline-block;height:0}.picker__button--today:before{content:" ";margin-right:.45em;top:-.05em;width:0;border-top:.66em solid #0059bc;border-left:.66em solid transparent}.picker__button--clear:before{content:"\D7";margin-right:.35em;top:-.1em;color:#e20;vertical-align:top;font-size:1.1em}.picker__button--close:before{content:"\D7";top:-.1em;vertical-align:top;font-size:1.1em;margin-right:.35em;color:#777} -------------------------------------------------------------------------------- /build/themes/classic.time.css: -------------------------------------------------------------------------------- 1 | .picker__list{list-style:none;padding:.75em 0 4.2em;margin:0}.picker__list-item{border-bottom:1px solid #ddd;border-top:1px solid #ddd;margin-bottom:-1px;position:relative;background:#fff;padding:.75em 1.25em}@media (min-height:46.75em){.picker__list-item{padding:.5em 1em}}.picker__list-item:hover{cursor:pointer;color:#000;background:#b1dcfb;border-color:#0089ec;z-index:10}.picker__list-item--selected,.picker__list-item--selected:hover{border-color:#0089ec;z-index:10}.picker__list-item--highlighted{background:#b1dcfb}.picker--focused .picker__list-item--highlighted,.picker__list-item--highlighted:hover{background:#0089ec;color:#fff}.picker--focused .picker__list-item--disabled,.picker__list-item--disabled,.picker__list-item--disabled:hover{background:#f5f5f5;color:#ddd;cursor:default;border-color:#ddd;z-index:auto}.picker--time .picker__button--clear{display:block;width:80%;margin:1em auto 0;padding:1em 1.25em;background:0 0;border:0;font-weight:500;font-size:.67em;text-align:center;text-transform:uppercase;color:#666}.picker--time .picker__button--clear:focus,.picker--time .picker__button--clear:hover{background:#b1dcfb;background:#e20;border-color:#e20;cursor:pointer;color:#fff;outline:0}.picker--time .picker__button--clear:before{top:-.25em;color:#666;font-size:1.25em;font-weight:700}.picker--time .picker__button--clear:focus:before,.picker--time .picker__button--clear:hover:before{color:#fff}.picker--time{min-width:256px;max-width:320px}.picker--time .picker__holder{background:#f2f2f2}@media (min-height:40.125em){.picker--time .picker__holder{font-size:.875em}}.picker--time .picker__box{padding:0;position:relative} -------------------------------------------------------------------------------- /build/themes/default.css: -------------------------------------------------------------------------------- 1 | .picker{font-size:16px;text-align:left;line-height:1.2;color:#000;position:absolute;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}/*! 2 | * Default mobile-first, responsive styling for pickadate.js 3 | * Demo: http://amsul.github.io/pickadate.js 4 | */.picker__frame,.picker__holder{bottom:0;left:0;right:0;top:100%}.picker__holder{position:fixed;-webkit-transition:background .15s ease-out,top 0s .15s;-moz-transition:background .15s ease-out,top 0s .15s;transition:background .15s ease-out,top 0s .15s}.picker__frame{position:absolute;margin:0 auto;min-width:256px;max-width:666px;width:100%;-ms-filter:"alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;opacity:0;-webkit-transition:all .15s ease-out;-moz-transition:all .15s ease-out;transition:all .15s ease-out}@media (min-height:33.875em){.picker__frame{overflow:visible;top:auto;bottom:-100%;max-height:80%}}@media (min-height:40.125em){.picker__frame{margin-bottom:7.5%}}.picker__wrap{display:table;width:100%;height:100%}@media (min-height:33.875em){.picker__wrap{display:block}}.picker__box{background:#fff;display:table-cell;vertical-align:middle}@media (min-height:26.5em){.picker__box{font-size:1.25em}}@media (min-height:33.875em){.picker__box{display:block;font-size:1.33em;border:1px solid #777;border-top-color:#898989;border-bottom-width:0;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0;-webkit-box-shadow:0 12px 36px 16px rgba(0,0,0,.24);-moz-box-shadow:0 12px 36px 16px rgba(0,0,0,.24);box-shadow:0 12px 36px 16px rgba(0,0,0,.24)}}@media (min-height:40.125em){.picker__box{font-size:1.5em;border-bottom-width:1px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}}.picker--opened .picker__holder{top:0;zoom:1;background:rgba(0,0,0,.32);-webkit-transition:background .15s ease-out;-moz-transition:background .15s ease-out;transition:background .15s ease-out}.picker--opened .picker__frame{top:0;-ms-filter:"alpha(Opacity=100)";filter:alpha(opacity=100);-moz-opacity:1;opacity:1}@media (min-height:33.875em){.picker--opened .picker__frame{top:auto;bottom:0}} -------------------------------------------------------------------------------- /build/themes/default.date.css: -------------------------------------------------------------------------------- 1 | .picker__box{padding:0 1em}.picker__header{text-align:center;position:relative;margin-top:.75em}.picker__month,.picker__year{font-weight:500;display:inline-block;margin-left:.25em;margin-right:.25em}.picker__year{color:#999;font-size:.8em;font-style:italic}.picker__select--month,.picker__select--year{border:1px solid #b7b7b7;height:2.5em;padding:.5em .25em;margin-left:.25em;margin-right:.25em;font-size:.6em}@media (min-width:24.5em){.picker__select--month,.picker__select--year{font-size:.8em;margin-top:-.5em}}.picker__select--month{width:35%}.picker__select--year{width:22.5%}.picker__select--month:focus,.picker__select--year:focus{border-color:#0089ec}.picker__nav--next,.picker__nav--prev{position:absolute;padding:.5em 1.25em;width:1em;height:1em;top:-.25em}@media (min-width:24.5em){.picker__nav--next,.picker__nav--prev{top:-.33em}}.picker__nav--prev{left:-1em;padding-right:1.25em}@media (min-width:24.5em){.picker__nav--prev{padding-right:1.5em}}.picker__nav--next{right:-1em;padding-left:1.25em}@media (min-width:24.5em){.picker__nav--next{padding-left:1.5em}}.picker__nav--next:before,.picker__nav--prev:before{content:" ";border-top:.5em solid transparent;border-bottom:.5em solid transparent;border-right:.75em solid #000;width:0;height:0;display:block;margin:0 auto}.picker__nav--next:before{border-right:0;border-left:.75em solid #000}.picker__nav--next:hover,.picker__nav--prev:hover{cursor:pointer;color:#000;background:#b1dcfb}.picker__nav--disabled,.picker__nav--disabled:before,.picker__nav--disabled:before:hover,.picker__nav--disabled:hover{cursor:default;background:0 0;border-right-color:#f5f5f5;border-left-color:#f5f5f5}.picker__table{text-align:center;border-collapse:collapse;border-spacing:0;table-layout:fixed;font-size:inherit;width:100%;margin-top:.75em;margin-bottom:.5em}@media (min-height:33.875em){.picker__table{margin-bottom:.75em}}.picker__table td{margin:0;padding:0}.picker__weekday{width:14.285714286%;font-size:.75em;padding-bottom:.25em;color:#999;font-weight:500}@media (min-height:33.875em){.picker__weekday{padding-bottom:.5em}}.picker__day{padding:.3125em 0;font-weight:200;border:1px solid transparent}.picker__day--today{color:#0089ec;position:relative}.picker__day--today:before{content:" ";position:absolute;top:2px;right:2px;width:0;height:0;border-top:.5em solid #0059bc;border-left:.5em solid transparent}.picker__day--selected,.picker__day--selected:hover{border-color:#0089ec}.picker__day--highlighted{background:#b1dcfb}.picker__day--disabled:before{border-top-color:#aaa}.picker__day--outfocus{color:#ddd}.picker__day--infocus:hover,.picker__day--outfocus:hover{cursor:pointer;color:#000;background:#b1dcfb}.picker--focused .picker__day--highlighted,.picker__day--highlighted:hover{background:#0089ec;color:#fff}.picker__day--disabled,.picker__day--disabled:hover{background:#f5f5f5;border-color:#f5f5f5;color:#ddd;cursor:default}.picker__day--highlighted.picker__day--disabled,.picker__day--highlighted.picker__day--disabled:hover{background:#bbb}.picker__footer{text-align:center}.picker__button--clear,.picker__button--close,.picker__button--today{border:1px solid #fff;background:#fff;font-size:.8em;padding:.66em 0;font-weight:700;width:33%;display:inline-block;vertical-align:bottom}.picker__button--clear:hover,.picker__button--close:hover,.picker__button--today:hover{cursor:pointer;color:#000;background:#b1dcfb;border-bottom-color:#b1dcfb}.picker__button--clear:focus,.picker__button--close:focus,.picker__button--today:focus{background:#b1dcfb;border-color:#0089ec;outline:0}.picker__button--clear:before,.picker__button--close:before,.picker__button--today:before{position:relative;display:inline-block;height:0}.picker__button--today:before{content:" ";margin-right:.45em;top:-.05em;width:0;border-top:.66em solid #0059bc;border-left:.66em solid transparent}.picker__button--clear:before{content:"\D7";margin-right:.35em;top:-.1em;color:#e20;vertical-align:top;font-size:1.1em}.picker__button--close:before{content:"\D7";top:-.1em;vertical-align:top;font-size:1.1em;margin-right:.35em;color:#777} -------------------------------------------------------------------------------- /build/themes/default.time.css: -------------------------------------------------------------------------------- 1 | .picker__list{list-style:none;padding:.75em 0 4.2em;margin:0}.picker__list-item{border-bottom:1px solid #ddd;border-top:1px solid #ddd;margin-bottom:-1px;position:relative;background:#fff;padding:.75em 1.25em}@media (min-height:46.75em){.picker__list-item{padding:.5em 1em}}.picker__list-item:hover{cursor:pointer;color:#000;background:#b1dcfb;border-color:#0089ec;z-index:10}.picker__list-item--selected,.picker__list-item--selected:hover{border-color:#0089ec;z-index:10}.picker__list-item--highlighted{background:#b1dcfb}.picker--focused .picker__list-item--highlighted,.picker__list-item--highlighted:hover{background:#0089ec;color:#fff}.picker--focused .picker__list-item--disabled,.picker__list-item--disabled,.picker__list-item--disabled:hover{background:#f5f5f5;color:#ddd;cursor:default;border-color:#ddd;z-index:auto}.picker--time .picker__button--clear{display:block;width:80%;margin:1em auto 0;padding:1em 1.25em;background:0 0;border:0;font-weight:500;font-size:.67em;text-align:center;text-transform:uppercase;color:#666}.picker--time .picker__button--clear:focus,.picker--time .picker__button--clear:hover{background:#b1dcfb;background:#e20;border-color:#e20;cursor:pointer;color:#fff;outline:0}.picker--time .picker__button--clear:before{top:-.25em;color:#666;font-size:1.25em;font-weight:700}.picker--time .picker__button--clear:focus:before,.picker--time .picker__button--clear:hover:before{color:#fff}.picker--time .picker__frame{min-width:256px;max-width:320px}.picker--time .picker__box{font-size:1em;background:#f2f2f2;padding:0}@media (min-height:40.125em){.picker--time .picker__box{margin-bottom:5em}} -------------------------------------------------------------------------------- /build/themes/rtl.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Styling for RTL (right-to-left) languages using pickadate.js 3 | */.picker{direction:rtl}.picker__nav--next{right:auto;left:-1em}.picker__nav--prev{left:auto;right:-1em}.picker__nav--next:before{border-left:0;border-right:.75em solid #000}.picker__nav--prev:before{border-right:0;border-left:.75em solid #000} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-datepicker", 3 | "version": "1.0.3", 4 | "title": "angular-datepicker", 5 | "description": "The mobile-friendly, responsive, and lightweight Angular date & time input picker.", 6 | "author": { 7 | "name": "Amsul", 8 | "email": "reach@amsul.ca", 9 | "url": "http://amsul.ca" 10 | }, 11 | "licenses": [ 12 | { 13 | "type": "MIT", 14 | "url": "http://amsul.ca/MIT" 15 | } 16 | ], 17 | "keywords": [ 18 | "date", 19 | "time", 20 | "picker", 21 | "input", 22 | "responsive" 23 | ], 24 | "ignore": [ 25 | "*", 26 | "!lib/**/*" 27 | ], 28 | "main": "src/picker.js", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/alongubkin/angular-datepicker.git" 32 | }, 33 | "dependencies": { 34 | }, 35 | "devDependencies": { 36 | "grunt": "~0.4.2", 37 | "grunt-contrib-concat": "~0.3.0", 38 | "grunt-contrib-watch": "~0.5.3", 39 | "grunt-contrib-copy": "~0.5.0", 40 | "grunt-contrib-less": "~0.9.0", 41 | "grunt-contrib-clean": "~0.5.0", 42 | "grunt-contrib-cssmin": "~0.7.0", 43 | "grunt-contrib-uglify": "~0.3.2" 44 | }, 45 | "scripts": { 46 | "test": "grunt travis --verbose" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/directives.js: -------------------------------------------------------------------------------- 1 | // mostly taken from http://www.codinginsight.com/angularjs-and-pickadate/ 2 | 3 | angular.module('angular-datepicker', []) 4 | .directive('pickADate', function() {     5 | return {         6 | restrict: "A", 7 | scope: {             8 | pickADate: '=', 9 | pickADateOptions: '='         10 | }, 11 | link: function(scope, element, attrs) { 12 | 13 | var options = {}; 14 | 15 | function initPickADate(){ 16 | 17 | options = scope.pickADateOptions ? angular.copy(scope.pickADateOptions) : {}; 18 | 19 | var userOnSet = options.onSet; 20 | 21 | function onSet(e) { 22 | if (typeof userOnSet === 'function') { 23 | userOnSet.apply(this, arguments); 24 | } 25 | 26 | if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup 27 | return;                     28 | var select = element.pickadate('picker').get('select'); // selected date 29 | 30 | if (select) { 31 | scope.$apply(function() {                         32 | if (e.hasOwnProperty('clear')) {                             33 | scope.pickADate = null;                             34 | return;                         35 | }                         36 | if (!scope.pickADate || typeof scope.pickADate === 'string') { 37 | scope.pickADate = new Date(0); 38 | } 39 | scope.pickADate.setYear(select.obj.getFullYear()); 40 | scope.pickADate.setMonth(select.obj.getMonth()); 41 | scope.pickADate.setDate(select.obj.getDate()); 42 | }); 43 | } 44 | } 45 | 46 | var userOnOpen = options.onOpen; 47 | 48 | function onOpen(e) { 49 | if (typeof userOnOpen === 'function') { 50 | userOnOpen.apply(this, arguments); 51 | } 52 | 53 | var time = scope.pickADate ? scope.pickADate : element.val(); 54 | 55 | element.pickadate('picker').set('select', time, { 56 | format: options.format 57 | }); 58 | } 59 | 60 | var userOnClose = options.onClose; 61 | 62 | function onClose(e) { 63 | if (typeof userOnClose === 'function') { 64 | userOnClose.apply(this, arguments); 65 | } 66 | 67 | if (typeof cordova === 'undefined' || !cordova.plugins || !cordova.plugins.Keyboard) { 68 | return; 69 | } 70 | 71 | var keyboardShowCallback = function() { 72 | cordova.plugins.Keyboard.close(); 73 | window.removeEventListener('native.keyboardshow', this); 74 | }; 75 | 76 | window.addEventListener('native.keyboardshow', keyboardShowCallback); 77 | 78 | setTimeout(function() { 79 | window.removeEventListener('native.keyboardshow', keyboardShowCallback); 80 | }, 500); 81 | } 82 | 83 | options = angular.extend(options, { 84 | onOpen: onOpen, 85 | onSet: onSet, 86 | onClose: onClose, 87 | container: document.body             88 | }); 89 | 90 | element.pickadate(options); 91 | 92 | scope.pickADateOptions.container = document.body; 93 | } 94 | 95 | var list = ''; 96 | for (var i in scope.pickADateOptions) { 97 | if (i != 'container') { 98 | list += !list ? 'pickADateOptions.' + i : ' + pickADateOptions.' + i; 99 | } 100 | }   101 | 102 | 103 | function reinitPickADate() { 104 | initPickADate(); 105 | if (element.pickadate('picker')) { 106 | element.pickadate('picker').changeSettings(options); 107 | 108 | if (scope.pickADate) { 109 | element.pickadate('picker').set('select', scope.pickADate); 110 | } 111 | } 112 | } 113 | 114 | scope.$watch(list, reinitPickADate);  115 |   116 | 117 | }     118 | }; 119 | }) 120 | .directive('pickATime', function() {     121 | return {         122 | restrict: "A", 123 | scope: {             124 | pickATime: '=', 125 | pickATimeOptions: '='         126 | }, 127 | link: function(scope, element, attrs) { 128 | 129 | var options = {}; 130 | 131 | function initPickATime(){ 132 | 133 | options = scope.pickATimeOptions ? angular.copy(scope.pickATimeOptions) : {}; 134 | 135 | var userOnSet = options.onSet; 136 | 137 | function onSet(e) { 138 | if (typeof userOnSet === 'function') { 139 | userOnSet.apply(this, arguments); 140 | } 141 | 142 | if (scope.$$phase || scope.$root.$$phase) // we are coming from $watch or link setup 143 | return;                     144 | var select = element.pickatime('picker').get('select'); // selected date 145 | 146 | if (select) { 147 | scope.$apply(function() {                         148 | if (e.hasOwnProperty('clear')) {                             149 | scope.pickATime = null;                             150 | return;                         151 | }                         152 | if (!scope.pickATime || typeof scope.pickATime === 'string') { 153 | scope.pickATime = new Date(); 154 | } 155 | scope.pickATime.setHours(select.hour);                         156 | scope.pickATime.setMinutes(select.mins);                         157 | scope.pickATime.setSeconds(0);                         158 | scope.pickATime.setMilliseconds(0);                     159 | }); 160 | } 161 | } 162 | 163 | 164 | 165 | var userOnOpen = options.onOpen; 166 | 167 | function onOpen(e) { 168 | if (typeof userOnOpen === 'function') { 169 | userOnOpen.apply(this, arguments); 170 | } 171 | 172 | var time = scope.pickATime ? scope.pickATime : element.val(); 173 | 174 | element.pickatime('picker').set('select', time, { 175 | format: options.format 176 | }); 177 | } 178 | 179 | var userOnClose = options.onClose; 180 | 181 | function onClose(e) { 182 | if (typeof userOnClose === 'function') { 183 | userOnClose.apply(this, arguments); 184 | } 185 | 186 | if (typeof cordova === 'undefined' || !cordova.plugins || !cordova.plugins.Keyboard) { 187 | return; 188 | } 189 | 190 | var keyboardShowCallback = function() { 191 | cordova.plugins.Keyboard.close(); 192 | window.removeEventListener('native.keyboardshow', this); 193 | }; 194 | 195 | window.addEventListener('native.keyboardshow', keyboardShowCallback); 196 | 197 | setTimeout(function() { 198 | window.removeEventListener('native.keyboardshow', keyboardShowCallback); 199 | }, 500); 200 | } 201 | 202 | 203 | 204 | element.pickatime(angular.extend(options, {     205 | onOpen: onOpen,             206 | onSet: onSet, 207 | onClose: onClose, 208 | container: document.body             209 | })); 210 | 211 | setTimeout(function() { 212 | if (scope.pickATime) { 213 | element.pickatime('picker').set('select', scope.pickATime); 214 | } 215 | }, 0);       216 | 217 | } 218 | 219 | var list = ''; 220 | for (var i in scope.pickATimeOptions) { 221 | if (i != 'container') { 222 | list += !list ? 'pickATimeOptions.' + i : ' + pickATimeOptions.' + i; 223 | } 224 | }   225 | 226 | function reinitPickATime() { 227 | initPickATime(); 228 | if (element.pickatime('picker')) { 229 | element.pickatime('picker').changeSettings(options); 230 | 231 | if (scope.pickATime) { 232 | element.pickatime('picker').set('select', scope.pickATime); 233 | } 234 | } 235 | } 236 | 237 | scope.$watch(list, reinitPickATime);    238 | }     239 | }; 240 | }); 241 | -------------------------------------------------------------------------------- /src/legacy.js: -------------------------------------------------------------------------------- 1 | 2 | /*jshint 3 | asi: true, 4 | unused: true, 5 | boss: true, 6 | loopfunc: true, 7 | eqnull: true 8 | */ 9 | 10 | 11 | /*! 12 | * Legacy browser support 13 | */ 14 | 15 | 16 | // Map array support 17 | if ( ![].map ) { 18 | Array.prototype.map = function ( callback, self ) { 19 | var array = this, len = array.length, newArray = new Array( len ) 20 | for ( var i = 0; i < len; i++ ) { 21 | if ( i in array ) { 22 | newArray[ i ] = callback.call( self, array[ i ], i, array ) 23 | } 24 | } 25 | return newArray 26 | } 27 | } 28 | 29 | 30 | // Filter array support 31 | if ( ![].filter ) { 32 | Array.prototype.filter = function( callback ) { 33 | if ( this == null ) throw new TypeError() 34 | var t = Object( this ), len = t.length >>> 0 35 | if ( typeof callback != 'function' ) throw new TypeError() 36 | var newArray = [], thisp = arguments[ 1 ] 37 | for ( var i = 0; i < len; i++ ) { 38 | if ( i in t ) { 39 | var val = t[ i ] 40 | if ( callback.call( thisp, val, i, t ) ) newArray.push( val ) 41 | } 42 | } 43 | return newArray 44 | } 45 | } 46 | 47 | 48 | // Index of array support 49 | if ( ![].indexOf ) { 50 | Array.prototype.indexOf = function( searchElement ) { 51 | if ( this == null ) throw new TypeError() 52 | var t = Object( this ), len = t.length >>> 0 53 | if ( len === 0 ) return -1 54 | var n = 0 55 | if ( arguments.length > 1 ) { 56 | n = Number( arguments[ 1 ] ) 57 | if ( n != n ) { 58 | n = 0 59 | } 60 | else if ( n !== 0 && n != Infinity && n != -Infinity ) { 61 | n = ( n > 0 || -1 ) * Math.floor( Math.abs( n ) ) 62 | } 63 | } 64 | if ( n >= len ) return -1 65 | var k = n >= 0 ? n : Math.max( len - Math.abs( n ), 0 ) 66 | for ( ; k < len; k++ ) { 67 | if ( k in t && t[ k ] === searchElement ) return k 68 | } 69 | return -1 70 | } 71 | } 72 | 73 | 74 | /*! 75 | * Cross-Browser Split 1.1.1 76 | * Copyright 2007-2012 Steven Levithan 77 | * Available under the MIT License 78 | * http://blog.stevenlevithan.com/archives/cross-browser-split 79 | */ 80 | var nativeSplit = String.prototype.split, compliantExecNpcg = /()??/.exec('')[1] === undefined 81 | String.prototype.split = function(separator, limit) { 82 | var str = this 83 | if (Object.prototype.toString.call(separator) !== '[object RegExp]') { 84 | return nativeSplit.call(str, separator, limit) 85 | } 86 | var output = [], 87 | flags = (separator.ignoreCase ? 'i' : '') + 88 | (separator.multiline ? 'm' : '') + 89 | (separator.extended ? 'x' : '') + 90 | (separator.sticky ? 'y' : ''), 91 | lastLastIndex = 0, 92 | separator2, match, lastIndex, lastLength 93 | separator = new RegExp(separator.source, flags + 'g') 94 | str += '' 95 | if (!compliantExecNpcg) { 96 | separator2 = new RegExp('^' + separator.source + '$(?!\\s)', flags) 97 | } 98 | limit = limit === undefined ? -1 >>> 0 : limit >>> 0 99 | while (match = separator.exec(str)) { 100 | lastIndex = match.index + match[0].length 101 | if (lastIndex > lastLastIndex) { 102 | output.push(str.slice(lastLastIndex, match.index)) 103 | if (!compliantExecNpcg && match.length > 1) { 104 | match[0].replace(separator2, function () { 105 | for (var i = 1; i < arguments.length - 2; i++) { 106 | if (arguments[i] === undefined) { 107 | match[i] = undefined 108 | } 109 | } 110 | }) 111 | } 112 | if (match.length > 1 && match.index < str.length) { 113 | Array.prototype.push.apply(output, match.slice(1)) 114 | } 115 | lastLength = match[0].length 116 | lastLastIndex = lastIndex 117 | if (output.length >= limit) { 118 | break 119 | } 120 | } 121 | if (separator.lastIndex === match.index) { 122 | separator.lastIndex++ 123 | } 124 | } 125 | if (lastLastIndex === str.length) { 126 | if (lastLength || !separator.test('')) { 127 | output.push('') 128 | } 129 | } else { 130 | output.push(str.slice(lastLastIndex)) 131 | } 132 | return output.length > limit ? output.slice(0, limit) : output 133 | } 134 | -------------------------------------------------------------------------------- /src/picker.date.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Date picker for pickadate.js v3.4.0 4 | * http://amsul.github.io/pickadate.js/date.htm 5 | */ 6 | 7 | (function ( factory ) { 8 | 9 | // Register as an anonymous module. 10 | if ( typeof define == 'function' && define.amd ) 11 | define( ['picker','angular'], factory ) 12 | 13 | // Or using browser globals. 14 | else factory( Picker, angular ) 15 | 16 | }(function( Picker, angular ) { 17 | 18 | 19 | /** 20 | * Globals and constants 21 | */ 22 | var DAYS_IN_WEEK = 7, 23 | WEEKS_IN_CALENDAR = 6, 24 | _ = Picker._ 25 | 26 | 27 | 28 | /** 29 | * The date picker constructor 30 | */ 31 | function DatePicker( picker, settings ) { 32 | 33 | var calendar = this, 34 | elementValue = picker.$node[ 0 ].value, 35 | elementDataValue = picker.$node.attr( 'data-value' ), 36 | valueString = elementDataValue || elementValue, 37 | formatString = elementDataValue ? settings.formatSubmit : settings.format, 38 | isRTL = function() { 39 | return getComputedStyle( picker.$root[0] ).direction === 'rtl' 40 | } 41 | 42 | calendar.settings = settings 43 | calendar.$node = picker.$node 44 | 45 | // The queue of methods that will be used to build item objects. 46 | calendar.queue = { 47 | min: 'measure create', 48 | max: 'measure create', 49 | now: 'now create', 50 | select: 'parse create validate', 51 | highlight: 'parse navigate create validate', 52 | view: 'parse create validate viewset', 53 | disable: 'deactivate', 54 | enable: 'activate' 55 | } 56 | 57 | // The component's item object. 58 | calendar.item = {} 59 | 60 | calendar.item.disable = ( settings.disable || [] ).slice( 0 ) 61 | calendar.item.enable = -(function( collectionDisabled ) { 62 | return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1 63 | })( calendar.item.disable ) 64 | 65 | calendar. 66 | set( 'min', settings.min ). 67 | set( 'max', settings.max ). 68 | set( 'now' ) 69 | 70 | // When there’s a value, set the `select`, which in turn 71 | // also sets the `highlight` and `view`. 72 | if ( valueString ) { 73 | calendar.set( 'select', valueString, { 74 | format: formatString, 75 | fromValue: !!elementValue 76 | }) 77 | } 78 | 79 | // If there’s no value, default to highlighting “today”. 80 | else { 81 | calendar. 82 | set( 'select', null ). 83 | set( 'highlight', calendar.item.now ) 84 | } 85 | 86 | 87 | // The keycode to movement mapping. 88 | calendar.key = { 89 | 40: 7, // Down 90 | 38: -7, // Up 91 | 39: function() { return isRTL() ? -1 : 1 }, // Right 92 | 37: function() { return isRTL() ? 1 : -1 }, // Left 93 | go: function( timeChange ) { 94 | var highlightedObject = calendar.item.highlight, 95 | targetDate = new Date( highlightedObject.year, highlightedObject.month, highlightedObject.date + timeChange), 96 | dateObj = calendar.create([targetDate.getFullYear(), targetDate.getMonth(), targetDate.getDate()]) 97 | calendar 98 | .set('select', dateObj) 99 | .set( 100 | 'highlight', 101 | dateObj, 102 | { interval: timeChange } 103 | ) 104 | this.render() 105 | } 106 | } 107 | 108 | 109 | // Bind some picker events. 110 | picker. 111 | on( 'render', function() { 112 | angular.element(picker.$root[0].querySelectorAll( '.' + settings.klass.selectMonth )).on( 'change', function() { 113 | var value = this.value 114 | if ( value ) { 115 | picker.set( 'highlight', [ picker.get( 'view' ).year, value, picker.get( 'highlight' ).date ] ) 116 | angular.element(picker.$root[0].querySelectorAll( '.' + settings.klass.selectMonth )).triggerHandler( 'focus' ) 117 | } 118 | }) 119 | angular.element(picker.$root[0].querySelectorAll( '.' + settings.klass.selectYear )).on( 'change', function() { 120 | var value = this.value 121 | if ( value ) { 122 | picker.set( 'highlight', [ value, picker.get( 'view' ).month, picker.get( 'highlight' ).date ] ) 123 | angular.element(picker.$root[0].querySelectorAll( '.' + settings.klass.selectYear )).triggerHandler( 'focus' ) 124 | } 125 | }) 126 | }). 127 | on( 'open', function() { 128 | angular.element(picker.$root[0].querySelectorAll( 'button, select' )).attr( 'disabled', false ) 129 | }). 130 | on( 'close', function() { 131 | angular.element(picker.$root[0].querySelectorAll( 'button, select' )).attr( 'disabled', true ) 132 | }) 133 | 134 | } //DatePicker 135 | 136 | 137 | /** 138 | * Set a datepicker item object. 139 | */ 140 | DatePicker.prototype.set = function( type, value, options ) { 141 | 142 | var calendar = this, 143 | calendarItem = calendar.item 144 | 145 | // If the value is `null` just set it immediately. 146 | if ( value === null ) { 147 | calendarItem[ type ] = value 148 | return calendar 149 | } 150 | 151 | // Otherwise go through the queue of methods, and invoke the functions. 152 | // Update this as the time unit, and set the final value as this item. 153 | // * In the case of `enable`, keep the queue but set `disable` instead. 154 | // And in the case of `flip`, keep the queue but set `enable` instead. 155 | calendarItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = calendar.queue[ type ].split( ' ' ).map( function( method ) { 156 | value = calendar[ method ]( type, value, options ) 157 | return value 158 | }).pop() 159 | 160 | // Check if we need to cascade through more updates. 161 | if ( type == 'select' ) { 162 | calendar.set( 'highlight', calendarItem.select, options ) 163 | } 164 | else if ( type == 'highlight' ) { 165 | calendar.set( 'view', calendarItem.highlight, options ) 166 | } 167 | else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) { 168 | if ( calendarItem.select && calendar.disabled( calendarItem.select ) ) { 169 | calendar.set( 'select', calendarItem.select, options ) 170 | } 171 | if ( calendarItem.highlight && calendar.disabled( calendarItem.highlight ) ) { 172 | calendar.set( 'highlight', calendarItem.highlight, options ) 173 | } 174 | } 175 | 176 | return calendar 177 | } //DatePicker.prototype.set 178 | 179 | 180 | /** 181 | * Get a datepicker item object. 182 | */ 183 | DatePicker.prototype.get = function( type ) { 184 | return this.item[ type ] 185 | } //DatePicker.prototype.get 186 | 187 | 188 | /** 189 | * Create a picker date object. 190 | */ 191 | DatePicker.prototype.create = function( type, value, options ) { 192 | 193 | var isInfiniteValue, 194 | calendar = this 195 | 196 | // If there’s no value, use the type as the value. 197 | value = value === undefined ? type : value 198 | 199 | 200 | // If it’s infinity, update the value. 201 | if ( value == -Infinity || value == Infinity ) { 202 | isInfiniteValue = value 203 | } 204 | 205 | // If it’s an object, use the native date object. 206 | else if ( angular.isObject( value ) && _.isInteger( value.pick ) ) { 207 | value = value.obj 208 | } 209 | 210 | // If it’s an array, convert it into a date and make sure 211 | // that it’s a valid date – otherwise default to today. 212 | else if ( angular.isArray( value ) ) { 213 | value = new Date( value[ 0 ], value[ 1 ], value[ 2 ] ) 214 | value = _.isDate( value ) ? value : calendar.create().obj 215 | } 216 | 217 | // If it’s a number or date object, make a normalized date. 218 | else if ( _.isInteger( value ) || _.isDate( value ) ) { 219 | value = calendar.normalize( new Date( value ), options ) 220 | } 221 | 222 | // If it’s a literal true or any other case, set it to now. 223 | else /*if ( value === true )*/ { 224 | value = calendar.now( type, value, options ) 225 | } 226 | 227 | // Return the compiled object. 228 | return { 229 | year: isInfiniteValue || value.getFullYear(), 230 | month: isInfiniteValue || value.getMonth(), 231 | date: isInfiniteValue || value.getDate(), 232 | day: isInfiniteValue || value.getDay(), 233 | obj: isInfiniteValue || value, 234 | pick: isInfiniteValue || value.getTime() 235 | } 236 | } //DatePicker.prototype.create 237 | 238 | 239 | /** 240 | * Create a range limit object using an array, date object, 241 | * literal “true”, or integer relative to another time. 242 | */ 243 | DatePicker.prototype.createRange = function( from, to ) { 244 | 245 | var calendar = this, 246 | createDate = function( date ) { 247 | if ( date === true || angular.isArray( date ) || _.isDate( date ) ) { 248 | return calendar.create( date ) 249 | } 250 | return date 251 | } 252 | 253 | // Create objects if possible. 254 | if ( !_.isInteger( from ) ) { 255 | from = createDate( from ) 256 | } 257 | if ( !_.isInteger( to ) ) { 258 | to = createDate( to ) 259 | } 260 | 261 | // Create relative dates. 262 | if ( _.isInteger( from ) && angular.isObject( to ) ) { 263 | from = [ to.year, to.month, to.date + from ]; 264 | } 265 | else if ( _.isInteger( to ) && angular.isObject( from ) ) { 266 | to = [ from.year, from.month, from.date + to ]; 267 | } 268 | 269 | return { 270 | from: createDate( from ), 271 | to: createDate( to ) 272 | } 273 | } //DatePicker.prototype.createRange 274 | 275 | 276 | /** 277 | * Check if a date unit falls within a date range object. 278 | */ 279 | DatePicker.prototype.withinRange = function( range, dateUnit ) { 280 | range = this.createRange(range.from, range.to) 281 | return dateUnit.pick >= range.from.pick && dateUnit.pick <= range.to.pick 282 | } 283 | 284 | 285 | /** 286 | * Check if two date range objects overlap. 287 | */ 288 | DatePicker.prototype.overlapRanges = function( one, two ) { 289 | 290 | var calendar = this 291 | 292 | // Convert the ranges into comparable dates. 293 | one = calendar.createRange( one.from, one.to ) 294 | two = calendar.createRange( two.from, two.to ) 295 | 296 | return calendar.withinRange( one, two.from ) || calendar.withinRange( one, two.to ) || 297 | calendar.withinRange( two, one.from ) || calendar.withinRange( two, one.to ) 298 | } 299 | 300 | 301 | /** 302 | * Get the date today. 303 | */ 304 | DatePicker.prototype.now = function( type, value, options ) { 305 | value = new Date() 306 | if ( options && options.rel ) { 307 | value.setDate( value.getDate() + options.rel ) 308 | } 309 | return this.normalize( value, options ) 310 | } 311 | 312 | 313 | /** 314 | * Navigate to next/prev month. 315 | */ 316 | DatePicker.prototype.navigate = function( type, value, options ) { 317 | 318 | var targetDateObject, 319 | targetYear, 320 | targetMonth, 321 | targetDate, 322 | isTargetArray = angular.isArray( value ), 323 | isTargetObject = angular.isObject( value ), 324 | viewsetObject = this.item.view/*, 325 | safety = 100*/ 326 | 327 | 328 | if ( isTargetArray || isTargetObject ) { 329 | 330 | if ( isTargetArray ) { 331 | targetYear = value[0] 332 | targetMonth = value[1] 333 | targetDate = value[2] 334 | } 335 | else { 336 | targetYear = value.year 337 | targetMonth = value.month 338 | targetDate = value.date 339 | } 340 | 341 | // If we’re navigating months but the view is in a different 342 | // month, navigate to the view’s year and month. 343 | if ( options && options.nav && viewsetObject && viewsetObject.month !== targetMonth ) { 344 | targetYear = viewsetObject.year 345 | targetMonth = viewsetObject.month 346 | } 347 | 348 | // Figure out the expected target year and month. 349 | targetDateObject = new Date( targetYear, parseInt(targetMonth) + parseInt( options && options.nav ? options.nav : 0 ), 1 ) 350 | targetYear = targetDateObject.getFullYear() 351 | targetMonth = targetDateObject.getMonth() 352 | 353 | // If the month we’re going to doesn’t have enough days, 354 | // keep decreasing the date until we reach the month’s last date. 355 | while ( /*safety &&*/ new Date( targetYear, targetMonth, targetDate ).getMonth() !== targetMonth ) { 356 | targetDate -= 1 357 | /*safety -= 1 358 | if ( !safety ) { 359 | throw 'Fell into an infinite loop while navigating to ' + new Date( targetYear, targetMonth, targetDate ) + '.' 360 | }*/ 361 | } 362 | 363 | value = [ targetYear, targetMonth, targetDate ] 364 | } 365 | 366 | return value 367 | } //DatePicker.prototype.navigate 368 | 369 | 370 | /** 371 | * Normalize a date by setting the hours to midnight. 372 | */ 373 | DatePicker.prototype.normalize = function( value/*, options*/ ) { 374 | value.setHours( 0, 0, 0, 0 ) 375 | return value 376 | } 377 | 378 | 379 | /** 380 | * Measure the range of dates. 381 | */ 382 | DatePicker.prototype.measure = function( type, value/*, options*/ ) { 383 | 384 | var calendar = this 385 | 386 | // If it's anything false-y, remove the limits. 387 | if ( !value ) { 388 | value = type == 'min' ? -Infinity : Infinity 389 | } 390 | 391 | // If it's an integer, get a date relative to today. 392 | else if ( _.isInteger( value ) ) { 393 | value = calendar.now( type, value, { rel: value } ) 394 | } 395 | 396 | return value 397 | } ///DatePicker.prototype.measure 398 | 399 | 400 | /** 401 | * Create a viewset object based on navigation. 402 | */ 403 | DatePicker.prototype.viewset = function( type, dateObject/*, options*/ ) { 404 | return this.create([ dateObject.year, dateObject.month, 1 ]) 405 | } 406 | 407 | 408 | /** 409 | * Validate a date as enabled and shift if needed. 410 | */ 411 | DatePicker.prototype.validate = function( type, dateObject, options ) { 412 | 413 | var calendar = this, 414 | 415 | // Keep a reference to the original date. 416 | originalDateObject = dateObject, 417 | 418 | // Make sure we have an interval. 419 | interval = options && options.interval ? options.interval : 1, 420 | 421 | // Check if the calendar enabled dates are inverted. 422 | isFlippedBase = calendar.item.enable === -1, 423 | 424 | // Check if we have any enabled dates after/before now. 425 | hasEnabledBeforeTarget, hasEnabledAfterTarget, 426 | 427 | // The min & max limits. 428 | minLimitObject = calendar.item.min, 429 | maxLimitObject = calendar.item.max, 430 | 431 | // Check if we’ve reached the limit during shifting. 432 | reachedMin, reachedMax, 433 | 434 | // Check if the calendar is inverted and at least one weekday is enabled. 435 | hasEnabledWeekdays = isFlippedBase && calendar.item.disable.filter( function( value ) { 436 | 437 | // If there’s a date, check where it is relative to the target. 438 | if ( angular.isArray( value ) ) { 439 | var dateTime = calendar.create( value ).pick 440 | if ( dateTime < dateObject.pick ) hasEnabledBeforeTarget = true 441 | else if ( dateTime > dateObject.pick ) hasEnabledAfterTarget = true 442 | } 443 | 444 | // Return only integers for enabled weekdays. 445 | return _.isInteger( value ) 446 | }).length/*, 447 | 448 | safety = 100*/ 449 | 450 | 451 | 452 | // Cases to validate for: 453 | // [1] Not inverted and date disabled. 454 | // [2] Inverted and some dates enabled. 455 | // [3] Not inverted and out of range. 456 | // 457 | // Cases to **not** validate for: 458 | // • Navigating months. 459 | // • Not inverted and date enabled. 460 | // • Inverted and all dates disabled. 461 | // • ..and anything else. 462 | if ( !options || !options.nav ) if ( 463 | /* 1 */ ( !isFlippedBase && calendar.disabled( dateObject ) ) || 464 | /* 2 */ ( isFlippedBase && calendar.disabled( dateObject ) && ( hasEnabledWeekdays || hasEnabledBeforeTarget || hasEnabledAfterTarget ) ) || 465 | /* 3 */ ( !isFlippedBase && (dateObject.pick <= minLimitObject.pick || dateObject.pick >= maxLimitObject.pick) ) 466 | ) { 467 | 468 | 469 | // When inverted, flip the direction if there aren’t any enabled weekdays 470 | // and there are no enabled dates in the direction of the interval. 471 | if ( isFlippedBase && !hasEnabledWeekdays && ( ( !hasEnabledAfterTarget && interval > 0 ) || ( !hasEnabledBeforeTarget && interval < 0 ) ) ) { 472 | interval *= -1 473 | } 474 | 475 | 476 | // Keep looping until we reach an enabled date. 477 | while ( /*safety &&*/ calendar.disabled( dateObject ) ) { 478 | 479 | /*safety -= 1 480 | if ( !safety ) { 481 | throw 'Fell into an infinite loop while validating ' + dateObject.obj + '.' 482 | }*/ 483 | 484 | 485 | // If we’ve looped into the next/prev month with a large interval, return to the original date and flatten the interval. 486 | if ( Math.abs( interval ) > 1 && ( dateObject.month < originalDateObject.month || dateObject.month > originalDateObject.month ) ) { 487 | dateObject = originalDateObject 488 | interval = interval > 0 ? 1 : -1 489 | } 490 | 491 | 492 | // If we’ve reached the min/max limit, reverse the direction, flatten the interval and set it to the limit. 493 | if ( dateObject.pick <= minLimitObject.pick ) { 494 | reachedMin = true 495 | interval = 1 496 | dateObject = calendar.create([ minLimitObject.year, minLimitObject.month, minLimitObject.date - 1 ]) 497 | } 498 | else if ( dateObject.pick >= maxLimitObject.pick ) { 499 | reachedMax = true 500 | interval = -1 501 | dateObject = calendar.create([ maxLimitObject.year, maxLimitObject.month, maxLimitObject.date + 1 ]) 502 | } 503 | 504 | 505 | // If we’ve reached both limits, just break out of the loop. 506 | if ( reachedMin && reachedMax ) { 507 | break 508 | } 509 | 510 | 511 | // Finally, create the shifted date using the interval and keep looping. 512 | dateObject = calendar.create([ dateObject.year, dateObject.month, dateObject.date + interval ]) 513 | } 514 | 515 | } //endif 516 | 517 | 518 | // Return the date object settled on. 519 | return dateObject 520 | } //DatePicker.prototype.validate 521 | 522 | 523 | /** 524 | * Check if a date is disabled. 525 | */ 526 | DatePicker.prototype.disabled = function( dateToVerify ) { 527 | 528 | var 529 | calendar = this, 530 | 531 | // Filter through the disabled dates to check if this is one. 532 | isDisabledMatch = calendar.item.disable.filter( function( dateToDisable ) { 533 | 534 | // If the date is a number, match the weekday with 0index and `firstDay` check. 535 | if ( _.isInteger( dateToDisable ) ) { 536 | return dateToVerify.day === ( calendar.settings.firstDay ? dateToDisable : dateToDisable - 1 ) % 7 537 | } 538 | 539 | // If it’s an array or a native JS date, create and match the exact date. 540 | if ( angular.isArray( dateToDisable ) || _.isDate( dateToDisable ) ) { 541 | return dateToVerify.pick === calendar.create( dateToDisable ).pick 542 | } 543 | 544 | // If it’s an object, match a date within the “from” and “to” range. 545 | if ( angular.isObject( dateToDisable ) ) { 546 | return calendar.withinRange( dateToDisable, dateToVerify ) 547 | } 548 | }) 549 | 550 | // If this date matches a disabled date, confirm it’s not inverted. 551 | isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( dateToDisable ) { 552 | return angular.isArray( dateToDisable ) && dateToDisable[3] == 'inverted' || 553 | angular.isObject( dateToDisable ) && dateToDisable.inverted 554 | }).length 555 | 556 | // Check the calendar “enabled” flag and respectively flip the 557 | // disabled state. Then also check if it’s beyond the min/max limits. 558 | return calendar.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || 559 | dateToVerify.pick < calendar.item.min.pick || 560 | dateToVerify.pick > calendar.item.max.pick 561 | 562 | } //DatePicker.prototype.disabled 563 | 564 | 565 | /** 566 | * Parse a string into a usable type. 567 | */ 568 | DatePicker.prototype.parse = function( type, value, options ) { 569 | 570 | var calendar = this, 571 | parsingObject = {}, 572 | monthIndex 573 | 574 | if ( !value || _.isInteger( value ) || angular.isArray( value ) || _.isDate( value ) || angular.isObject( value ) && _.isInteger( value.pick ) ) { 575 | return value 576 | } 577 | 578 | // We need a `.format` to parse the value with. 579 | if ( !( options && options.format ) ) { 580 | options = options || {} 581 | options.format = calendar.settings.format 582 | } 583 | 584 | // Calculate the month index to adjust with. 585 | monthIndex = typeof value == 'string' && !options.fromValue ? 1 : 0 586 | 587 | // Convert the format into an array and then map through it. 588 | calendar.formats.toArray( options.format ).map( function( label ) { 589 | 590 | var 591 | // Grab the formatting label. 592 | formattingLabel = calendar.formats[ label ], 593 | 594 | // The format length is from the formatting label function or the 595 | // label length without the escaping exclamation (!) mark. 596 | formatLength = formattingLabel ? _.trigger( formattingLabel, calendar, [ value, parsingObject ] ) : label.replace( /^!/, '' ).length 597 | 598 | // If there's a format label, split the value up to the format length. 599 | // Then add it to the parsing object with appropriate label. 600 | if ( formattingLabel ) { 601 | parsingObject[ label ] = value.substr( 0, formatLength ) 602 | } 603 | 604 | // Update the value as the substring from format length to end. 605 | value = value.substr( formatLength ) 606 | }) 607 | 608 | // If it’s parsing a user provided month value, compensate for month 0index. 609 | return [ 610 | parsingObject.yyyy || parsingObject.yy, 611 | +( parsingObject.mm || parsingObject.m ) - monthIndex, 612 | parsingObject.dd || parsingObject.d 613 | ] 614 | } //DatePicker.prototype.parse 615 | 616 | 617 | /** 618 | * Various formats to display the object in. 619 | */ 620 | DatePicker.prototype.formats = (function() { 621 | 622 | // Return the length of the first word in a collection. 623 | function getWordLengthFromCollection( string, collection, dateObject ) { 624 | 625 | // Grab the first word from the string. 626 | var word = string.match( /\w+/ )[ 0 ] 627 | 628 | // If there's no month index, add it to the date object 629 | if ( !dateObject.mm && !dateObject.m ) { 630 | dateObject.m = collection.indexOf( word ) 631 | } 632 | 633 | // Return the length of the word. 634 | return word.length 635 | } 636 | 637 | // Get the length of the first word in a string. 638 | function getFirstWordLength( string ) { 639 | return string.match( /\w+/ )[ 0 ].length 640 | } 641 | 642 | return { 643 | 644 | d: function( string, dateObject ) { 645 | 646 | // If there's string, then get the digits length. 647 | // Otherwise return the selected date. 648 | return string ? _.digits( string ) : dateObject.date 649 | }, 650 | dd: function( string, dateObject ) { 651 | 652 | // If there's a string, then the length is always 2. 653 | // Otherwise return the selected date with a leading zero. 654 | return string ? 2 : _.lead( dateObject.date ) 655 | }, 656 | ddd: function( string, dateObject ) { 657 | 658 | // If there's a string, then get the length of the first word. 659 | // Otherwise return the short selected weekday. 660 | return string ? getFirstWordLength( string ) : this.settings.weekdaysShort[ dateObject.day ] 661 | }, 662 | dddd: function( string, dateObject ) { 663 | 664 | // If there's a string, then get the length of the first word. 665 | // Otherwise return the full selected weekday. 666 | return string ? getFirstWordLength( string ) : this.settings.weekdaysFull[ dateObject.day ] 667 | }, 668 | m: function( string, dateObject ) { 669 | 670 | // If there's a string, then get the length of the digits 671 | // Otherwise return the selected month with 0index compensation. 672 | return string ? _.digits( string ) : dateObject.month + 1 673 | }, 674 | mm: function( string, dateObject ) { 675 | 676 | // If there's a string, then the length is always 2. 677 | // Otherwise return the selected month with 0index and leading zero. 678 | return string ? 2 : _.lead( dateObject.month + 1 ) 679 | }, 680 | mmm: function( string, dateObject ) { 681 | 682 | var collection = this.settings.monthsShort 683 | 684 | // If there's a string, get length of the relevant month from the short 685 | // months collection. Otherwise return the selected month from that collection. 686 | return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] 687 | }, 688 | mmmm: function( string, dateObject ) { 689 | 690 | var collection = this.settings.monthsFull 691 | 692 | // If there's a string, get length of the relevant month from the full 693 | // months collection. Otherwise return the selected month from that collection. 694 | return string ? getWordLengthFromCollection( string, collection, dateObject ) : collection[ dateObject.month ] 695 | }, 696 | yy: function( string, dateObject ) { 697 | 698 | // If there's a string, then the length is always 2. 699 | // Otherwise return the selected year by slicing out the first 2 digits. 700 | return string ? 2 : ( '' + dateObject.year ).slice( 2 ) 701 | }, 702 | yyyy: function( string, dateObject ) { 703 | 704 | // If there's a string, then the length is always 4. 705 | // Otherwise return the selected year. 706 | return string ? 4 : dateObject.year 707 | }, 708 | 709 | // Create an array by splitting the formatting string passed. 710 | toArray: function( formatString ) { return formatString.split( /(d{1,4}|m{1,4}|y{4}|yy|!.)/g ) }, 711 | 712 | // Format an object into a string using the formatting options. 713 | toString: function ( formatString, itemObject ) { 714 | var calendar = this 715 | return calendar.formats.toArray( formatString ).map( function( label ) { 716 | return _.trigger( calendar.formats[ label ], calendar, [ 0, itemObject ] ) || label.replace( /^!/, '' ) 717 | }).join( '' ) 718 | } 719 | } 720 | })() //DatePicker.prototype.formats 721 | 722 | 723 | 724 | 725 | /** 726 | * Check if two date units are the exact. 727 | */ 728 | DatePicker.prototype.isDateExact = function( one, two ) { 729 | 730 | var calendar = this 731 | 732 | // When we’re working with weekdays, do a direct comparison. 733 | if ( 734 | ( _.isInteger( one ) && _.isInteger( two ) ) || 735 | ( typeof one == 'boolean' && typeof two == 'boolean' ) 736 | ) { 737 | return one === two 738 | } 739 | 740 | // When we’re working with date representations, compare the “pick” value. 741 | if ( 742 | ( _.isDate( one ) || angular.isArray( one ) ) && 743 | ( _.isDate( two ) || angular.isArray( two ) ) 744 | ) { 745 | return calendar.create( one ).pick === calendar.create( two ).pick 746 | } 747 | 748 | // When we’re working with range objects, compare the “from” and “to”. 749 | if ( angular.isObject( one ) && angular.isObject( two ) ) { 750 | return calendar.isDateExact( one.from, two.from ) && calendar.isDateExact( one.to, two.to ) 751 | } 752 | 753 | return false 754 | } 755 | 756 | 757 | /** 758 | * Check if two date units overlap. 759 | */ 760 | DatePicker.prototype.isDateOverlap = function( one, two ) { 761 | 762 | var calendar = this 763 | 764 | // When we’re working with a weekday index, compare the days. 765 | if ( _.isInteger( one ) && ( _.isDate( two ) || angular.isArray( two ) ) ) { 766 | return one === calendar.create( two ).day + 1 767 | } 768 | if ( _.isInteger( two ) && ( _.isDate( one ) || angular.isArray( one ) ) ) { 769 | return two === calendar.create( one ).day + 1 770 | } 771 | 772 | // When we’re working with range objects, check if the ranges overlap. 773 | if ( angular.isObject( one ) && angular.isObject( two ) ) { 774 | return calendar.overlapRanges( one, two ) 775 | } 776 | 777 | return false 778 | } 779 | 780 | 781 | /** 782 | * Flip the “enabled” state. 783 | */ 784 | DatePicker.prototype.flipEnable = function(val) { 785 | var itemObject = this.item 786 | itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) 787 | } 788 | 789 | 790 | /** 791 | * Mark a collection of dates as “disabled”. 792 | */ 793 | DatePicker.prototype.deactivate = function( type, datesToDisable ) { 794 | 795 | var calendar = this, 796 | disabledItems = calendar.item.disable.slice(0) 797 | 798 | 799 | // If we’re flipping, that’s all we need to do. 800 | if ( datesToDisable == 'flip' ) { 801 | calendar.flipEnable() 802 | } 803 | 804 | else if ( datesToDisable === false ) { 805 | calendar.flipEnable(1) 806 | disabledItems = [] 807 | } 808 | 809 | else if ( datesToDisable === true ) { 810 | calendar.flipEnable(-1) 811 | disabledItems = [] 812 | } 813 | 814 | // Otherwise go through the dates to disable. 815 | else { 816 | 817 | datesToDisable.map(function( unitToDisable ) { 818 | 819 | var matchFound 820 | 821 | // When we have disabled items, check for matches. 822 | // If something is matched, immediately break out. 823 | for ( var index = 0; index < disabledItems.length; index += 1 ) { 824 | if ( calendar.isDateExact( unitToDisable, disabledItems[index] ) ) { 825 | matchFound = true 826 | break 827 | } 828 | } 829 | 830 | // If nothing was found, add the validated unit to the collection. 831 | if ( !matchFound ) { 832 | if ( 833 | _.isInteger( unitToDisable ) || 834 | _.isDate( unitToDisable ) || 835 | angular.isArray( unitToDisable ) || 836 | ( angular.isObject( unitToDisable ) && unitToDisable.from && unitToDisable.to ) 837 | ) { 838 | disabledItems.push( unitToDisable ) 839 | } 840 | } 841 | }) 842 | } 843 | 844 | // Return the updated collection. 845 | return disabledItems 846 | } //DatePicker.prototype.deactivate 847 | 848 | 849 | /** 850 | * Mark a collection of dates as “enabled”. 851 | */ 852 | DatePicker.prototype.activate = function( type, datesToEnable ) { 853 | 854 | var calendar = this, 855 | disabledItems = calendar.item.disable, 856 | disabledItemsCount = disabledItems.length 857 | 858 | // If we’re flipping, that’s all we need to do. 859 | if ( datesToEnable == 'flip' ) { 860 | calendar.flipEnable() 861 | } 862 | 863 | else if ( datesToEnable === true ) { 864 | calendar.flipEnable(1) 865 | disabledItems = [] 866 | } 867 | 868 | else if ( datesToEnable === false ) { 869 | calendar.flipEnable(-1) 870 | disabledItems = [] 871 | } 872 | 873 | // Otherwise go through the disabled dates. 874 | else { 875 | 876 | datesToEnable.map(function( unitToEnable ) { 877 | 878 | var matchFound, 879 | disabledUnit, 880 | index, 881 | isExactRange 882 | 883 | // Go through the disabled items and try to find a match. 884 | for ( index = 0; index < disabledItemsCount; index += 1 ) { 885 | 886 | disabledUnit = disabledItems[index] 887 | 888 | // When an exact match is found, remove it from the collection. 889 | if ( calendar.isDateExact( disabledUnit, unitToEnable ) ) { 890 | matchFound = disabledItems[index] = null 891 | isExactRange = true 892 | break 893 | } 894 | 895 | // When an overlapped match is found, add the “inverted” state to it. 896 | else if ( calendar.isDateOverlap( disabledUnit, unitToEnable ) ) { 897 | if ( angular.isObject( unitToEnable ) ) { 898 | unitToEnable.inverted = true 899 | matchFound = unitToEnable 900 | } 901 | else if ( angular.isArray( unitToEnable ) ) { 902 | matchFound = unitToEnable 903 | if ( !matchFound[3] ) matchFound.push( 'inverted' ) 904 | } 905 | else if ( _.isDate( unitToEnable ) ) { 906 | matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ] 907 | } 908 | break 909 | } 910 | } 911 | 912 | // If a match was found, remove a previous duplicate entry. 913 | if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 914 | if ( calendar.isDateExact( disabledItems[index], unitToEnable ) ) { 915 | disabledItems[index] = null 916 | break 917 | } 918 | } 919 | 920 | // In the event that we’re dealing with an exact range of dates, 921 | // make sure there are no “inverted” dates because of it. 922 | if ( isExactRange ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 923 | if ( calendar.isDateOverlap( disabledItems[index], unitToEnable ) ) { 924 | disabledItems[index] = null 925 | break 926 | } 927 | } 928 | 929 | // If something is still matched, add it into the collection. 930 | if ( matchFound ) { 931 | disabledItems.push( matchFound ) 932 | } 933 | }) 934 | } 935 | 936 | // Return the updated collection. 937 | return disabledItems.filter(function( val ) { return val != null }) 938 | } //DatePicker.prototype.activate 939 | 940 | 941 | /** 942 | * Create a string for the nodes in the picker. 943 | */ 944 | DatePicker.prototype.nodes = function( isOpen ) { 945 | 946 | var 947 | calendar = this, 948 | settings = calendar.settings, 949 | calendarItem = calendar.item, 950 | nowObject = calendarItem.now, 951 | selectedObject = calendarItem.select, 952 | highlightedObject = calendarItem.highlight, 953 | viewsetObject = calendarItem.view, 954 | disabledCollection = calendarItem.disable, 955 | minLimitObject = calendarItem.min, 956 | maxLimitObject = calendarItem.max, 957 | 958 | 959 | // Create the calendar table head using a copy of weekday labels collection. 960 | // * We do a copy so we don't mutate the original array. 961 | tableHead = (function( collection ) { 962 | 963 | // If the first day should be Monday, move Sunday to the end. 964 | if ( settings.firstDay ) { 965 | collection.push( collection.shift() ) 966 | } 967 | 968 | // Create and return the table head group. 969 | return _.node( 970 | 'thead', 971 | _.node( 972 | 'tr', 973 | _.group({ 974 | min: 0, 975 | max: DAYS_IN_WEEK - 1, 976 | i: 1, 977 | node: 'th', 978 | item: function( counter ) { 979 | return [ 980 | collection[ counter ], 981 | settings.klass.weekdays 982 | ] 983 | } 984 | }) 985 | ) 986 | ) //endreturn 987 | })( ( settings.showWeekdaysFull ? settings.weekdaysFull : settings.weekdaysShort ).slice( 0 ) ), //tableHead 988 | 989 | 990 | // Create the nav for next/prev month. 991 | createMonthNav = function( next ) { 992 | 993 | // Otherwise, return the created month tag. 994 | return _.node( 995 | 'div', 996 | ' ', 997 | settings.klass[ 'nav' + ( next ? 'Next' : 'Prev' ) ] + ( 998 | 999 | // If the focused month is outside the range, disabled the button. 1000 | ( next && viewsetObject.year >= maxLimitObject.year && viewsetObject.month >= maxLimitObject.month ) || 1001 | ( !next && viewsetObject.year <= minLimitObject.year && viewsetObject.month <= minLimitObject.month ) ? 1002 | ' ' + settings.klass.navDisabled : '' 1003 | ), 1004 | 'data-nav=' + ( next || -1 ) 1005 | ) //endreturn 1006 | }, //createMonthNav 1007 | 1008 | 1009 | // Create the month label. 1010 | createMonthLabel = function( monthsCollection ) { 1011 | 1012 | // If there are months to select, add a dropdown menu. 1013 | if ( settings.selectMonths ) { 1014 | 1015 | return _.node( 'select', _.group({ 1016 | min: 0, 1017 | max: 11, 1018 | i: 1, 1019 | node: 'option', 1020 | item: function( loopedMonth ) { 1021 | 1022 | return [ 1023 | 1024 | // The looped month and no classes. 1025 | monthsCollection[ loopedMonth ], 0, 1026 | 1027 | // Set the value and selected index. 1028 | 'value=' + loopedMonth + 1029 | ( viewsetObject.month == loopedMonth ? ' selected' : '' ) + 1030 | ( 1031 | ( 1032 | ( viewsetObject.year == minLimitObject.year && loopedMonth < minLimitObject.month ) || 1033 | ( viewsetObject.year == maxLimitObject.year && loopedMonth > maxLimitObject.month ) 1034 | ) ? 1035 | ' disabled' : '' 1036 | ) 1037 | ] 1038 | } 1039 | }), settings.klass.selectMonth, isOpen ? '' : 'disabled' ) 1040 | } 1041 | 1042 | // If there's a need for a month selector 1043 | return _.node( 'div', monthsCollection[ viewsetObject.month ], settings.klass.month ) 1044 | }, //createMonthLabel 1045 | 1046 | 1047 | // Create the year label. 1048 | createYearLabel = function() { 1049 | 1050 | var focusedYear = viewsetObject.year, 1051 | 1052 | // If years selector is set to a literal "true", set it to 5. Otherwise 1053 | // divide in half to get half before and half after focused year. 1054 | numberYears = settings.selectYears === true ? 5 : ~~( settings.selectYears / 2 ) 1055 | 1056 | // If there are years to select, add a dropdown menu. 1057 | if ( numberYears ) { 1058 | 1059 | var 1060 | minYear = minLimitObject.year, 1061 | maxYear = maxLimitObject.year, 1062 | lowestYear = focusedYear - numberYears, 1063 | highestYear = focusedYear + numberYears 1064 | 1065 | // If the min year is greater than the lowest year, increase the highest year 1066 | // by the difference and set the lowest year to the min year. 1067 | if ( minYear > lowestYear ) { 1068 | highestYear += minYear - lowestYear 1069 | lowestYear = minYear 1070 | } 1071 | 1072 | // If the max year is less than the highest year, decrease the lowest year 1073 | // by the lower of the two: available and needed years. Then set the 1074 | // highest year to the max year. 1075 | if ( maxYear < highestYear ) { 1076 | 1077 | var availableYears = lowestYear - minYear, 1078 | neededYears = highestYear - maxYear 1079 | 1080 | lowestYear -= availableYears > neededYears ? neededYears : availableYears 1081 | highestYear = maxYear 1082 | } 1083 | 1084 | return _.node( 'select', _.group({ 1085 | min: lowestYear, 1086 | max: highestYear, 1087 | i: 1, 1088 | node: 'option', 1089 | item: function( loopedYear ) { 1090 | return [ 1091 | 1092 | // The looped year and no classes. 1093 | loopedYear, 0, 1094 | 1095 | // Set the value and selected index. 1096 | 'value=' + loopedYear + ( focusedYear == loopedYear ? ' selected' : '' ) 1097 | ] 1098 | } 1099 | }), settings.klass.selectYear, isOpen ? '' : 'disabled' ) 1100 | } 1101 | 1102 | // Otherwise just return the year focused 1103 | return _.node( 'div', focusedYear, settings.klass.year ) 1104 | } //createYearLabel 1105 | 1106 | 1107 | // Create and return the entire calendar. 1108 | return _.node( 1109 | 'div', 1110 | createMonthNav() + createMonthNav( 1 ) + 1111 | createMonthLabel( settings.showMonthsShort ? settings.monthsShort : settings.monthsFull ) + 1112 | createYearLabel(), 1113 | settings.klass.header 1114 | ) + _.node( 1115 | 'table', 1116 | tableHead + 1117 | _.node( 1118 | 'tbody', 1119 | _.group({ 1120 | min: 0, 1121 | max: WEEKS_IN_CALENDAR - 1, 1122 | i: 1, 1123 | node: 'tr', 1124 | item: function( rowCounter ) { 1125 | 1126 | // If Monday is the first day and the month starts on Sunday, shift the date back a week. 1127 | var shiftDateBy = settings.firstDay && calendar.create([ viewsetObject.year, viewsetObject.month, 1 ]).day === 0 ? -7 : 0 1128 | 1129 | return [ 1130 | _.group({ 1131 | min: DAYS_IN_WEEK * rowCounter - viewsetObject.day + shiftDateBy + 1, // Add 1 for weekday 0index 1132 | max: function() { 1133 | return this.min + DAYS_IN_WEEK - 1 1134 | }, 1135 | i: 1, 1136 | node: 'td', 1137 | item: function( targetDate ) { 1138 | 1139 | // Convert the time date from a relative date to a target date. 1140 | targetDate = calendar.create([ viewsetObject.year, viewsetObject.month, targetDate + ( settings.firstDay ? 1 : 0 ) ]) 1141 | 1142 | var isSelected = selectedObject && selectedObject.pick == targetDate.pick, 1143 | isHighlighted = highlightedObject && highlightedObject.pick == targetDate.pick, 1144 | isDisabled = disabledCollection && calendar.disabled( targetDate ) || targetDate.pick < minLimitObject.pick || targetDate.pick > maxLimitObject.pick 1145 | 1146 | return [ 1147 | _.node( 1148 | 'div', 1149 | targetDate.date, 1150 | (function( klasses ) { 1151 | 1152 | // Add the `infocus` or `outfocus` classes based on month in view. 1153 | klasses.push( viewsetObject.month == targetDate.month ? settings.klass.infocus : settings.klass.outfocus ) 1154 | 1155 | // Add the `today` class if needed. 1156 | if ( nowObject.pick == targetDate.pick ) { 1157 | klasses.push( settings.klass.now ) 1158 | } 1159 | 1160 | // Add the `selected` class if something's selected and the time matches. 1161 | if ( isSelected ) { 1162 | klasses.push( settings.klass.selected ) 1163 | } 1164 | 1165 | // Add the `highlighted` class if something's highlighted and the time matches. 1166 | if ( isHighlighted ) { 1167 | klasses.push( settings.klass.highlighted ) 1168 | } 1169 | 1170 | // Add the `disabled` class if something's disabled and the object matches. 1171 | if ( isDisabled ) { 1172 | klasses.push( settings.klass.disabled ) 1173 | } 1174 | 1175 | return klasses.join( ' ' ) 1176 | })([ settings.klass.day ]), 1177 | 'data-pick=' + targetDate.pick + ' ' + _.ariaAttr({ 1178 | role: 'button', 1179 | controls: calendar.$node[0].id, 1180 | checked: isSelected && calendar.$node[0].value === _.trigger( 1181 | calendar.formats.toString, 1182 | calendar, 1183 | [ settings.format, targetDate ] 1184 | ) ? true : null, 1185 | activedescendant: isHighlighted ? true : null, 1186 | disabled: isDisabled ? true : null 1187 | }) 1188 | ) 1189 | ] //endreturn 1190 | } 1191 | }) 1192 | ] //endreturn 1193 | } 1194 | }) 1195 | ), 1196 | settings.klass.table 1197 | ) + 1198 | 1199 | // * For Firefox forms to submit, make sure to set the buttons’ `type` attributes as “button”. 1200 | _.node( 1201 | 'div', 1202 | _.node( 'button', settings.today, settings.klass.buttonToday, 'type=button data-pick=' + nowObject.pick + ( isOpen ? '' : ' disabled' ) ) + 1203 | _.node( 'button', settings.clear, settings.klass.buttonClear, 'type=button data-clear=1' + ( isOpen ? '' : ' disabled' ) ) + 1204 | _.node( 'button', settings.close, settings.klass.buttonClose, 'type=button data-close=true ' + ( isOpen ? '' : ' disabled' ) ), 1205 | settings.klass.footer 1206 | ) //endreturn 1207 | } //DatePicker.prototype.nodes 1208 | 1209 | 1210 | 1211 | 1212 | /** 1213 | * The date picker defaults. 1214 | */ 1215 | DatePicker.defaults = (function( prefix ) { 1216 | 1217 | return { 1218 | 1219 | // Months and weekdays 1220 | monthsFull: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], 1221 | monthsShort: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], 1222 | weekdaysFull: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], 1223 | weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ], 1224 | 1225 | // Today and clear 1226 | today: 'Today', 1227 | clear: 'Clear', 1228 | close: 'Close', 1229 | 1230 | // The format to show on the `input` element 1231 | format: 'd mmmm, yyyy', 1232 | 1233 | // Classes 1234 | klass: { 1235 | 1236 | table: prefix + 'table', 1237 | 1238 | header: prefix + 'header', 1239 | 1240 | navPrev: prefix + 'nav--prev', 1241 | navNext: prefix + 'nav--next', 1242 | navDisabled: prefix + 'nav--disabled', 1243 | 1244 | month: prefix + 'month', 1245 | year: prefix + 'year', 1246 | 1247 | selectMonth: prefix + 'select--month', 1248 | selectYear: prefix + 'select--year', 1249 | 1250 | weekdays: prefix + 'weekday', 1251 | 1252 | day: prefix + 'day', 1253 | disabled: prefix + 'day--disabled', 1254 | selected: prefix + 'day--selected', 1255 | highlighted: prefix + 'day--highlighted', 1256 | now: prefix + 'day--today', 1257 | infocus: prefix + 'day--infocus', 1258 | outfocus: prefix + 'day--outfocus', 1259 | 1260 | footer: prefix + 'footer', 1261 | 1262 | buttonClear: prefix + 'button--clear', 1263 | buttonClose: prefix + 'button--close', 1264 | buttonToday: prefix + 'button--today' 1265 | } 1266 | } 1267 | })( Picker.klasses().picker + '__' ) 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | /** 1274 | * Extend the picker to add the date picker. 1275 | */ 1276 | Picker.extend( 'pickadate', DatePicker ) 1277 | 1278 | 1279 | })); 1280 | 1281 | 1282 | 1283 | -------------------------------------------------------------------------------- /src/picker.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * pickadate.js v3.4.0, 2014/02/15 4 | * By Amsul, http://amsul.ca 5 | * Hosted on http://amsul.github.io/pickadate.js 6 | * Licensed under MIT 7 | */ 8 | 9 | (function ( factory ) { 10 | 11 | // Register as an anonymous module. 12 | if ( typeof define === 'function' && define.amd ) 13 | define( 'picker', ['angular'], factory ) 14 | 15 | // Or using browser globals. 16 | else this.Picker = factory( angular ) 17 | 18 | }(function( $ ) { 19 | 20 | var $document = angular.element( document ) 21 | 22 | 23 | /** 24 | * The picker constructor that creates a blank picker. 25 | */ 26 | function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) { 27 | 28 | // If there’s no element, return the picker constructor. 29 | if ( !ELEMENT ) return PickerConstructor 30 | 31 | var SETTINGS; 32 | 33 | // Merge the defaults and options passed. 34 | if (COMPONENT) { 35 | SETTINGS = COMPONENT.defaults; 36 | angular.extend(SETTINGS, OPTIONS); 37 | } else { 38 | SETTINGS = OPTIONS || {}; 39 | } 40 | 41 | // Merge the default classes with the settings classes. 42 | var CLASSES = PickerConstructor.klasses(); 43 | angular.extend(CLASSES, SETTINGS.klass); 44 | 45 | 46 | var 47 | // The state of the picker. 48 | STATE = { 49 | id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) ) 50 | }, 51 | 52 | // The element node wrapper into a jQuery object. 53 | $ELEMENT = angular.element(ELEMENT), 54 | 55 | 56 | // Pseudo picker constructor. 57 | PickerInstance = function() { 58 | return this.start() 59 | }, 60 | 61 | 62 | // The picker prototype. 63 | P = PickerInstance.prototype = { 64 | 65 | constructor: PickerInstance, 66 | 67 | $node: $ELEMENT, 68 | 69 | 70 | /** 71 | * Initialize everything 72 | */ 73 | start: function() { 74 | 75 | // If it’s already started, do nothing. 76 | if ( STATE && STATE.start ) return P 77 | 78 | 79 | // Update the picker states. 80 | STATE.methods = {} 81 | STATE.start = true 82 | STATE.open = false 83 | STATE.type = ELEMENT.type 84 | 85 | 86 | // Confirm focus state, convert into text input to remove UA stylings, 87 | // and set as readonly to prevent keyboard popup. 88 | ELEMENT.autofocus = ELEMENT == document.activeElement 89 | ELEMENT.type = 'text' 90 | ELEMENT.readOnly = !SETTINGS.editable 91 | ELEMENT.id = ELEMENT.id || STATE.id 92 | 93 | 94 | // Create a new picker component with the settings. 95 | P.component = new COMPONENT(P, SETTINGS) 96 | 97 | // Create the picker root with a holder and then prepare it. 98 | P.$root = angular.element( PickerConstructor._.node('div', createWrappedComponent(), CLASSES.picker, 'id="' + ELEMENT.id + '_root"') ) 99 | prepareElementRoot() 100 | 101 | 102 | // If there’s a format for the hidden input element, create the element. 103 | if ( SETTINGS.formatSubmit ) { 104 | prepareElementHidden() 105 | } 106 | 107 | 108 | // Prepare the input element. 109 | prepareElement() 110 | 111 | 112 | // Insert the root as specified in the settings. 113 | if ( SETTINGS.container ) { 114 | angular.element( SETTINGS.container ).append( P.$root ) 115 | } 116 | else { 117 | $ELEMENT.after( P.$root ) 118 | } 119 | 120 | 121 | // Bind the default component and settings events. 122 | P.on({ 123 | start: P.component.onStart, 124 | render: P.component.onRender, 125 | stop: P.component.onStop, 126 | open: P.component.onOpen, 127 | close: P.component.onClose, 128 | set: P.component.onSet 129 | }).on({ 130 | start: SETTINGS.onStart, 131 | render: SETTINGS.onRender, 132 | stop: SETTINGS.onStop, 133 | open: SETTINGS.onOpen, 134 | close: SETTINGS.onClose, 135 | set: SETTINGS.onSet 136 | }) 137 | 138 | 139 | // If the element has autofocus, open the picker. 140 | if ( ELEMENT.autofocus ) { 141 | P.open() 142 | } 143 | 144 | 145 | // Trigger queued the “start” and “render” events. 146 | return P.trigger( 'start' ).trigger( 'render' ) 147 | }, //start 148 | 149 | changeSettings: function (options) { 150 | angular.extend(SETTINGS, options); 151 | this.stop(); 152 | this.start(); 153 | }, 154 | 155 | /** 156 | * Render a new picker 157 | */ 158 | render: function( entireComponent ) { 159 | 160 | // Insert a new component holder in the root or box. 161 | if ( entireComponent ) P.$root.html( createWrappedComponent() ) 162 | else angular.element(P.$root[0].querySelectorAll( '.' + CLASSES.box )).html( P.component.nodes( STATE.open ) ) 163 | 164 | P.attachLiveEvents(); 165 | // Trigger the queued “render” events. 166 | return P.trigger( 'render' ) 167 | }, //render 168 | 169 | 170 | /** 171 | * Destroy everything 172 | */ 173 | stop: function() { 174 | 175 | // If it’s already stopped, do nothing. 176 | if ( !STATE.start ) return P 177 | 178 | // Then close the picker. 179 | P.close() 180 | 181 | // Remove the hidden field. 182 | if ( P._hidden ) { 183 | P._hidden.parentNode.removeChild( P._hidden ) 184 | } 185 | 186 | // Remove the root. 187 | P.$root.remove() 188 | 189 | // Remove the input class, remove the stored data, and unbind 190 | // the events (after a tick for IE - see `P.close`). 191 | $ELEMENT.removeClass( CLASSES.input ).removeData( NAME ) 192 | setTimeout( function() { 193 | $ELEMENT.off( '.' + STATE.id ) 194 | }, 0) 195 | 196 | // Restore the element state 197 | ELEMENT.type = STATE.type 198 | ELEMENT.readOnly = false 199 | 200 | // Trigger the queued “stop” events. 201 | P.trigger( 'stop' ) 202 | 203 | // Reset the picker states. 204 | STATE.methods = {} 205 | STATE.start = false 206 | 207 | return P 208 | }, //stop 209 | 210 | 211 | /* 212 | * Open up the picker 213 | */ 214 | open: function( dontGiveFocus ) { 215 | 216 | // If it’s already open, do nothing. 217 | if ( STATE.open ) return P 218 | 219 | 220 | // Add the “active” class. 221 | $ELEMENT.addClass( CLASSES.active ) 222 | aria( ELEMENT, 'expanded', true ) 223 | 224 | // Add the “opened” class to the picker root. 225 | P.$root.addClass( CLASSES.opened ) 226 | aria( P.$root[0], 'hidden', false ) 227 | 228 | // If we have to give focus, bind the element and doc events. 229 | if ( dontGiveFocus !== false ) { 230 | 231 | // Set it as open. 232 | STATE.open = true 233 | 234 | // Pass focus to the element’s jQuery object. 235 | $ELEMENT.triggerHandler( 'focus' ) 236 | 237 | // Bind the document events. 238 | angular.element(document.querySelectorAll('#' + STATE.id)).off('click focusin').on('click focusin', function( event ) { 239 | var target = event.target; 240 | 241 | // If the target of the event is not the element, close the picker picker. 242 | // * Don’t worry about clicks or focusins on the root because those don’t bubble up. 243 | // Also, for Firefox, a click on an `option` element bubbles up directly 244 | // to the doc. So make sure the target wasn't the doc. 245 | // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling, 246 | // which causes the picker to unexpectedly close when right-clicking it. So make 247 | // sure the event wasn’t a right-click. 248 | if ( target != ELEMENT && target != document && event.which != 3 ) { 249 | 250 | // If the target was the holder that covers the screen, 251 | // keep the element focused to maintain tabindex. 252 | P.close( target === P.$root.children()[0] ) 253 | } 254 | 255 | }); 256 | 257 | angular.element(document.querySelectorAll('#' + STATE.id)).off('keydown').on('keydown', function( event ) { 258 | var 259 | // Get the keycode. 260 | keycode = event.keyCode, 261 | 262 | // Translate that to a selection change. 263 | keycodeToMove = P.component.key[ keycode ], 264 | 265 | // Grab the target. 266 | target = event.target 267 | 268 | 269 | // On escape, close the picker and give focus. 270 | if ( keycode == 27 ) { 271 | P.close( true ) 272 | } 273 | 274 | 275 | // Check if there is a key movement or “enter” keypress on the element. 276 | else if ( target == ELEMENT && ( keycodeToMove || keycode == 13 ) ) { 277 | 278 | // Prevent the default action to stop page movement. 279 | event.preventDefault() 280 | 281 | // Trigger the key movement action. 282 | if ( keycodeToMove ) { 283 | PickerConstructor._.trigger( P.component.key.go, P, [ PickerConstructor._.trigger( keycodeToMove ) ] ) 284 | } 285 | 286 | // On “enter”, if the highlighted item isn’t disabled, set the value and close. 287 | else if ( !angular.element(P.$root[0].querySelectorAll( '.' + CLASSES.highlighted )).hasClass( CLASSES.disabled ) ) { 288 | P.set( 'select', P.component.item.highlight ).close() 289 | } 290 | } 291 | 292 | 293 | // If the target is within the root and “enter” is pressed, 294 | // prevent the default action and trigger a click on the target instead. 295 | else if ( P.$root[0].contains(target) && keycode == 13 ) { 296 | event.preventDefault() 297 | target.click() 298 | } 299 | }) 300 | } 301 | 302 | // Trigger the queued “open” events. 303 | return P.trigger( 'open' ) 304 | }, //open 305 | 306 | 307 | /** 308 | * Close the picker 309 | */ 310 | close: function( giveFocus ) { 311 | 312 | // If we need to give focus, do it before changing states. 313 | if ( giveFocus ) { 314 | // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :| 315 | // The focus is triggered *after* the close has completed - causing it 316 | // to open again. So unbind and rebind the event at the next tick. 317 | $ELEMENT.off( 'focus.' + STATE.id ); 318 | $ELEMENT.triggerHandler( 'focus' ); 319 | setTimeout( function() { 320 | angular.element(document.querySelectorAll('#' + STATE.id)).off( 'focus', focusToOpen ); 321 | angular.element(document.querySelectorAll('#' + STATE.id)).on( 'focus', focusToOpen ); 322 | }, 0 ) 323 | } 324 | 325 | // Remove the “active” class. 326 | $ELEMENT.removeClass( CLASSES.active ) 327 | aria( ELEMENT, 'expanded', false ) 328 | 329 | // Remove the “opened” and “focused” class from the picker root. 330 | P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused ) 331 | aria( P.$root[0], 'hidden', true ) 332 | aria( P.$root[0], 'selected', false ) 333 | 334 | // If it’s already closed, do nothing more. 335 | if ( !STATE.open ) return P 336 | 337 | // Set it as closed. 338 | setTimeout(function () { 339 | STATE.open = false; 340 | }, 0); 341 | 342 | // Unbind the document events. 343 | $document.off( '.' + STATE.id ) 344 | 345 | // Trigger the queued “close” events. 346 | return P.trigger( 'close' ) 347 | }, //close 348 | 349 | 350 | /** 351 | * Clear the values 352 | */ 353 | clear: function() { 354 | return P.set( 'clear' ) 355 | }, //clear 356 | 357 | 358 | /** 359 | * Set something 360 | */ 361 | set: function( thing, value, options ) { 362 | 363 | var thingItem, thingValue, 364 | thingIsObject = angular.isObject( thing ), 365 | thingObject = thingIsObject ? thing : {} 366 | 367 | // Make sure we have usable options. 368 | options = thingIsObject && angular.isObject( value ) ? value : options || {} 369 | 370 | if ( thing ) { 371 | 372 | // If the thing isn’t an object, make it one. 373 | if ( !thingIsObject ) { 374 | thingObject[ thing ] = value 375 | } 376 | 377 | // Go through the things of items to set. 378 | for ( thingItem in thingObject ) { 379 | 380 | // Grab the value of the thing. 381 | thingValue = thingObject[ thingItem ] 382 | 383 | // First, if the item exists and there’s a value, set it. 384 | if ( thingItem in P.component.item ) { 385 | P.component.set( thingItem, thingValue, options ) 386 | } 387 | 388 | // Then, check to update the element value and broadcast a change. 389 | if ( thingItem == 'select' || thingItem == 'clear' ) { 390 | $ELEMENT[0].value = thingItem == 'clear' ? 391 | '' : P.get( thingItem, SETTINGS.format ); 392 | $ELEMENT.triggerHandler('change'); 393 | } 394 | } 395 | 396 | // Render a new picker. 397 | P.render() 398 | } 399 | 400 | // When the method isn’t muted, trigger queued “set” events and pass the `thingObject`. 401 | return options.muted ? P : P.trigger( 'set', thingObject ) 402 | }, //set 403 | 404 | 405 | /** 406 | * Get something 407 | */ 408 | get: function( thing, format ) { 409 | 410 | // Make sure there’s something to get. 411 | thing = thing || 'value' 412 | 413 | // If a picker state exists, return that. 414 | if ( STATE[ thing ] != null ) { 415 | return STATE[ thing ] 416 | } 417 | 418 | // Return the value, if that. 419 | if ( thing == 'value' ) { 420 | return ELEMENT.value 421 | } 422 | 423 | // Check if a component item exists, return that. 424 | if ( thing in P.component.item ) { 425 | if ( typeof format == 'string' ) { 426 | return PickerConstructor._.trigger( 427 | P.component.formats.toString, 428 | P.component, 429 | [ format, P.component.get( thing ) ] 430 | ) 431 | } 432 | return P.component.get( thing ) 433 | } 434 | }, //get 435 | 436 | 437 | 438 | /** 439 | * Bind events on the things. 440 | */ 441 | on: function( thing, method ) { 442 | 443 | var thingName, thingMethod, 444 | thingIsObject = angular.isObject( thing ), 445 | thingObject = thingIsObject ? thing : {} 446 | 447 | if ( thing ) { 448 | 449 | // If the thing isn’t an object, make it one. 450 | if ( !thingIsObject ) { 451 | thingObject[ thing ] = method 452 | } 453 | 454 | // Go through the things to bind to. 455 | for ( thingName in thingObject ) { 456 | 457 | // Grab the method of the thing. 458 | thingMethod = thingObject[ thingName ] 459 | 460 | // Make sure the thing methods collection exists. 461 | STATE.methods[ thingName ] = STATE.methods[ thingName ] || [] 462 | 463 | // Add the method to the relative method collection. 464 | STATE.methods[ thingName ].push( thingMethod ) 465 | } 466 | } 467 | 468 | return P 469 | }, //on 470 | 471 | 472 | 473 | /** 474 | * Unbind events on the things. 475 | */ 476 | off: function() { 477 | var i, thingName, 478 | names = arguments; 479 | for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) { 480 | thingName = names[i] 481 | if ( thingName in STATE.methods ) { 482 | delete STATE.methods[thingName] 483 | } 484 | } 485 | return P 486 | }, 487 | 488 | 489 | /** 490 | * Fire off method events. 491 | */ 492 | trigger: function( name, data ) { 493 | var methodList = STATE.methods[ name ] 494 | if ( methodList ) { 495 | methodList.map( function( method ) { 496 | PickerConstructor._.trigger( method, P, [ data ] ) 497 | }) 498 | } 499 | return P 500 | } //trigger 501 | } //PickerInstance.prototype 502 | 503 | 504 | /** 505 | * Wrap the picker holder components together. 506 | */ 507 | function createWrappedComponent() { 508 | 509 | // Create a picker wrapper holder 510 | return PickerConstructor._.node( 'div', 511 | 512 | // Create a picker wrapper node 513 | PickerConstructor._.node( 'div', 514 | 515 | // Create a picker frame 516 | PickerConstructor._.node( 'div', 517 | 518 | // Create a picker box node 519 | PickerConstructor._.node( 'div', 520 | 521 | // Create the components nodes. 522 | P.component.nodes( STATE.open ), 523 | 524 | // The picker box class 525 | CLASSES.box 526 | ), 527 | 528 | // Picker wrap class 529 | CLASSES.wrap 530 | ), 531 | 532 | // Picker frame class 533 | CLASSES.frame 534 | ), 535 | 536 | // Picker holder class 537 | CLASSES.holder 538 | ) //endreturn 539 | } //createWrappedComponent 540 | 541 | 542 | 543 | /** 544 | * Prepare the input element with all bindings. 545 | */ 546 | function prepareElement() { 547 | // Store the picker data by component name. 548 | $ELEMENT.data(NAME, P); 549 | 550 | // Add the “input” class name. 551 | $ELEMENT.addClass(CLASSES.input) 552 | 553 | // If there’s a `data-value`, update the value of the element. 554 | $ELEMENT[0].value = $ELEMENT.attr('data-value') ? 555 | P.get('select', SETTINGS.format) : 556 | ELEMENT.value; 557 | 558 | // On focus/click, open the picker and adjust the root “focused” state. 559 | 560 | angular.element(document.querySelectorAll('#' + STATE.id)).off( 'focus', focusToOpen ); 561 | angular.element(document.querySelectorAll('#' + STATE.id)).on('focus', focusToOpen); 562 | 563 | // Only bind keydown events if the element isn’t editable. 564 | if ( !SETTINGS.editable ) { 565 | 566 | // Handle keyboard event based on the picker being opened or not. 567 | angular.element(document.querySelectorAll('#' + STATE.id)).on('keydown', function(event) { 568 | 569 | var keycode = event.keyCode, 570 | 571 | // Check if one of the delete keys was pressed. 572 | isKeycodeDelete = /^(8|46)$/.test(keycode) 573 | 574 | // For some reason IE clears the input value on “escape”. 575 | if ( keycode == 27 ) { 576 | P.close() 577 | return false 578 | } 579 | 580 | // Check if `space` or `delete` was pressed or the picker is closed with a key movement. 581 | if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) { 582 | 583 | // Prevent it from moving the page and bubbling to doc. 584 | event.preventDefault() 585 | event.stopPropagation() 586 | 587 | // If `delete` was pressed, clear the values and close the picker. 588 | // Otherwise open the picker. 589 | if ( isKeycodeDelete ) { P.clear().close() } 590 | else { P.open() } 591 | } 592 | }) 593 | } 594 | 595 | 596 | // Update the aria attributes. 597 | aria(ELEMENT, { 598 | haspopup: true, 599 | expanded: false, 600 | readonly: false, 601 | owns: ELEMENT.id + '_root' + (P._hidden ? ' ' + P._hidden.id : '') 602 | }) 603 | } 604 | 605 | 606 | /** 607 | * Prepare the root picker element with all bindings. 608 | */ 609 | function prepareElementRoot() { 610 | // When something within the root is focused, stop from bubbling 611 | // to the doc and remove the “focused” state from the root. 612 | P.$root.on('focusin', function( event ) { 613 | P.$root.removeClass( CLASSES.focused ) 614 | aria( P.$root[0], 'selected', false ) 615 | event.stopPropagation() 616 | }); 617 | 618 | // When something within the root holder is clicked, stop it 619 | // from bubbling to the doc. 620 | P.$root.on('mousedown click', function( event ) { 621 | 622 | var target = event.target 623 | 624 | // Make sure the target isn’t the root holder so it can bubble up. 625 | if ( target != P.$root.children()[ 0 ] ) { 626 | 627 | event.stopPropagation() 628 | 629 | // * For mousedown events, cancel the default action in order to 630 | // prevent cases where focus is shifted onto external elements 631 | // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120). 632 | // Also, for Firefox, don’t prevent action on the `option` element. 633 | if ( event.type == 'mousedown' && angular.element( target )[0].tagName !== 'input' && target.nodeName != 'SELECT' && target.nodeName != 'OPTION' ) { 634 | 635 | event.preventDefault() 636 | 637 | // Re-focus onto the element so that users can click away 638 | // from elements focused within the picker. 639 | ELEMENT.focus() 640 | } 641 | } else if ( event.type == 'click' && P.get('open') ) { 642 | P.close(); 643 | } 644 | }); 645 | 646 | P.attachLiveEvents = function() { 647 | // If there’s a click on an actionable element, carry out the actions. 648 | angular.element(P.$root[0].querySelectorAll('[data-pick], [data-nav], [data-clear], [data-close]')).off('click').on('click', function() { 649 | var $target = angular.element( this ), 650 | targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ), 651 | 652 | // * For IE, non-focusable elements can be active elements as well 653 | // (http://stackoverflow.com/a/2684561). 654 | activeElement = document.activeElement 655 | activeElement = activeElement && ( activeElement.type || activeElement.href ) && activeElement 656 | 657 | // If it’s disabled or nothing inside is actively focused, re-focus the element. 658 | if ( targetDisabled || activeElement && !P.$root[0].contains(activeElement) ) { 659 | ELEMENT.focus(); 660 | angular.element(ELEMENT).on( 'click', focusToOpen); 661 | var unClick = function(){ 662 | angular.element(ELEMENT).off( 'click', focusToOpen); 663 | angular.element(ELEMENT).off( 'blur', unClick); 664 | } 665 | angular.element(ELEMENT).off( 'blur', unClick); 666 | angular.element(ELEMENT).on( 'blur', unClick); 667 | } 668 | 669 | // If something is superficially changed, update the `highlight` based on the `nav`. 670 | if ( $target.attr('data-nav') && !targetDisabled ) { 671 | P.set( 'highlight', P.component.item.highlight, { nav: parseInt($target.attr('data-nav')) } ) 672 | P.attachLiveEvents(); 673 | } 674 | 675 | // If something is picked, set `select` then close with focus. 676 | else if ( PickerConstructor._.isInteger( parseInt($target.attr('data-pick')) ) && !targetDisabled ) { 677 | P.set( 'select', parseInt($target.attr('data-pick')) ).close( true ) 678 | P.attachLiveEvents(); 679 | } 680 | 681 | // If a “clear” button is pressed, empty the values and close with focus. 682 | else if ( $target.attr('data-clear') ) { 683 | P.clear().close( true ) 684 | P.attachLiveEvents(); 685 | } 686 | 687 | // If a "close" button is pressed, close with focus. 688 | else if ( $target.attr('data-close') ) { 689 | P.close( true ); 690 | P.attachLiveEvents(); 691 | } 692 | 693 | }); 694 | } 695 | 696 | aria( P.$root[0], 'hidden', true ) 697 | } 698 | 699 | 700 | /** 701 | * Prepare the hidden input element along with all bindings. 702 | */ 703 | function prepareElementHidden() { 704 | 705 | var id = [ 706 | typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '', 707 | typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' 708 | ] 709 | 710 | P._hidden = angular.element( 711 | '' 726 | )[0] 727 | 728 | $ELEMENT. 729 | 730 | // If the value changes, update the hidden input with the correct format. 731 | on('change.' + STATE.id, function() { 732 | P._hidden.value = ELEMENT.value ? 733 | P.get('select', SETTINGS.formatSubmit) : 734 | '' 735 | }). 736 | 737 | // Insert the hidden input after the element. 738 | after(P._hidden) 739 | } 740 | 741 | 742 | // Separated for IE 743 | function focusToOpen( event ) { 744 | 745 | // Stop the event from propagating to the doc. 746 | event.stopPropagation() 747 | 748 | // If it’s a focus event, add the “focused” class to the root. 749 | if ( event.type == 'focus' ) { 750 | P.$root.addClass( CLASSES.focused ) 751 | aria( P.$root[0], 'selected', true ) 752 | } 753 | 754 | // And then finally open the picker. 755 | P.open() 756 | } 757 | 758 | 759 | // Return a new picker instance. 760 | return new PickerInstance() 761 | } //PickerConstructor 762 | 763 | 764 | 765 | /** 766 | * The default classes and prefix to use for the HTML classes. 767 | */ 768 | PickerConstructor.klasses = function( prefix ) { 769 | prefix = prefix || 'picker' 770 | return { 771 | 772 | picker: prefix, 773 | opened: prefix + '--opened', 774 | focused: prefix + '--focused', 775 | 776 | input: prefix + '__input', 777 | active: prefix + '__input--active', 778 | 779 | holder: prefix + '__holder', 780 | 781 | frame: prefix + '__frame', 782 | wrap: prefix + '__wrap', 783 | 784 | box: prefix + '__box' 785 | } 786 | } //PickerConstructor.klasses 787 | 788 | 789 | 790 | /** 791 | * PickerConstructor helper methods. 792 | */ 793 | PickerConstructor._ = { 794 | 795 | /** 796 | * Create a group of nodes. Expects: 797 | * ` 798 | { 799 | min: {Integer}, 800 | max: {Integer}, 801 | i: {Integer}, 802 | node: {String}, 803 | item: {Function} 804 | } 805 | * ` 806 | */ 807 | group: function( groupObject ) { 808 | 809 | var 810 | // Scope for the looped object 811 | loopObjectScope, 812 | 813 | // Create the nodes list 814 | nodesList = '', 815 | 816 | // The counter starts from the `min` 817 | counter = PickerConstructor._.trigger( groupObject.min, groupObject ) 818 | 819 | 820 | // Loop from the `min` to `max`, incrementing by `i` 821 | for ( ; counter <= PickerConstructor._.trigger( groupObject.max, groupObject, [ counter ] ); counter += groupObject.i ) { 822 | 823 | // Trigger the `item` function within scope of the object 824 | loopObjectScope = PickerConstructor._.trigger( groupObject.item, groupObject, [ counter ] ) 825 | 826 | // Splice the subgroup and create nodes out of the sub nodes 827 | nodesList += PickerConstructor._.node( 828 | groupObject.node, 829 | loopObjectScope[ 0 ], // the node 830 | loopObjectScope[ 1 ], // the classes 831 | loopObjectScope[ 2 ] // the attributes 832 | ) 833 | } 834 | 835 | // Return the list of nodes 836 | return nodesList 837 | }, //group 838 | 839 | 840 | /** 841 | * Create a dom node string 842 | */ 843 | node: function( wrapper, item, klass, attribute ) { 844 | 845 | // If the item is false-y, just return an empty string 846 | if ( !item ) return '' 847 | 848 | // If the item is an array, do a join 849 | item = $.isArray( item ) ? item.join( '' ) : item 850 | 851 | // Check for the class 852 | klass = klass ? ' class="' + klass + '"' : '' 853 | 854 | // Check for any attributes 855 | attribute = attribute ? ' ' + attribute : '' 856 | 857 | // Return the wrapped item 858 | return '<' + wrapper + klass + attribute + '>' + item + '' 859 | }, //node 860 | 861 | 862 | /** 863 | * Lead numbers below 10 with a zero. 864 | */ 865 | lead: function( number ) { 866 | return ( number < 10 ? '0': '' ) + number 867 | }, 868 | 869 | 870 | /** 871 | * Trigger a function otherwise return the value. 872 | */ 873 | trigger: function( callback, scope, args ) { 874 | return typeof callback == 'function' ? callback.apply( scope, args || [] ) : callback 875 | }, 876 | 877 | 878 | /** 879 | * If the second character is a digit, length is 2 otherwise 1. 880 | */ 881 | digits: function( string ) { 882 | return ( /\d/ ).test( string[ 1 ] ) ? 2 : 1 883 | }, 884 | 885 | 886 | /** 887 | * Tell if something is a date object. 888 | */ 889 | isDate: function( value ) { 890 | return {}.toString.call( value ).indexOf( 'Date' ) > -1 && this.isInteger( value.getDate() ) 891 | }, 892 | 893 | 894 | /** 895 | * Tell if something is an integer. 896 | */ 897 | isInteger: function( value ) { 898 | return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0 899 | }, 900 | 901 | 902 | /** 903 | * Create ARIA attribute strings. 904 | */ 905 | ariaAttr: ariaAttr 906 | } //PickerConstructor._ 907 | 908 | 909 | 910 | /** 911 | * Extend the picker with a component and defaults. 912 | */ 913 | PickerConstructor.extend = function( name, Component ) { 914 | 915 | // Extend jQuery. 916 | angular.element.prototype[ name ] = function( options, action ) { 917 | 918 | // Grab the component data. 919 | var componentData = this.data( name ) 920 | 921 | // If the picker is requested, return the data object. 922 | if ( options == 'picker' ) { 923 | return componentData 924 | } 925 | 926 | // If the component data exists and `options` is a string, carry out the action. 927 | if ( componentData && typeof options == 'string' ) { 928 | PickerConstructor._.trigger( componentData[ options ], componentData, [ action ] ) 929 | return this 930 | } 931 | 932 | // Otherwise go through each matched element and if the component 933 | // doesn’t exist, create a new picker using `this` element 934 | // and merging the defaults and options with a deep copy. 935 | for (var i = 0; i < this.length; i++) { 936 | var element = angular.element(this[i]); 937 | if ( !element.data( name ) ) { 938 | new PickerConstructor( element[0], name, Component, options ) 939 | } 940 | } 941 | } 942 | 943 | // Set the defaults. 944 | angular.element.prototype[ name ].defaults = Component.defaults 945 | } //PickerConstructor.extend 946 | 947 | 948 | 949 | function aria(element, attribute, value) { 950 | if ( angular.isObject(attribute) ) { 951 | for ( var key in attribute ) { 952 | ariaSet(element, key, attribute[key]) 953 | } 954 | } 955 | else { 956 | ariaSet(element, attribute, value) 957 | } 958 | } 959 | function ariaSet(element, attribute, value) { 960 | angular.element(element).attr( 961 | (attribute == 'role' ? '' : 'aria-') + attribute, 962 | value 963 | ) 964 | } 965 | function ariaAttr(attribute, data) { 966 | if ( !angular.isObject(attribute) ) { 967 | attribute = { attribute: data } 968 | } 969 | data = '' 970 | for ( var key in attribute ) { 971 | var attr = (key == 'role' ? '' : 'aria-') + key, 972 | attrVal = attribute[key] 973 | data += attrVal == null ? '' : attr + '="' + attribute[key] + '"' 974 | } 975 | return data 976 | } 977 | 978 | 979 | 980 | // Expose the picker constructor. 981 | return PickerConstructor 982 | 983 | 984 | })); 985 | 986 | 987 | 988 | -------------------------------------------------------------------------------- /src/picker.time.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Time picker for pickadate.js v3.4.0 4 | * http://amsul.github.io/pickadate.js/time.htm 5 | */ 6 | 7 | (function ( factory ) { 8 | 9 | // Register as an anonymous module. 10 | if ( typeof define == 'function' && define.amd ) 11 | define( ['picker','angular'], factory ) 12 | 13 | // Or using browser globals. 14 | else factory( Picker, angular ) 15 | 16 | }(function( Picker, angular ) { 17 | 18 | 19 | /** 20 | * Globals and constants 21 | */ 22 | var HOURS_IN_DAY = 24, 23 | MINUTES_IN_HOUR = 60, 24 | HOURS_TO_NOON = 12, 25 | MINUTES_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR, 26 | _ = Picker._ 27 | 28 | 29 | 30 | /** 31 | * The time picker constructor 32 | */ 33 | function TimePicker( picker, settings ) { 34 | 35 | var clock = this, 36 | elementValue = picker.$node[ 0 ].value, 37 | elementDataValue = picker.$node.data( 'value' ), 38 | valueString = elementDataValue || elementValue, 39 | formatString = elementDataValue ? settings.formatSubmit : settings.format 40 | 41 | clock.settings = settings 42 | clock.$node = picker.$node 43 | 44 | // The queue of methods that will be used to build item objects. 45 | clock.queue = { 46 | interval: 'i', 47 | min: 'measure create', 48 | max: 'measure create', 49 | now: 'now create', 50 | select: 'parse create validate', 51 | highlight: 'parse create validate', 52 | view: 'parse create validate', 53 | disable: 'deactivate', 54 | enable: 'activate' 55 | } 56 | 57 | // The component's item object. 58 | clock.item = {} 59 | 60 | clock.item.interval = settings.interval || 30 61 | clock.item.disable = ( settings.disable || [] ).slice( 0 ) 62 | clock.item.enable = -(function( collectionDisabled ) { 63 | return collectionDisabled[ 0 ] === true ? collectionDisabled.shift() : -1 64 | })( clock.item.disable ) 65 | 66 | clock. 67 | set( 'min', settings.min ). 68 | set( 'max', settings.max ). 69 | set( 'now' ) 70 | 71 | // When there’s a value, set the `select`, which in turn 72 | // also sets the `highlight` and `view`. 73 | if ( valueString ) { 74 | clock.set( 'select', valueString, { 75 | format: formatString, 76 | fromValue: !!elementValue 77 | }) 78 | } 79 | 80 | // If there’s no value, default to highlighting “today”. 81 | else { 82 | clock. 83 | set( 'select', null ). 84 | set( 'highlight', clock.item.now ) 85 | } 86 | 87 | // The keycode to movement mapping. 88 | clock.key = { 89 | 40: 1, // Down 90 | 38: -1, // Up 91 | 39: 1, // Right 92 | 37: -1, // Left 93 | go: function( timeChange ) { 94 | clock.set( 95 | 'highlight', 96 | clock.item.highlight.pick + timeChange * clock.item.interval, 97 | { interval: timeChange * clock.item.interval } 98 | ) 99 | this.render() 100 | } 101 | } 102 | 103 | 104 | // Bind some picker events. 105 | picker. 106 | on( 'render', function() { 107 | var $pickerHolder = picker.$root.children(), 108 | $viewset = $pickerHolder.find( '.' + settings.klass.viewset ) 109 | if ( $viewset.length ) { 110 | $pickerHolder[ 0 ].scrollTop = ~~$viewset.position().top - ( $viewset[ 0 ].clientHeight * 2 ) 111 | } 112 | }). 113 | on( 'open', function() { 114 | picker.$root.find( 'button' ).attr( 'disable', false ) 115 | }). 116 | on( 'close', function() { 117 | picker.$root.find( 'button' ).attr( 'disable', true ) 118 | }) 119 | 120 | } //TimePicker 121 | 122 | 123 | /** 124 | * Set a timepicker item object. 125 | */ 126 | TimePicker.prototype.set = function( type, value, options ) { 127 | 128 | var clock = this, 129 | clockItem = clock.item 130 | 131 | // If the value is `null` just set it immediately. 132 | if ( value === null ) { 133 | clockItem[ type ] = value 134 | return clock 135 | } 136 | 137 | // Otherwise go through the queue of methods, and invoke the functions. 138 | // Update this as the time unit, and set the final value as this item. 139 | // * In the case of `enable`, keep the queue but set `disable` instead. 140 | // And in the case of `flip`, keep the queue but set `enable` instead. 141 | clockItem[ ( type == 'enable' ? 'disable' : type == 'flip' ? 'enable' : type ) ] = clock.queue[ type ].split( ' ' ).map( function( method ) { 142 | value = clock[ method ]( type, value, options ) 143 | return value 144 | }).pop() 145 | 146 | // Check if we need to cascade through more updates. 147 | if ( type == 'select' ) { 148 | clock.set( 'highlight', clockItem.select, options ) 149 | } 150 | else if ( type == 'highlight' ) { 151 | clock.set( 'view', clockItem.highlight, options ) 152 | } 153 | else if ( type == 'interval' ) { 154 | clock. 155 | set( 'min', clockItem.min, options ). 156 | set( 'max', clockItem.max, options ) 157 | } 158 | else if ( type.match( /^(flip|min|max|disable|enable)$/ ) ) { 159 | if ( type == 'min' ) { 160 | clock.set( 'max', clockItem.max, options ) 161 | } 162 | if ( clockItem.select && clock.disabled( clockItem.select ) ) { 163 | clock.set( 'select', clockItem.select, options ) 164 | } 165 | if ( clockItem.highlight && clock.disabled( clockItem.highlight ) ) { 166 | clock.set( 'highlight', clockItem.highlight, options ) 167 | } 168 | } 169 | 170 | return clock 171 | } //TimePicker.prototype.set 172 | 173 | 174 | /** 175 | * Get a timepicker item object. 176 | */ 177 | TimePicker.prototype.get = function( type ) { 178 | return this.item[ type ] 179 | } //TimePicker.prototype.get 180 | 181 | 182 | /** 183 | * Create a picker time object. 184 | */ 185 | TimePicker.prototype.create = function( type, value, options ) { 186 | 187 | var clock = this 188 | 189 | // If there’s no value, use the type as the value. 190 | value = value === undefined ? type : value 191 | 192 | // If it’s a date object, convert it into an array. 193 | if ( _.isDate( value ) ) { 194 | value = [ value.getHours(), value.getMinutes() ] 195 | } 196 | 197 | // If it’s an object, use the “pick” value. 198 | if ( angular.isObject( value ) && _.isInteger( value.pick ) ) { 199 | value = value.pick 200 | } 201 | 202 | // If it’s an array, convert it into minutes. 203 | else if ( angular.isArray( value ) ) { 204 | value = +value[ 0 ] * MINUTES_IN_HOUR + (+value[ 1 ]) 205 | } 206 | 207 | // If no valid value is passed, set it to “now”. 208 | else if ( !_.isInteger( value ) ) { 209 | value = clock.now( type, value, options ) 210 | } 211 | 212 | // If we’re setting the max, make sure it’s greater than the min. 213 | if ( type == 'max' && value < clock.item.min.pick ) { 214 | value += MINUTES_IN_DAY 215 | } 216 | 217 | // If the value doesn’t fall directly on the interval, 218 | // add one interval to indicate it as “passed”. 219 | if ( type != 'min' && type != 'max' && (value - clock.item.min.pick) % clock.item.interval !== 0 ) { 220 | value += clock.item.interval 221 | } 222 | 223 | // Normalize it into a “reachable” interval. 224 | value = clock.normalize( type, value, options ) 225 | 226 | // Return the compiled object. 227 | return { 228 | 229 | // Divide to get hours from minutes. 230 | hour: ~~( HOURS_IN_DAY + value / MINUTES_IN_HOUR ) % HOURS_IN_DAY, 231 | 232 | // The remainder is the minutes. 233 | mins: ( MINUTES_IN_HOUR + value % MINUTES_IN_HOUR ) % MINUTES_IN_HOUR, 234 | 235 | // The time in total minutes. 236 | time: ( MINUTES_IN_DAY + value ) % MINUTES_IN_DAY, 237 | 238 | // Reference to the “relative” value to pick. 239 | pick: value 240 | } 241 | } //TimePicker.prototype.create 242 | 243 | 244 | /** 245 | * Create a range limit object using an array, date object, 246 | * literal “true”, or integer relative to another time. 247 | */ 248 | TimePicker.prototype.createRange = function( from, to ) { 249 | 250 | var clock = this, 251 | createTime = function( time ) { 252 | if ( time === true || angular.isArray( time ) || _.isDate( time ) ) { 253 | return clock.create( time ) 254 | } 255 | return time 256 | } 257 | 258 | // Create objects if possible. 259 | if ( !_.isInteger( from ) ) { 260 | from = createTime( from ) 261 | } 262 | if ( !_.isInteger( to ) ) { 263 | to = createTime( to ) 264 | } 265 | 266 | // Create relative times. 267 | if ( _.isInteger( from ) && angular.isObject( to ) ) { 268 | from = [ to.hour, to.mins + ( from * clock.settings.interval ) ]; 269 | } 270 | else if ( _.isInteger( to ) && angular.isObject( from ) ) { 271 | to = [ from.hour, from.mins + ( to * clock.settings.interval ) ]; 272 | } 273 | 274 | return { 275 | from: createTime( from ), 276 | to: createTime( to ) 277 | } 278 | } //TimePicker.prototype.createRange 279 | 280 | 281 | /** 282 | * Check if a time unit falls within a time range object. 283 | */ 284 | TimePicker.prototype.withinRange = function( range, timeUnit ) { 285 | range = this.createRange(range.from, range.to) 286 | return timeUnit.pick >= range.from.pick && timeUnit.pick <= range.to.pick 287 | } 288 | 289 | 290 | /** 291 | * Check if two time range objects overlap. 292 | */ 293 | TimePicker.prototype.overlapRanges = function( one, two ) { 294 | 295 | var clock = this 296 | 297 | // Convert the ranges into comparable times. 298 | one = clock.createRange( one.from, one.to ) 299 | two = clock.createRange( two.from, two.to ) 300 | 301 | return clock.withinRange( one, two.from ) || clock.withinRange( one, two.to ) || 302 | clock.withinRange( two, one.from ) || clock.withinRange( two, one.to ) 303 | } 304 | 305 | 306 | /** 307 | * Get the time relative to now. 308 | */ 309 | TimePicker.prototype.now = function( type, value/*, options*/ ) { 310 | 311 | var interval = this.item.interval, 312 | date = new Date(), 313 | nowMinutes = date.getHours() * MINUTES_IN_HOUR + date.getMinutes(), 314 | isValueInteger = _.isInteger( value ), 315 | isBelowInterval 316 | 317 | // Make sure “now” falls within the interval range. 318 | nowMinutes -= nowMinutes % interval 319 | 320 | // Check if the difference is less than the interval itself. 321 | isBelowInterval = value < 0 && interval * value + nowMinutes <= -interval 322 | 323 | // Add an interval because the time has “passed”. 324 | nowMinutes += type == 'min' && isBelowInterval ? 0 : interval 325 | 326 | // If the value is a number, adjust by that many intervals. 327 | if ( isValueInteger ) { 328 | nowMinutes += interval * ( 329 | isBelowInterval && type != 'max' ? 330 | value + 1 : 331 | value 332 | ) 333 | } 334 | 335 | // Return the final calculation. 336 | return nowMinutes 337 | } //TimePicker.prototype.now 338 | 339 | 340 | /** 341 | * Normalize minutes to be “reachable” based on the min and interval. 342 | */ 343 | TimePicker.prototype.normalize = function( type, value/*, options*/ ) { 344 | 345 | var interval = this.item.interval, 346 | minTime = this.item.min && this.item.min.pick || 0 347 | 348 | // If setting min time, don’t shift anything. 349 | // Otherwise get the value and min difference and then 350 | // normalize the difference with the interval. 351 | value -= type == 'min' ? 0 : ( value - minTime ) % interval 352 | 353 | // Return the adjusted value. 354 | return value 355 | } //TimePicker.prototype.normalize 356 | 357 | 358 | /** 359 | * Measure the range of minutes. 360 | */ 361 | TimePicker.prototype.measure = function( type, value, options ) { 362 | 363 | var clock = this 364 | 365 | // If it’s anything false-y, set it to the default. 366 | if ( !value ) { 367 | value = type == 'min' ? [ 0, 0 ] : [ HOURS_IN_DAY - 1, MINUTES_IN_HOUR - 1 ] 368 | } 369 | 370 | // If it’s a literal true, or an integer, make it relative to now. 371 | else if ( value === true || _.isInteger( value ) ) { 372 | value = clock.now( type, value, options ) 373 | } 374 | 375 | // If it’s an object already, just normalize it. 376 | else if ( angular.isObject( value ) && _.isInteger( value.pick ) ) { 377 | value = clock.normalize( type, value.pick, options ) 378 | } 379 | 380 | return value 381 | } ///TimePicker.prototype.measure 382 | 383 | 384 | /** 385 | * Validate an object as enabled. 386 | */ 387 | TimePicker.prototype.validate = function( type, timeObject, options ) { 388 | 389 | var clock = this, 390 | interval = options && options.interval ? options.interval : clock.item.interval 391 | 392 | // Check if the object is disabled. 393 | if ( clock.disabled( timeObject ) ) { 394 | 395 | // Shift with the interval until we reach an enabled time. 396 | timeObject = clock.shift( timeObject, interval ) 397 | } 398 | 399 | // Scope the object into range. 400 | timeObject = clock.scope( timeObject ) 401 | 402 | // Do a second check to see if we landed on a disabled min/max. 403 | // In that case, shift using the opposite interval as before. 404 | if ( clock.disabled( timeObject ) ) { 405 | timeObject = clock.shift( timeObject, interval * -1 ) 406 | } 407 | 408 | // Return the final object. 409 | return timeObject 410 | } //TimePicker.prototype.validate 411 | 412 | 413 | /** 414 | * Check if an object is disabled. 415 | */ 416 | TimePicker.prototype.disabled = function( timeToVerify ) { 417 | 418 | var clock = this, 419 | 420 | // Filter through the disabled times to check if this is one. 421 | isDisabledMatch = clock.item.disable.filter( function( timeToDisable ) { 422 | 423 | // If the time is a number, match the hours. 424 | if ( _.isInteger( timeToDisable ) ) { 425 | return timeToVerify.hour == timeToDisable 426 | } 427 | 428 | // If it’s an array, create the object and match the times. 429 | if ( angular.isArray( timeToDisable ) || _.isDate( timeToDisable ) ) { 430 | return timeToVerify.pick == clock.create( timeToDisable ).pick 431 | } 432 | 433 | // If it’s an object, match a time within the “from” and “to” range. 434 | if ( angular.isObject( timeToDisable ) ) { 435 | return clock.withinRange( timeToDisable, timeToVerify ) 436 | } 437 | }) 438 | 439 | // If this time matches a disabled time, confirm it’s not inverted. 440 | isDisabledMatch = isDisabledMatch.length && !isDisabledMatch.filter(function( timeToDisable ) { 441 | return angular.isArray( timeToDisable ) && timeToDisable[2] == 'inverted' || 442 | angular.isObject( timeToDisable ) && timeToDisable.inverted 443 | }).length 444 | 445 | // If the clock is "enabled" flag is flipped, flip the condition. 446 | return clock.item.enable === -1 ? !isDisabledMatch : isDisabledMatch || 447 | timeToVerify.pick < clock.item.min.pick || 448 | timeToVerify.pick > clock.item.max.pick 449 | } //TimePicker.prototype.disabled 450 | 451 | 452 | /** 453 | * Shift an object by an interval until we reach an enabled object. 454 | */ 455 | TimePicker.prototype.shift = function( timeObject, interval ) { 456 | 457 | var clock = this, 458 | minLimit = clock.item.min.pick, 459 | maxLimit = clock.item.max.pick/*, 460 | safety = 1000*/ 461 | 462 | interval = interval || clock.item.interval 463 | 464 | // Keep looping as long as the time is disabled. 465 | while ( /*safety &&*/ clock.disabled( timeObject ) ) { 466 | 467 | /*safety -= 1 468 | if ( !safety ) { 469 | throw 'Fell into an infinite loop while shifting to ' + timeObject.hour + ':' + timeObject.mins + '.' 470 | }*/ 471 | 472 | // Increase/decrease the time by the interval and keep looping. 473 | timeObject = clock.create( timeObject.pick += interval ) 474 | 475 | // If we've looped beyond the limits, break out of the loop. 476 | if ( timeObject.pick <= minLimit || timeObject.pick >= maxLimit ) { 477 | break 478 | } 479 | } 480 | 481 | // Return the final object. 482 | return timeObject 483 | } //TimePicker.prototype.shift 484 | 485 | 486 | /** 487 | * Scope an object to be within range of min and max. 488 | */ 489 | TimePicker.prototype.scope = function( timeObject ) { 490 | var minLimit = this.item.min.pick, 491 | maxLimit = this.item.max.pick 492 | return this.create( timeObject.pick > maxLimit ? maxLimit : timeObject.pick < minLimit ? minLimit : timeObject ) 493 | } //TimePicker.prototype.scope 494 | 495 | 496 | /** 497 | * Parse a string into a usable type. 498 | */ 499 | TimePicker.prototype.parse = function( type, value, options ) { 500 | 501 | var hour, minutes, isPM, item, parseValue, 502 | clock = this, 503 | parsingObject = {} 504 | 505 | if ( !value || _.isInteger( value ) || angular.isArray( value ) || _.isDate( value ) || angular.isObject( value ) && _.isInteger( value.pick ) ) { 506 | return value 507 | } 508 | 509 | // We need a `.format` to parse the value with. 510 | if ( !( options && options.format ) ) { 511 | options = options || {} 512 | options.format = clock.settings.format 513 | } 514 | 515 | // Convert the format into an array and then map through it. 516 | clock.formats.toArray( options.format ).map( function( label ) { 517 | 518 | var 519 | substring, 520 | 521 | // Grab the formatting label. 522 | formattingLabel = clock.formats[ label ], 523 | 524 | // The format length is from the formatting label function or the 525 | // label length without the escaping exclamation (!) mark. 526 | formatLength = formattingLabel ? 527 | _.trigger( formattingLabel, clock, [ value, parsingObject ] ) : 528 | label.replace( /^!/, '' ).length 529 | 530 | // If there's a format label, split the value up to the format length. 531 | // Then add it to the parsing object with appropriate label. 532 | if ( formattingLabel ) { 533 | substring = value.substr( 0, formatLength ) 534 | parsingObject[ label ] = substring.match(/^\d+$/) ? +substring : substring 535 | } 536 | 537 | // Update the time value as the substring from format length to end. 538 | value = value.substr( formatLength ) 539 | }) 540 | 541 | // Grab the hour and minutes from the parsing object. 542 | for ( item in parsingObject ) { 543 | parseValue = parsingObject[item] 544 | if ( _.isInteger(parseValue) ) { 545 | if ( item.match(/^(h|hh)$/i) ) { 546 | hour = parseValue 547 | if ( item == 'h' || item == 'hh' ) { 548 | hour %= 12 549 | } 550 | } 551 | else if ( item == 'i' ) { 552 | minutes = parseValue 553 | } 554 | } 555 | else if ( item.match(/^a$/i) && parseValue.match(/^p/i) && ('h' in parsingObject || 'hh' in parsingObject) ) { 556 | isPM = true 557 | } 558 | } 559 | 560 | // Calculate it in minutes and return. 561 | return (isPM ? hour + 12 : hour) * MINUTES_IN_HOUR + minutes 562 | } //TimePicker.prototype.parse 563 | 564 | 565 | /** 566 | * Various formats to display the object in. 567 | */ 568 | TimePicker.prototype.formats = { 569 | 570 | h: function( string, timeObject ) { 571 | 572 | // If there's string, then get the digits length. 573 | // Otherwise return the selected hour in "standard" format. 574 | return string ? _.digits( string ) : timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON 575 | }, 576 | hh: function( string, timeObject ) { 577 | 578 | // If there's a string, then the length is always 2. 579 | // Otherwise return the selected hour in "standard" format with a leading zero. 580 | return string ? 2 : _.lead( timeObject.hour % HOURS_TO_NOON || HOURS_TO_NOON ) 581 | }, 582 | H: function( string, timeObject ) { 583 | 584 | // If there's string, then get the digits length. 585 | // Otherwise return the selected hour in "military" format as a string. 586 | return string ? _.digits( string ) : '' + ( timeObject.hour % 24 ) 587 | }, 588 | HH: function( string, timeObject ) { 589 | 590 | // If there's string, then get the digits length. 591 | // Otherwise return the selected hour in "military" format with a leading zero. 592 | return string ? _.digits( string ) : _.lead( timeObject.hour % 24 ) 593 | }, 594 | i: function( string, timeObject ) { 595 | 596 | // If there's a string, then the length is always 2. 597 | // Otherwise return the selected minutes. 598 | return string ? 2 : _.lead( timeObject.mins ) 599 | }, 600 | a: function( string, timeObject ) { 601 | 602 | // If there's a string, then the length is always 4. 603 | // Otherwise check if it's more than "noon" and return either am/pm. 604 | return string ? 4 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'a.m.' : 'p.m.' 605 | }, 606 | A: function( string, timeObject ) { 607 | 608 | // If there's a string, then the length is always 2. 609 | // Otherwise check if it's more than "noon" and return either am/pm. 610 | return string ? 2 : MINUTES_IN_DAY / 2 > timeObject.time % MINUTES_IN_DAY ? 'AM' : 'PM' 611 | }, 612 | 613 | // Create an array by splitting the formatting string passed. 614 | toArray: function( formatString ) { return formatString.split( /(h{1,2}|H{1,2}|i|a|A|!.)/g ) }, 615 | 616 | // Format an object into a string using the formatting options. 617 | toString: function ( formatString, itemObject ) { 618 | var clock = this 619 | return clock.formats.toArray( formatString ).map( function( label ) { 620 | return _.trigger( clock.formats[ label ], clock, [ 0, itemObject ] ) || label.replace( /^!/, '' ) 621 | }).join( '' ) 622 | } 623 | } //TimePicker.prototype.formats 624 | 625 | 626 | 627 | 628 | /** 629 | * Check if two time units are the exact. 630 | */ 631 | TimePicker.prototype.isTimeExact = function( one, two ) { 632 | 633 | var clock = this 634 | 635 | // When we’re working with minutes, do a direct comparison. 636 | if ( 637 | ( _.isInteger( one ) && _.isInteger( two ) ) || 638 | ( typeof one == 'boolean' && typeof two == 'boolean' ) 639 | ) { 640 | return one === two 641 | } 642 | 643 | // When we’re working with time representations, compare the “pick” value. 644 | if ( 645 | ( _.isDate( one ) || angular.isArray( one ) ) && 646 | ( _.isDate( two ) || angular.isArray( two ) ) 647 | ) { 648 | return clock.create( one ).pick === clock.create( two ).pick 649 | } 650 | 651 | // When we’re working with range objects, compare the “from” and “to”. 652 | if ( angular.isObject( one ) && angular.isObject( two ) ) { 653 | return clock.isTimeExact( one.from, two.from ) && clock.isTimeExact( one.to, two.to ) 654 | } 655 | 656 | return false 657 | } 658 | 659 | 660 | /** 661 | * Check if two time units overlap. 662 | */ 663 | TimePicker.prototype.isTimeOverlap = function( one, two ) { 664 | 665 | var clock = this 666 | 667 | // When we’re working with an integer, compare the hours. 668 | if ( _.isInteger( one ) && ( _.isDate( two ) || angular.isArray( two ) ) ) { 669 | return one === clock.create( two ).hour 670 | } 671 | if ( _.isInteger( two ) && ( _.isDate( one ) || angular.isArray( one ) ) ) { 672 | return two === clock.create( one ).hour 673 | } 674 | 675 | // When we’re working with range objects, check if the ranges overlap. 676 | if ( angular.isObject( one ) && angular.isObject( two ) ) { 677 | return clock.overlapRanges( one, two ) 678 | } 679 | 680 | return false 681 | } 682 | 683 | 684 | /** 685 | * Flip the “enabled” state. 686 | */ 687 | TimePicker.prototype.flipEnable = function(val) { 688 | var itemObject = this.item 689 | itemObject.enable = val || (itemObject.enable == -1 ? 1 : -1) 690 | } 691 | 692 | 693 | /** 694 | * Mark a collection of times as “disabled”. 695 | */ 696 | TimePicker.prototype.deactivate = function( type, timesToDisable ) { 697 | 698 | var clock = this, 699 | disabledItems = clock.item.disable.slice(0) 700 | 701 | 702 | // If we’re flipping, that’s all we need to do. 703 | if ( timesToDisable == 'flip' ) { 704 | clock.flipEnable() 705 | } 706 | 707 | else if ( timesToDisable === false ) { 708 | clock.flipEnable(1) 709 | disabledItems = [] 710 | } 711 | 712 | else if ( timesToDisable === true ) { 713 | clock.flipEnable(-1) 714 | disabledItems = [] 715 | } 716 | 717 | // Otherwise go through the times to disable. 718 | else { 719 | 720 | timesToDisable.map(function( unitToDisable ) { 721 | 722 | var matchFound 723 | 724 | // When we have disabled items, check for matches. 725 | // If something is matched, immediately break out. 726 | for ( var index = 0; index < disabledItems.length; index += 1 ) { 727 | if ( clock.isTimeExact( unitToDisable, disabledItems[index] ) ) { 728 | matchFound = true 729 | break 730 | } 731 | } 732 | 733 | // If nothing was found, add the validated unit to the collection. 734 | if ( !matchFound ) { 735 | if ( 736 | _.isInteger( unitToDisable ) || 737 | _.isDate( unitToDisable ) || 738 | angular.isArray( unitToDisable ) || 739 | ( angular.isObject( unitToDisable ) && unitToDisable.from && unitToDisable.to ) 740 | ) { 741 | disabledItems.push( unitToDisable ) 742 | } 743 | } 744 | }) 745 | } 746 | 747 | // Return the updated collection. 748 | return disabledItems 749 | } //TimePicker.prototype.deactivate 750 | 751 | 752 | /** 753 | * Mark a collection of times as “enabled”. 754 | */ 755 | TimePicker.prototype.activate = function( type, timesToEnable ) { 756 | 757 | var clock = this, 758 | disabledItems = clock.item.disable, 759 | disabledItemsCount = disabledItems.length 760 | 761 | // If we’re flipping, that’s all we need to do. 762 | if ( timesToEnable == 'flip' ) { 763 | clock.flipEnable() 764 | } 765 | 766 | else if ( timesToEnable === true ) { 767 | clock.flipEnable(1) 768 | disabledItems = [] 769 | } 770 | 771 | else if ( timesToEnable === false ) { 772 | clock.flipEnable(-1) 773 | disabledItems = [] 774 | } 775 | 776 | // Otherwise go through the disabled times. 777 | else { 778 | 779 | timesToEnable.map(function( unitToEnable ) { 780 | 781 | var matchFound, 782 | disabledUnit, 783 | index, 784 | isRangeMatched 785 | 786 | // Go through the disabled items and try to find a match. 787 | for ( index = 0; index < disabledItemsCount; index += 1 ) { 788 | 789 | disabledUnit = disabledItems[index] 790 | 791 | // When an exact match is found, remove it from the collection. 792 | if ( clock.isTimeExact( disabledUnit, unitToEnable ) ) { 793 | matchFound = disabledItems[index] = null 794 | isRangeMatched = true 795 | break 796 | } 797 | 798 | // When an overlapped match is found, add the “inverted” state to it. 799 | else if ( clock.isTimeOverlap( disabledUnit, unitToEnable ) ) { 800 | if ( angular.isObject( unitToEnable ) ) { 801 | unitToEnable.inverted = true 802 | matchFound = unitToEnable 803 | } 804 | else if ( angular.isArray( unitToEnable ) ) { 805 | matchFound = unitToEnable 806 | if ( !matchFound[2] ) matchFound.push( 'inverted' ) 807 | } 808 | else if ( _.isDate( unitToEnable ) ) { 809 | matchFound = [ unitToEnable.getFullYear(), unitToEnable.getMonth(), unitToEnable.getDate(), 'inverted' ] 810 | } 811 | break 812 | } 813 | } 814 | 815 | // If a match was found, remove a previous duplicate entry. 816 | if ( matchFound ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 817 | if ( clock.isTimeExact( disabledItems[index], unitToEnable ) ) { 818 | disabledItems[index] = null 819 | break 820 | } 821 | } 822 | 823 | // In the event that we’re dealing with an overlap of range times, 824 | // make sure there are no “inverted” times because of it. 825 | if ( isRangeMatched ) for ( index = 0; index < disabledItemsCount; index += 1 ) { 826 | if ( clock.isTimeOverlap( disabledItems[index], unitToEnable ) ) { 827 | disabledItems[index] = null 828 | break 829 | } 830 | } 831 | 832 | // If something is still matched, add it into the collection. 833 | if ( matchFound ) { 834 | disabledItems.push( matchFound ) 835 | } 836 | }) 837 | } 838 | 839 | // Return the updated collection. 840 | return disabledItems.filter(function( val ) { return val != null }) 841 | } //TimePicker.prototype.activate 842 | 843 | 844 | /** 845 | * The division to use for the range intervals. 846 | */ 847 | TimePicker.prototype.i = function( type, value/*, options*/ ) { 848 | return _.isInteger( value ) && value > 0 ? value : this.item.interval 849 | } 850 | 851 | 852 | /** 853 | * Create a string for the nodes in the picker. 854 | */ 855 | TimePicker.prototype.nodes = function( isOpen ) { 856 | 857 | var 858 | clock = this, 859 | settings = clock.settings, 860 | selectedObject = clock.item.select, 861 | highlightedObject = clock.item.highlight, 862 | viewsetObject = clock.item.view, 863 | disabledCollection = clock.item.disable 864 | 865 | return _.node( 866 | 'ul', 867 | _.group({ 868 | min: clock.item.min.pick, 869 | max: clock.item.max.pick, 870 | i: clock.item.interval, 871 | node: 'li', 872 | item: function( loopedTime ) { 873 | loopedTime = clock.create( loopedTime ) 874 | var timeMinutes = loopedTime.pick, 875 | isSelected = selectedObject && selectedObject.pick == timeMinutes, 876 | isHighlighted = highlightedObject && highlightedObject.pick == timeMinutes, 877 | isDisabled = disabledCollection && clock.disabled( loopedTime ) 878 | return [ 879 | _.trigger( clock.formats.toString, clock, [ _.trigger( settings.formatLabel, clock, [ loopedTime ] ) || settings.format, loopedTime ] ), 880 | (function( klasses ) { 881 | 882 | if ( isSelected ) { 883 | klasses.push( settings.klass.selected ) 884 | } 885 | 886 | if ( isHighlighted ) { 887 | klasses.push( settings.klass.highlighted ) 888 | } 889 | 890 | if ( viewsetObject && viewsetObject.pick == timeMinutes ) { 891 | klasses.push( settings.klass.viewset ) 892 | } 893 | 894 | if ( isDisabled ) { 895 | klasses.push( settings.klass.disabled ) 896 | } 897 | 898 | return klasses.join( ' ' ) 899 | })( [ settings.klass.listItem ] ), 900 | 'data-pick=' + loopedTime.pick + ' ' + _.ariaAttr({ 901 | role: 'button', 902 | controls: clock.$node[0].id, 903 | checked: isSelected && clock.$node.val() === _.trigger( 904 | clock.formats.toString, 905 | clock, 906 | [ settings.format, loopedTime ] 907 | ) ? true : null, 908 | activedescendant: isHighlighted ? true : null, 909 | disabled: isDisabled ? true : null 910 | }) 911 | ] 912 | } 913 | }) + 914 | 915 | // * For Firefox forms to submit, make sure to set the button’s `type` attribute as “button”. 916 | _.node( 917 | 'li', 918 | _.node( 919 | 'button', 920 | settings.clear, 921 | settings.klass.buttonClear, 922 | 'type=button data-clear=1' + ( isOpen ? '' : ' disable' ) 923 | ) 924 | ), 925 | settings.klass.list 926 | ) 927 | } //TimePicker.prototype.nodes 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | /* ========================================================================== 936 | Extend the picker to add the component with the defaults. 937 | ========================================================================== */ 938 | 939 | TimePicker.defaults = (function( prefix ) { 940 | 941 | return { 942 | 943 | // Clear 944 | clear: 'Clear', 945 | 946 | // The format to show on the `input` element 947 | format: 'h:i A', 948 | 949 | // The interval between each time 950 | interval: 30, 951 | 952 | // Classes 953 | klass: { 954 | 955 | picker: prefix + ' ' + prefix + '--time', 956 | holder: prefix + '__holder', 957 | 958 | list: prefix + '__list', 959 | listItem: prefix + '__list-item', 960 | 961 | disabled: prefix + '__list-item--disabled', 962 | selected: prefix + '__list-item--selected', 963 | highlighted: prefix + '__list-item--highlighted', 964 | viewset: prefix + '__list-item--viewset', 965 | now: prefix + '__list-item--now', 966 | 967 | buttonClear: prefix + '__button--clear' 968 | } 969 | } 970 | })( Picker.klasses().picker ) 971 | 972 | 973 | 974 | 975 | 976 | /** 977 | * Extend the picker to add the time picker. 978 | */ 979 | Picker.extend( 'pickatime', TimePicker ) 980 | 981 | 982 | })); 983 | 984 | 985 | 986 | -------------------------------------------------------------------------------- /src/themes/_variables.less: -------------------------------------------------------------------------------- 1 | 2 | // ========================================================================== 3 | // $VARIABLES 4 | // ========================================================================== 5 | 6 | 7 | // 8 | // Base colors 9 | // 10 | @blue: #0089ec; 11 | @blue-hover: #b1dcfb; 12 | @black: #000; 13 | @white: #fff; 14 | 15 | 16 | // 17 | // Backgrounds 18 | // 19 | @bg-white: @white; 20 | @bg-grey-light: #f2f2f2; 21 | 22 | 23 | // 24 | // Borders 25 | // 26 | @border-grey: #777; 27 | @border-grey-light: #ddd; 28 | @border-select: darken( @border-grey-light, 15% ); 29 | 30 | 31 | // 32 | // Buttons 33 | // 34 | @clear-red: #e20; 35 | @close-grey: #777777; 36 | 37 | 38 | 39 | 40 | 41 | // 42 | // Picker base 43 | // 44 | 45 | // Make sure nothing is above the picker. 46 | @picker-z-index: 10000; 47 | 48 | // Animation speeds. 49 | @speed-animate-in: .15s; 50 | 51 | // Focused input border color. 52 | @input-active-border: @blue; 53 | 54 | // Typography. 55 | @base-font-size: 16px; 56 | @base-line-height: 1.2; 57 | 58 | // Corners. 59 | @picker-border-radius: 5px; 60 | 61 | // Drop shadows. 62 | @picker-box-shadow: 0 12px 36px 16px rgba(0,0,0,.24); 63 | @picker-box-shadow-light: 0 6px 18px 1px rgba(0,0,0,.12); 64 | 65 | // Height breakpoints. 66 | @breakpoint-tiny: 26.5em; // 424px @ 16px 67 | @breakpoint-small: 33.875em; // 542px @ 16px 68 | @breakpoint-medium: 40.125em; // 642px @ 16px 69 | @breakpoint-large: 46.75em; // 748px @ 16px 70 | 71 | // Width breakpoints. 72 | @breakpoint-width-tiny: 24.5em; // 392px @ 16px 73 | 74 | 75 | 76 | 77 | // 78 | // Date picker options 79 | // 80 | 81 | // The year and weekday labels. 82 | @year-weekday-label: #999; 83 | 84 | // “Today” tag indicators. 85 | @blue-tag: #0059bc; 86 | @disabled-tag: #aaa; 87 | 88 | // Disabled things.. such as days, month nav, etc. 89 | @disabled-things-bg: #f5f5f5; 90 | @disabled-things-text: #ddd; 91 | @disabled-highlighted-things-bg: #bbb; 92 | 93 | 94 | 95 | 96 | 97 | // 98 | // Theme configurations 99 | // 100 | 101 | // The “default” min & max widths. 102 | @picker-min-width: 256px; 103 | @picker-max-width: 666px; 104 | 105 | // The time picker min & max widths. 106 | @time-min-width: @picker-min-width; 107 | @time-max-width: 320px; 108 | 109 | // The “classic” theme settings. 110 | @classic-max-width: @picker-max-width - 200px; 111 | @classic-min-width: @picker-min-width - 80px; 112 | @classic-max-height: 25em; 113 | @classic-box-shadow: 0 6px 18px 1px rgba(0,0,0,.12); 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | // ========================================================================== 123 | // $MIXINS 124 | // ========================================================================== 125 | 126 | 127 | // 128 | // Common picker item states 129 | // 130 | 131 | // Highlighted. 132 | .picker-item-highlighted () { 133 | background: @blue; 134 | color: @white; 135 | } 136 | 137 | // Hovered. 138 | .picker-item-hovered () { 139 | cursor: pointer; 140 | color: @black; 141 | background: @blue-hover; 142 | } 143 | 144 | // Selected. 145 | .picker-item-selected () { 146 | border-color: @blue; 147 | } 148 | 149 | // Disabled. 150 | .picker-item-disabled () { 151 | background: @disabled-things-bg; 152 | border-color: @disabled-things-bg; 153 | color: @disabled-things-text; 154 | cursor: default; 155 | } 156 | 157 | 158 | 159 | 160 | // 161 | // Opacity 162 | // 163 | .opacity( @decimal ) { 164 | @percent: @decimal * 100; 165 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=@{percent})"; 166 | filter: ~"alpha(opacity=@{percent})"; 167 | -moz-opacity: @decimal; 168 | opacity: @decimal; 169 | } 170 | 171 | 172 | 173 | // 174 | // Vendor prefixes 175 | // 176 | .box-shadow ( @rest... ) { 177 | -webkit-box-shadow: @rest; 178 | -moz-box-shadow: @rest; 179 | box-shadow: @rest; 180 | } 181 | .box-sizing ( @rest... ) { 182 | -webkit-box-sizing: @rest; 183 | -moz-box-sizing: @rest; 184 | box-sizing: @rest; 185 | } 186 | .border-radius ( @rest... ) { 187 | -webkit-border-radius: @rest; 188 | -moz-border-radius: @rest; 189 | border-radius: @rest; 190 | } 191 | .transition ( ... ) { 192 | -webkit-transition: @arguments; 193 | -moz-transition: @arguments; 194 | transition: @arguments; 195 | } 196 | .transform ( @rest... ) { 197 | -webkit-transform: @rest; 198 | -moz-transform: @rest; 199 | transform: @rest; 200 | } 201 | .user-select ( @rest... ) { 202 | -webkit-user-select: @rest; 203 | -moz-user-select: @rest; 204 | -ms-user-select: @rest; 205 | user-select: @rest; 206 | } 207 | 208 | 209 | 210 | -------------------------------------------------------------------------------- /src/themes/base.date.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $BASE-DATE-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * The picker box. 11 | */ 12 | .picker__box { 13 | padding: 0 1em; 14 | } 15 | 16 | 17 | /** 18 | * The header containing the month and year stuff. 19 | */ 20 | .picker__header { 21 | text-align: center; 22 | position: relative; 23 | margin-top: .75em; 24 | } 25 | 26 | 27 | /** 28 | * The month and year labels. 29 | */ 30 | .picker__month, 31 | .picker__year { 32 | font-weight: 500; 33 | display: inline-block; 34 | margin-left: .25em; 35 | margin-right: .25em; 36 | } 37 | .picker__year { 38 | color: @year-weekday-label; 39 | font-size: .8em; 40 | font-style: italic; 41 | } 42 | 43 | 44 | /** 45 | * The month and year selectors. 46 | */ 47 | .picker__select--month, 48 | .picker__select--year { 49 | border: 1px solid @border-select; 50 | height: 2.5em; 51 | padding: .5em .25em; // For firefox 52 | margin-left: .25em; 53 | margin-right: .25em; 54 | font-size: .6em; 55 | 56 | // For `tiny` width screens, increase the size a bit and move it up. 57 | @media ( min-width: @breakpoint-width-tiny ) { 58 | font-size: .8em; 59 | margin-top: -.5em; 60 | } 61 | } 62 | .picker__select--month { 63 | width: 35%; 64 | } 65 | .picker__select--year { 66 | width: 22.5%; 67 | } 68 | .picker__select--month:focus, 69 | .picker__select--year:focus { 70 | border-color: @blue; 71 | } 72 | 73 | 74 | /** 75 | * The month navigation buttons. 76 | */ 77 | .picker__nav--prev, 78 | .picker__nav--next { 79 | position: absolute; 80 | padding: .5em 1.25em; 81 | width: 1em; 82 | height: 1em; 83 | top: -.25em; 84 | 85 | // For `tiny` width screens, move it up a bit. 86 | @media ( min-width: @breakpoint-width-tiny ) { 87 | top: -.33em; 88 | } 89 | } 90 | .picker__nav--prev { 91 | left: -1em; 92 | padding-right: 1.25em; 93 | 94 | // For `tiny` width screens, increase the padding a bit. 95 | @media ( min-width: @breakpoint-width-tiny ) { 96 | padding-right: 1.5em; 97 | } 98 | } 99 | .picker__nav--next { 100 | right: -1em; 101 | padding-left: 1.25em; 102 | 103 | // For `tiny` width screens, increase the padding a bit. 104 | @media ( min-width: @breakpoint-width-tiny ) { 105 | padding-left: 1.5em; 106 | } 107 | } 108 | .picker__nav--prev:before, 109 | .picker__nav--next:before { 110 | content: " "; 111 | border-top: .5em inset fade(@black, 0%); 112 | border-bottom: .5em inset fade(@black, 0%); 113 | border-right: .75em solid @black; 114 | width: 0; 115 | height: 0; 116 | display: block; 117 | margin: 0 auto; 118 | } 119 | .picker__nav--next:before { 120 | border-right: 0; 121 | border-left: .75em solid @black; 122 | } 123 | 124 | // Hovered date picker items. 125 | .picker__nav--prev:hover, 126 | .picker__nav--next:hover { 127 | .picker-item-hovered; 128 | } 129 | 130 | // Disabled month nav. 131 | .picker__nav--disabled, 132 | .picker__nav--disabled:hover, 133 | .picker__nav--disabled:before, 134 | .picker__nav--disabled:before:hover { 135 | cursor: default; 136 | background: none; 137 | border-right-color: @disabled-things-bg; 138 | border-left-color: @disabled-things-bg; 139 | } 140 | 141 | 142 | 143 | 144 | /** 145 | * The calendar table of dates 146 | */ 147 | .picker__table { 148 | text-align: center; 149 | border-collapse: collapse; 150 | border-spacing: 0; 151 | table-layout: fixed; 152 | font-size: inherit; 153 | width: 100%; 154 | margin-top: .75em; 155 | margin-bottom: .5em; 156 | 157 | // For `small` screens, increase the spacing a tad. 158 | @media ( min-height: @breakpoint-small ) { 159 | margin-bottom: .75em; 160 | } 161 | } 162 | 163 | // Remove browser stylings on a table cell. 164 | .picker__table td { 165 | margin: 0; 166 | padding: 0; 167 | } 168 | 169 | 170 | /** 171 | * The weekday labels 172 | */ 173 | .picker__weekday { 174 | width: 14.285714286%; // 100/7 175 | font-size: .75em; 176 | padding-bottom: .25em; 177 | color: @year-weekday-label; 178 | font-weight: 500; 179 | 180 | /* Increase the spacing a tad */ 181 | @media ( min-height: @breakpoint-small ) { 182 | padding-bottom: .5em; 183 | } 184 | } 185 | 186 | 187 | /** 188 | * The days on the calendar 189 | */ 190 | .picker__day { 191 | padding: .3125em 0; 192 | font-weight: 200; 193 | border: 1px solid transparent; 194 | } 195 | 196 | // Today. 197 | .picker__day--today { 198 | color: @blue; 199 | position: relative; 200 | } 201 | .picker__day--today:before { 202 | content: " "; 203 | position: absolute; 204 | top: 2px; 205 | right: 2px; 206 | width: 0; 207 | height: 0; 208 | border-top: .5em solid @blue-tag; 209 | border-left: .5em inset fade(@blue-tag, 0%); 210 | } 211 | 212 | // Selected day. 213 | .picker__day--selected, 214 | .picker__day--selected:hover { 215 | .picker-item-selected; 216 | } 217 | 218 | // Highlighted day. 219 | .picker__day--highlighted { 220 | background: @blue-hover; 221 | } 222 | 223 | // Disabled day. 224 | .picker__day--disabled:before { 225 | border-top-color: @disabled-tag; 226 | } 227 | 228 | // Out of focus days. 229 | .picker__day--outfocus { 230 | color: @disabled-things-text; 231 | } 232 | 233 | // Hovered date picker items. 234 | .picker__day--infocus:hover, 235 | .picker__day--outfocus:hover { 236 | .picker-item-hovered; 237 | } 238 | 239 | // Highlighted and hovered/focused dates. 240 | .picker__day--highlighted:hover, 241 | .picker--focused .picker__day--highlighted { 242 | .picker-item-highlighted; 243 | } 244 | 245 | // Disabled dates. 246 | .picker__day--disabled, 247 | .picker__day--disabled:hover { 248 | .picker-item-disabled; 249 | } 250 | 251 | // Disabled and highlighted dates. 252 | .picker__day--highlighted.picker__day--disabled, 253 | .picker__day--highlighted.picker__day--disabled:hover { 254 | background: @disabled-highlighted-things-bg; 255 | } 256 | 257 | 258 | /** 259 | * The footer containing the "today" and "clear" buttons. 260 | */ 261 | .picker__footer { 262 | text-align: center; 263 | } 264 | 265 | // Today and clear buttons. 266 | .picker__button--today, 267 | .picker__button--clear, 268 | .picker__button--close { 269 | border: 1px solid @white; 270 | background: @white; 271 | font-size: .8em; 272 | padding: .66em 0; 273 | font-weight: bold; 274 | width: 33%; 275 | display: inline-block; 276 | vertical-align: bottom; 277 | } 278 | .picker__button--today:hover, 279 | .picker__button--clear:hover, 280 | .picker__button--close:hover { 281 | .picker-item-hovered; 282 | border-bottom-color: @blue-hover; 283 | } 284 | .picker__button--today:focus, 285 | .picker__button--clear:focus, 286 | .picker__button--close:focus { 287 | background: @blue-hover; 288 | border-color: @blue; 289 | outline: none; 290 | } 291 | 292 | // Today and clear “indicators”. 293 | .picker__button--today:before, 294 | .picker__button--clear:before, 295 | .picker__button--close:before { 296 | position: relative; 297 | display: inline-block; 298 | height: 0; 299 | } 300 | .picker__button--today:before { 301 | content: " "; 302 | margin-right: .45em; 303 | top: -.05em; 304 | width: 0; 305 | border-top: .66em solid @blue-tag; 306 | border-left: .66em inset fade(@black, 0%); 307 | } 308 | .picker__button--clear:before { 309 | content: "\D7"; // × 310 | margin-right: .35em; 311 | top: -.1em; 312 | color: @clear-red; 313 | vertical-align: top; 314 | font-size: 1.1em; 315 | } 316 | .picker__button--close:before { 317 | content: "\D7"; // × 318 | top: -0.1em; 319 | vertical-align: top; 320 | font-size: 1.1em; 321 | margin-right: .35em; 322 | color: @close-grey; 323 | } 324 | 325 | 326 | -------------------------------------------------------------------------------- /src/themes/base.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $BASE-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * Note: the root picker element should *NOT* be styled more than what’s here. 11 | */ 12 | .picker { 13 | 14 | // The base font stylings. 15 | font-size: @base-font-size; 16 | text-align: left; 17 | line-height: @base-line-height; 18 | color: @black; 19 | 20 | // The picker shouldn’t affect or be affected by elements around it. 21 | position: absolute; 22 | z-index: @picker-z-index; 23 | 24 | // The picker shouldn’t be selectable. 25 | .user-select( none ); 26 | } 27 | 28 | 29 | /** 30 | * The picker input element. 31 | */ 32 | .picker__input { 33 | cursor: default; 34 | } 35 | 36 | 37 | /** 38 | * When the picker is opened, the input element is “activated”. 39 | */ 40 | .picker__input.picker__input--active { 41 | border-color: @input-active-border; 42 | } 43 | 44 | 45 | /** 46 | * The holder is the only “scrollable” top-level container element. 47 | */ 48 | .picker__holder { 49 | width: 100%; 50 | overflow-y: auto; 51 | -webkit-overflow-scrolling: touch; 52 | } 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/themes/base.time.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $BASE-TIME-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * The list of times. 11 | */ 12 | .picker__list { 13 | list-style: none; 14 | padding: 0.75em 0 4.2em; 15 | margin: 0; 16 | } 17 | 18 | 19 | /** 20 | * The times on the clock. 21 | */ 22 | .picker__list-item { 23 | border-bottom: 1px solid @border-grey-light; 24 | border-top: 1px solid @border-grey-light; 25 | margin-bottom: -1px; // Prevent border from doubling up. 26 | position: relative; 27 | background: @bg-white; 28 | padding: .75em 1.25em; 29 | 30 | // For `large` screens, reduce the padding to show more in view. 31 | @media ( min-height: @breakpoint-large ) { 32 | padding: .5em 1em; 33 | } 34 | } 35 | 36 | /* Hovered time */ 37 | .picker__list-item:hover { 38 | .picker-item-hovered; 39 | border-color: @blue; 40 | z-index: 10; 41 | } 42 | 43 | /* Selected time */ 44 | .picker__list-item--selected, 45 | .picker__list-item--selected:hover { 46 | .picker-item-selected; 47 | z-index: 10; 48 | } 49 | 50 | /* Highlighted time */ 51 | .picker__list-item--highlighted { 52 | background: @blue-hover; 53 | } 54 | 55 | /* Highlighted and hovered/focused time */ 56 | .picker__list-item--highlighted:hover, 57 | .picker--focused .picker__list-item--highlighted { 58 | .picker-item-highlighted; 59 | } 60 | 61 | /* Disabled time */ 62 | .picker__list-item--disabled, 63 | .picker__list-item--disabled:hover, 64 | .picker--focused .picker__list-item--disabled { 65 | .picker-item-disabled; 66 | border-color: @border-grey-light; 67 | z-index: auto; 68 | } 69 | 70 | 71 | 72 | 73 | /** 74 | * The clear button 75 | */ 76 | .picker--time { 77 | 78 | .picker__button--clear { 79 | display: block; 80 | width: 80%; 81 | margin: 1em auto 0; 82 | padding: 1em 1.25em; 83 | 84 | background: none; 85 | border: 0; 86 | 87 | font-weight: 500; 88 | font-size: .67em; 89 | text-align: center; 90 | text-transform: uppercase; 91 | color: #666; 92 | } 93 | 94 | .picker__button--clear:hover, 95 | .picker__button--clear:focus { 96 | .picker-item-hovered; 97 | background: @clear-red; 98 | border-color: @clear-red; 99 | cursor: pointer; 100 | color: @white; 101 | outline: none; 102 | } 103 | 104 | .picker__button--clear:before { 105 | top: -.25em; 106 | color: #666; 107 | font-size: 1.25em; 108 | font-weight: bold; 109 | } 110 | 111 | .picker__button--clear:hover:before, 112 | .picker__button--clear:focus:before { 113 | color: @white; 114 | } 115 | } 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/themes/classic.date.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $CLASSIC-DATE-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/themes/classic.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Classic picker styling for pickadate.js 3 | * Demo: http://amsul.github.io/pickadate.js 4 | */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * Note: the root picker element should *NOT* be styled more than what’s here. 11 | */ 12 | .picker { 13 | 14 | // Make it full-width so that it doesn’t collapse. 15 | width: 100%; 16 | } 17 | 18 | 19 | 20 | /** 21 | * The holder is the base of the picker. 22 | */ 23 | .picker__holder { 24 | 25 | // The base stylings. 26 | position: absolute; 27 | background: @bg-white; 28 | 29 | // Add a light border - except top & bottom to let it collapse. 30 | border: 1px solid lighten( @border-grey, 20% ); 31 | border-top-width: 0; 32 | border-bottom-width: 0; 33 | 34 | // Round the bottom corners. 35 | .border-radius( 0 0 @picker-border-radius @picker-border-radius ); 36 | 37 | // Let’s not go 100% + 2px. 38 | .box-sizing( border-box ); 39 | 40 | // Specify the min & max widths. 41 | min-width: @classic-min-width; 42 | max-width: @classic-max-width; 43 | 44 | // Hide everything to begin with. 45 | max-height: 0; 46 | .opacity( 0 ); 47 | 48 | // Tilt the picker. 49 | .transform( translateY( -1em ) perspective( 600px ) rotateX( 10deg ) ); 50 | 51 | // Everything should be smoothly animated – the height & border should wait till the rest is done. 52 | @transition: all @speed-animate-in ease-out, max-height 0 @speed-animate-in, border-width 0 @speed-animate-in; 53 | .transition( @transition ); 54 | } 55 | 56 | 57 | 58 | /** 59 | * The frame and wrap work together to ensure that 60 | * clicks within the picker don’t reach the holder. 61 | */ 62 | .picker__frame { 63 | padding: 1px; 64 | } 65 | .picker__wrap { 66 | margin: -1px; 67 | } 68 | 69 | 70 | 71 | /** 72 | * When the picker opens... 73 | */ 74 | .picker--opened { 75 | 76 | .picker__holder { 77 | 78 | // Reveal the content. 79 | max-height: @classic-max-height; 80 | .opacity( 1 ); 81 | 82 | // Expand the top & bottom borders. 83 | border-top-width: 1px; 84 | border-bottom-width: 1px; 85 | 86 | // Straighten the picker. 87 | .transform( translateY( 0 ) perspective( 600px ) rotateX( 0 ) ); 88 | 89 | // Everything should be smoothly animated – except the height & border. 90 | @transition: all @speed-animate-in ease-out, max-height 0, border-width 0; 91 | .transition( @transition ); 92 | 93 | // Add a light shadow. 94 | .box-shadow( @classic-box-shadow ); 95 | } 96 | } 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /src/themes/classic.time.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $CLASSIC-TIME-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * Note: the root picker element should __NOT__ be styled 11 | * more than what’s here. Style the `.picker__holder` instead. 12 | */ 13 | .picker--time { 14 | 15 | // Adjust the min & max widths. 16 | min-width: @time-min-width; 17 | max-width: @time-max-width; 18 | } 19 | 20 | 21 | /** 22 | * The holder is the base of the picker. 23 | */ 24 | .picker--time .picker__holder { 25 | 26 | // Add a slight background color. 27 | background: @bg-grey-light; 28 | 29 | // For `medium` screens, reduce the font-size a bit to get more in view. 30 | @media ( min-height: @breakpoint-medium ) { 31 | font-size: .875em; 32 | } 33 | } 34 | 35 | 36 | /** 37 | * The box contains the list of times. 38 | */ 39 | .picker--time .picker__box { 40 | 41 | // Remove any stylings overflowing from the date picker. 42 | padding: 0; 43 | 44 | // Make the “viewset” time position relative to the box. 45 | position: relative; 46 | } 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/themes/default.date.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $DEFAULT-DATE-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/themes/default.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Default mobile-first, responsive styling for pickadate.js 3 | * Demo: http://amsul.github.io/pickadate.js 4 | */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * Note: the root picker element should *NOT* be styled more than what’s here. 11 | */ 12 | .picker {} 13 | 14 | 15 | /** 16 | * Make the holder and frame fullscreen. 17 | */ 18 | .picker__holder, 19 | .picker__frame { 20 | bottom: 0; 21 | left: 0; 22 | right: 0; 23 | 24 | // Nudge everything off-screen to begin with. 25 | top: 100%; 26 | } 27 | 28 | 29 | /** 30 | * The holder should overlay the entire screen. 31 | */ 32 | .picker__holder { 33 | 34 | // Fill the screen and fix the position. 35 | position: fixed; 36 | 37 | // Fade out the background, then immediately shift the holder out of view. 38 | @transition: background @speed-animate-in ease-out, top 0s @speed-animate-in; 39 | .transition( @transition ); 40 | } 41 | 42 | 43 | 44 | /** 45 | * The frame that bounds the box contents of the picker. 46 | */ 47 | .picker__frame { 48 | 49 | position: absolute; 50 | 51 | // Specify the min & max widths and center align it. 52 | margin: 0 auto; 53 | min-width: @picker-min-width; 54 | max-width: @picker-max-width; 55 | width: 100%; // For IE9 & 10 to keep it centered. 56 | 57 | // Hide it to begin with. 58 | .opacity( 0 ); 59 | 60 | // Animate the frame in and out of view. 61 | .transition( all @speed-animate-in ease-out ); 62 | 63 | // For `small` screens... 64 | @media ( min-height: @breakpoint-small ) { 65 | 66 | // Reveal what’s beyond to allow drop shadows, et al. 67 | overflow: visible; 68 | 69 | // Align to the bottom edge instead of top. 70 | top: auto; 71 | bottom: -100%; 72 | 73 | // Prevent it from overflowing over the top edge. 74 | max-height: 80%; 75 | } 76 | 77 | // For `medium` screens... 78 | @media ( min-height: @breakpoint-medium ) { 79 | 80 | // Move away from the bottom edge. 81 | margin-bottom: 7.5%; 82 | } 83 | } 84 | 85 | /** 86 | * The wrapper sets the stage to vertically align the box contents. 87 | */ 88 | .picker__wrap { 89 | display: table; 90 | width: 100%; 91 | height: 100%; 92 | 93 | // For `small` screens, remove the “middle-aligned” styling 94 | @media ( min-height: @breakpoint-small ) { 95 | display: block; 96 | } 97 | } 98 | 99 | 100 | 101 | /** 102 | * The box contains all the picker contents. 103 | */ 104 | .picker__box { 105 | background: @bg-white; 106 | 107 | // To start with, vertically align to center 108 | display: table-cell; 109 | vertical-align: middle; 110 | 111 | // For `tiny` screens, increase the font size a bit 112 | @media ( min-height: @breakpoint-tiny ) { 113 | font-size: 1.25em; 114 | } 115 | 116 | // For `small` screens... 117 | @media ( min-height: @breakpoint-small ) { 118 | 119 | // Remove the “middle-aligned” styling 120 | display: block; 121 | 122 | // Increase the font size a bit more 123 | font-size: 1.33em; 124 | 125 | // Add the borders except the bottom one 126 | border: 1px solid @border-grey; 127 | border-top-color: lighten( @border-grey, 7% ); 128 | border-bottom-width: 0; 129 | 130 | // Make ‘em rounded at the top corners 131 | .border-radius( @picker-border-radius @picker-border-radius 0 0 ); 132 | 133 | // And finally, add a nice shadow 134 | .box-shadow( @picker-box-shadow ); 135 | } 136 | 137 | // For `medium` screens... 138 | @media ( min-height: @breakpoint-medium ) { 139 | 140 | // Increase the font size. 141 | font-size: 1.5em; 142 | 143 | // Reveal all borders and round all corners. 144 | border-bottom-width: 1px; 145 | .border-radius( @picker-border-radius ); 146 | } 147 | } 148 | 149 | 150 | /** 151 | * When the picker opens... 152 | */ 153 | .picker--opened { 154 | 155 | // Immediately move the holder to the top edge then fade in an overlay 156 | .picker__holder { 157 | 158 | // Move it to the top edge 159 | top: 0; 160 | 161 | // Show a translucent black background (order is important for IE) 162 | background: transparent; 163 | -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E000000,endColorstr=#1E000000)"; // IE8 164 | zoom: 1; 165 | background: rgba(0,0,0,.32); // Normal browsers 166 | 167 | // Animate in the background 168 | @transition: background @speed-animate-in ease-out; 169 | .transition( @transition ); 170 | } 171 | 172 | 173 | // Smoothly move the content to the top edge while fading it in 174 | .picker__frame { 175 | 176 | // Move to the top edge 177 | top: 0; 178 | 179 | // Ånd then reveal the content 180 | .opacity( 1 ); 181 | 182 | // For `small` screens, move to the bottom edge instead 183 | @media ( min-height: @breakpoint-small ) { 184 | top: auto; 185 | bottom: 0; 186 | } 187 | } 188 | } 189 | 190 | 191 | 192 | 193 | 194 | 195 | /** 196 | * For `large` screens, transform into an inline picker. 197 | */ 198 | // @include min-screen( large ) { 199 | 200 | // .picker { 201 | // width: 100%; 202 | // } 203 | 204 | // .picker__holder, 205 | // .picker--opened .picker__holder { 206 | // background: @white; 207 | // @include prefix( transition, all @speed-animate-in ease-out ); 208 | // } 209 | 210 | // .picker__holder { 211 | // font-size: 12px; 212 | // position: relative; 213 | // max-height: 0; 214 | // border: 1px solid transparent; 215 | // @include prefix( border-radius, @picker-border-radius ); 216 | // @include prefix( box-sizing, border-box ); 217 | // } 218 | // .picker--opened .picker__holder { 219 | // @include picker-holder-open; 220 | // @include prefix( box-shadow, @picker-box-shadow-light ); 221 | // } 222 | 223 | // .picker__frame, 224 | // .picker--opened .picker__frame { 225 | // max-width: none !important; 226 | // } 227 | 228 | // .picker__frame { 229 | // position: initial; 230 | // margin: 0; 231 | // @include opacity( 1 ); 232 | // } 233 | 234 | // .picker__box { 235 | // border: 0; 236 | // margin-top: 0; 237 | // @include prefix( border-radius, 0 ); 238 | // @include prefix( box-shadow, none ); 239 | // } 240 | // } 241 | 242 | 243 | 244 | -------------------------------------------------------------------------------- /src/themes/default.time.less: -------------------------------------------------------------------------------- 1 | 2 | /* ========================================================================== 3 | $DEFAULT-TIME-PICKER 4 | ========================================================================== */ 5 | 6 | @import "_variables.less"; 7 | 8 | 9 | /** 10 | * The frame the bounds the time picker. 11 | */ 12 | .picker--time .picker__frame { 13 | 14 | // Adjust the min & max widths. 15 | min-width: @time-min-width; 16 | max-width: @time-max-width; 17 | } 18 | 19 | 20 | /** 21 | * The picker box. 22 | */ 23 | .picker--time .picker__box { 24 | 25 | // Keep the font-size small to show more in view. 26 | font-size: 1em; 27 | 28 | // Add a slight background color. 29 | background: @bg-grey-light; 30 | 31 | // Remove the side paddings. 32 | padding: 0; 33 | 34 | // For `medium` screens, move it away from the bottom edge of the screen. 35 | @media ( min-height: @breakpoint-medium ) { 36 | margin-bottom: 5em; 37 | } 38 | } 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/themes/rtl.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Styling for RTL (right-to-left) languages using pickadate.js 3 | */ 4 | 5 | @import "_variables.less"; 6 | 7 | 8 | /** 9 | * Switch the direction - only really necessary if 10 | * it hasn’t already been applied higher up in the DOM. 11 | */ 12 | .picker { 13 | direction: rtl; 14 | } 15 | 16 | 17 | /** 18 | * Flip around the “next” and “previous” buttons. 19 | */ 20 | .picker__nav--next { 21 | right: auto; 22 | left: -1em; 23 | } 24 | .picker__nav--prev { 25 | left: auto; 26 | right: -1em; 27 | } 28 | .picker__nav--next:before { 29 | border-left: 0; 30 | border-right: .75em solid @black; 31 | } 32 | .picker__nav--prev:before { 33 | border-right: 0; 34 | border-left: .75em solid @black; 35 | } 36 | 37 | 38 | 39 | --------------------------------------------------------------------------------