├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── index.html ├── input.js ├── locales.js ├── nodep-date-input-polyfill.dist.js ├── nodep-date-input-polyfill.js ├── nodep-date-input-polyfill.scss ├── package-lock.json ├── package.json ├── picker.js └── rollup.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack.config.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | 4 | "rules": { 5 | "strict": ["off", "global"], 6 | "indent": ["warn", 2, { 7 | "VariableDeclarator": {"var": 2, "let": 2, "const": 3}, 8 | "SwitchCase": 1 9 | }], 10 | "semi": ["error", "always", { 11 | "omitLastInOneLineBlock": true 12 | }], 13 | "keyword-spacing": ["error", { 14 | "before": true, 15 | "after": false, 16 | "overrides": { 17 | "else": { 18 | "after": true 19 | }, 20 | "import": { 21 | "after": true 22 | }, 23 | "export": { 24 | "after": true 25 | }, 26 | "from": { 27 | "after": true 28 | }, 29 | "try": { 30 | "after": true 31 | }, 32 | "const": { 33 | "after": true 34 | }, 35 | "case": { 36 | "after": true 37 | }, 38 | "return": { 39 | "after": true 40 | } 41 | } 42 | }], 43 | "no-unused-vars": "off", 44 | "quotes": ["error", "backtick"], 45 | "no-console": ["warn", { 46 | "allow": ["warn", "error"] 47 | }], 48 | "no-var": "error", 49 | "object-shorthand": "error", 50 | "prefer-const": "error", 51 | "prefer-rest-params": "error", 52 | "prefer-spread": "error", 53 | "prefer-template": "error" 54 | }, 55 | "env": { 56 | "browser": true, 57 | "es6": true, 58 | "commonjs": true, 59 | "node": true 60 | }, 61 | "parserOptions": { 62 | "ecmaVersion": 7, 63 | "sourceType": "module", 64 | "impliedStrict": true 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | *.log 4 | 5 | build/ 6 | dist/ 7 | 8 | .tmp/ 9 | 10 | .DS_Store 11 | Thumbs.db 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 liorwohl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nodep-date-input-polyfill 2 | 3 | Just include this simple script and IE, macOS Safari, and legacy browser versions will support ``, without any dependencies, not even jQuery! 4 | 5 | Support dynamically created inputs, so can be used in single page applications. 6 | 7 | Forked from [html5-simple-date-input-polyfill](https://www.npmjs.com/package/html5-simple-date-input-polyfill). Continuing as a separate project. 8 | 9 | ## Demo 10 | 11 | [Try it in IE, macOS Safari, and legacy browser versions.](https://brianblakely.github.io/nodep-date-input-polyfill/) 12 | 13 | ## Install 14 | 15 | ### NPM 16 | 17 | `npm install --save nodep-date-input-polyfill` 18 | 19 | Add to your project: 20 | 21 | * **Webpack / Rollup / Babel / ES:** `import 'nodep-date-input-polyfill';` 22 | 23 | * **Webpack 1 / Browserify:** `require('nodep-date-input-polyfill');` 24 | 25 | * **Script Tag:** Copy `nodep-date-input-polyfill.dist.js` from `node_modules` and 26 | include it anywhere in your HTML. 27 | 28 | * This package also supports **AMD**. 29 | 30 | ### Bower 31 | 32 | `bower install nodep-date-input-polyfill` 33 | 34 | ## Features 35 | * **Easily Stylable:** [These are the default styles](https://github.com/brianblakely/nodep-date-input-polyfill/blob/master/nodep-date-input-polyfill.scss), 36 | which you may override with your own. 37 | 38 | * **Polyfills `valueAsDate` and `valueAsNumber`:** 39 | [Learn more about these properties.](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement#property-valueasdate) 40 | They behave as getters and setters. 41 | 42 | * **Keyboard Shortcuts:** `Esc` will hide the datepicker. `Up/Down` will 43 | increment/decrement the date by one day. 44 | 45 | * **Localization:** Specify the datepicker's locale by setting the 46 | `lang` attribute of the `` or any of its parent nodes. 47 | 48 | `` 49 | 50 | `` 51 | 52 | The default locale is `en`. 53 | 54 | The rendered date format will automatically adhere to the given locale. 55 | 56 | Currently supported locales include: 57 | 58 | * English (US / UK) 59 | 60 | * Chinese (Simplified / Simplified Informal / Traditional) 61 | 62 | * Japanese 63 | 64 | * Spanish 65 | 66 | * Portuguese 67 | 68 | * Hindi 69 | 70 | * German 71 | 72 | * Dutch 73 | 74 | * Danish 75 | 76 | * Turkish 77 | 78 | * Ukrainian 79 | 80 | * French 81 | 82 | * Italian 83 | 84 | * Polish 85 | 86 | * Czech 87 | 88 | * Russian 89 | 90 | ## Usage Notes 91 | 92 | * `getAttribute` and `setAttribute` will only reflect the field's text content. 93 | 94 | * In order to work with the field's underlying value, you must get/set its 95 | `value`, `valueAsDate`, or `valueAsNumber` properties. 96 | 97 | * Per the native implementation, polyfilled date fields will only accept 98 | values in the format `yyyy-MM-dd`. 99 | 100 | * If a user dirties a date field by typing into it manually, the browser will no 101 | longer allow the polyfill to populate the field from the datepicker. 102 | 103 | The polyfill will not attempt to solve this on its own. 104 | One potential workaround that you may choose to adopt 105 | is to prevent typing entirely: 106 | ```js 107 | el.addEventListener('keydown', (e)=> e.preventDefault()); 108 | ``` 109 | 110 | * When submitting an HTML form, the browser will submit the date field's `value` 111 | attribute (i.e. its text content), not the normalized content of the field's 112 | `value` *property*. 113 | 114 | If you don't want that, one potential workaround is to change 115 | the attribute upon form submission: 116 | ```js 117 | el.form.addEventListener('submit', (e)=> el.setAttribute('value', el.value)); 118 | ``` 119 | 120 | ## Contributing 121 | 122 | ### Local Development 123 | Run `npm start` or, for Cloud9 IDE users: `npm run start-c9` 124 | 125 | ### Build 126 | Run `npm run build` 127 | 128 | ### Localization 129 | Please submit PRs with new localizations! Open `locales.js` to add more. 130 | File an issue on GitHub if anything is unclear. 131 | 132 | ## Thanks 133 | Some words of appreciation for those who have submitted tickets, pull requests, 134 | and new localizations. The library is more robust and helpful to everyone 135 | because of those who choose to help out. 136 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodep-date-input-polyfill", 3 | "description": "Automatically adds datepickers to input[type=date] on IE, Firefox, and OS X Safari.", 4 | "main": "nodep-date-input-polyfill.dist.js", 5 | "authors": [ 6 | "Brian Blakely" 7 | ], 8 | "license": "MIT", 9 | "keywords": [ 10 | "html5", 11 | "light", 12 | "lightweight", 13 | "input", 14 | "type", 15 | "date", 16 | "datepicker", 17 | "polyfill" 18 | ], 19 | "homepage": "https://github.com/brianblakely/nodep-date-input-polyfill", 20 | "ignore": [ 21 | "**/.*", 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nodep Polyfill Test 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

14 | normal: 15 | 16 | 22 |

23 | 24 |

25 | with value: 26 | 27 |

28 | 29 |

30 | dynamically created: 31 | 38 |

39 | 40 | -------------------------------------------------------------------------------- /input.js: -------------------------------------------------------------------------------- 1 | import Picker from './picker.js'; 2 | import locales from './locales.js'; 3 | 4 | export default class Input { 5 | constructor(input) { 6 | this.element = input; 7 | this.element.setAttribute(`data-has-picker`, ``); 8 | 9 | let langEl = this.element, 10 | lang = ``; 11 | 12 | while(langEl.parentNode) { 13 | lang = langEl.getAttribute(`lang`); 14 | 15 | if(lang) { 16 | break; 17 | } 18 | 19 | langEl = langEl.parentNode; 20 | } 21 | 22 | this.locale = lang || `en`; 23 | 24 | this.localeText = this.getLocaleText(); 25 | 26 | Object.defineProperties( 27 | this.element, 28 | { 29 | 'value': { 30 | get: ()=> this.element.polyfillValue, 31 | set: val=> { 32 | if(!/^\d{4}-\d{2}-\d{2}$/.test(val)) { 33 | this.element.polyfillValue = ``; 34 | this.element.setAttribute(`value`, ``); 35 | return false; 36 | } 37 | 38 | this.element.polyfillValue = val; 39 | 40 | const YMD = val.split(`-`); 41 | 42 | this.element.setAttribute( 43 | `value`, 44 | this.localeText.format 45 | .replace(`Y`, YMD[0]) 46 | .replace(`M`, YMD[1]) 47 | .replace(`D`, YMD[2]) 48 | ); 49 | } 50 | }, 51 | 'valueAsDate': { 52 | get: ()=> { 53 | if(!this.element.polyfillValue) { 54 | return null; 55 | } 56 | 57 | return new Date(this.element.polyfillValue); 58 | }, 59 | set: val=> { 60 | this.element.value = val.toISOString().slice(0,10); 61 | } 62 | }, 63 | 'valueAsNumber': { 64 | get: ()=> { 65 | if(!this.element.value) { 66 | return NaN; 67 | } 68 | 69 | return this.element.valueAsDate.getTime(); 70 | }, 71 | set: val=> { 72 | this.element.valueAsDate = new Date(val); 73 | } 74 | } 75 | } 76 | ); 77 | 78 | // Initialize value for display. 79 | this.element.value = this.element.getAttribute(`value`); 80 | 81 | // Open the picker when the input get focus, 82 | // also on various click events to capture it in all corner cases. 83 | const showPicker = ()=> { 84 | Picker.instance.attachTo(this); 85 | }; 86 | this.element.addEventListener(`focus`, showPicker); 87 | this.element.addEventListener(`mousedown`, showPicker); 88 | this.element.addEventListener(`mouseup`, showPicker); 89 | 90 | // Update the picker if the date changed manually in the input. 91 | this.element.addEventListener(`keydown`, e=> { 92 | const date = new Date(); 93 | 94 | switch(e.keyCode) { 95 | case 27: 96 | Picker.instance.hide(); 97 | break; 98 | case 38: 99 | if(this.element.valueAsDate) { 100 | date.setDate(this.element.valueAsDate.getDate() + 1); 101 | this.element.valueAsDate = date; 102 | Picker.instance.pingInput(); 103 | } 104 | break; 105 | case 40: 106 | if(this.element.valueAsDate) { 107 | date.setDate(this.element.valueAsDate.getDate() - 1); 108 | this.element.valueAsDate = date; 109 | Picker.instance.pingInput(); 110 | } 111 | break; 112 | default: 113 | break; 114 | } 115 | 116 | Picker.instance.sync(); 117 | }); 118 | } 119 | 120 | getLocaleText() { 121 | const locale = this.locale.toLowerCase(); 122 | 123 | // First, look for an exact match to the provided locale. 124 | 125 | for(const localeSet in locales) { 126 | const localeList = localeSet.split(`_`).map(el=>el.toLowerCase()); 127 | 128 | if(!!~localeList.indexOf(locale)) { 129 | return locales[localeSet]; 130 | } 131 | } 132 | 133 | // If not found, look for a match to only the language. 134 | 135 | for(const localeSet in locales) { 136 | const localeList = localeSet.split(`_`).map(el=>el.toLowerCase()); 137 | 138 | if(!!~localeList.indexOf(locale.substr(0,2))) { 139 | return locales[localeSet]; 140 | } 141 | } 142 | 143 | // If still not found, reassign locale to English and rematch. 144 | 145 | this.locale = `en`; 146 | 147 | return this.getLocaleText(); 148 | } 149 | 150 | // Return false if the browser does not support input[type="date"]. 151 | static supportsDateInput() { 152 | const input = document.createElement(`input`); 153 | input.setAttribute(`type`, `date`); 154 | 155 | const notADateValue = `not-a-date`; 156 | input.setAttribute(`value`, notADateValue); 157 | 158 | return ( 159 | ( 160 | document.currentScript 161 | && !document.currentScript.hasAttribute(`data-nodep-date-input-polyfill-debug`) 162 | ) 163 | && !(input.value === notADateValue) 164 | ); 165 | } 166 | 167 | // Will add the Picker to all inputs in the page. 168 | static addPickerToDateInputs() { 169 | // Get and loop all the input[type="date"]s in the page that do not have `[data-has-picker]` yet. 170 | const dateInputs = document.querySelectorAll(`input[type="date"]:not([data-has-picker]):not([readonly])`); 171 | const length = dateInputs.length; 172 | 173 | if(!length) { 174 | return false; 175 | } 176 | 177 | for(let i = 0; i < length; ++i) { 178 | new Input(dateInputs[i]); 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /locales.js: -------------------------------------------------------------------------------- 1 | // Localizations for UI text. 2 | // Keys correspond to applicable `lang` values, delimited by an underscore. 3 | // Days and months must be listed in the order they should display. 4 | 5 | const locales = { 6 | 'en_en-US': { 7 | days: [ 8 | `Sun`, 9 | `Mon`, 10 | `Tue`, 11 | `Wed`, 12 | `Thu`, 13 | `Fri`, 14 | `Sat` 15 | ], 16 | months: [ 17 | `January`, 18 | `February`, 19 | `March`, 20 | `April`, 21 | `May`, 22 | `June`, 23 | `July`, 24 | `August`, 25 | `September`, 26 | `October`, 27 | `November`, 28 | `December` 29 | ], 30 | today: `Today`, 31 | format: `M/D/Y` 32 | }, 33 | 'en-GB': { 34 | days: [ 35 | `Sun`, 36 | `Mon`, 37 | `Tue`, 38 | `Wed`, 39 | `Thu`, 40 | `Fri`, 41 | `Sat` 42 | ], 43 | months: [ 44 | `January`, 45 | `February`, 46 | `March`, 47 | `April`, 48 | `May`, 49 | `June`, 50 | `July`, 51 | `August`, 52 | `September`, 53 | `October`, 54 | `November`, 55 | `December` 56 | ], 57 | today: `Today`, 58 | format: `D/M/Y` 59 | }, 60 | /* Simplified Chinese */ 61 | 'zh_zh-CN': { 62 | days: [ 63 | `星期天`, 64 | `星期一`, 65 | `星期二`, 66 | `星期三`, 67 | `星期四`, 68 | `星期五`, 69 | `星期六` 70 | ], 71 | months: [ 72 | `一月`, 73 | `二月`, 74 | `三月`, 75 | `四月`, 76 | `五月`, 77 | `六月`, 78 | `七月`, 79 | `八月`, 80 | `九月`, 81 | `十月`, 82 | `十一月`, 83 | `十二月` 84 | ], 85 | today: `今天`, 86 | format: `Y/M/D` 87 | }, 88 | /* Simplified Chinese, informal*/ 89 | 'zh-Hans_zh-Hans-CN': { 90 | days: [ 91 | `周日`, 92 | `周一`, 93 | `周二`, 94 | `周三`, 95 | `周四`, 96 | `周五`, 97 | `周六` 98 | ], 99 | months: [ 100 | `一月`, 101 | `二月`, 102 | `三月`, 103 | `四月`, 104 | `五月`, 105 | `六月`, 106 | `七月`, 107 | `八月`, 108 | `九月`, 109 | `十月`, 110 | `十一月`, 111 | `十二月` 112 | ], 113 | today: `今天`, 114 | format: `Y/M/D` 115 | }, 116 | /* Traditional Chinese */ 117 | 'zh-Hant_zh-Hant-TW': { 118 | days: [ 119 | `週日`, 120 | `週一`, 121 | `週二`, 122 | `週三`, 123 | `週四`, 124 | `週五`, 125 | `週六` 126 | ], 127 | months: [ 128 | `一月`, 129 | `二月`, 130 | `三月`, 131 | `四月`, 132 | `五月`, 133 | `六月`, 134 | `七月`, 135 | `八月`, 136 | `九月`, 137 | `十月`, 138 | `十一月`, 139 | `十二月` 140 | ], 141 | today: `今天`, 142 | format: `Y/M/D` 143 | }, 144 | /* German (Germany) */ 145 | 'de_de-DE': { 146 | days: [ 147 | `So`, 148 | `Mo`, 149 | `Di`, 150 | `Mi`, 151 | `Do`, 152 | `Fr`, 153 | `Sa` 154 | ], 155 | months: [ 156 | `Januar`, 157 | `Februar`, 158 | `März`, 159 | `April`, 160 | `Mai`, 161 | `Juni`, 162 | `Juli`, 163 | `August`, 164 | `September`, 165 | `Oktober`, 166 | `November`, 167 | `Dezember` 168 | ], 169 | today: `Heute`, 170 | format: `D.M.Y` 171 | }, 172 | /* Danish */ 173 | 'da_da-DA': { 174 | days: [ 175 | `Søn`, 176 | `Man`, 177 | `Tirs`, 178 | `Ons`, 179 | `Tors`, 180 | `Fre`, 181 | `Lør` 182 | ], 183 | months: [ 184 | `Januar`, 185 | `Februar`, 186 | `Marts`, 187 | `April`, 188 | `Maj`, 189 | `Juni`, 190 | `Juli`, 191 | `August`, 192 | `September`, 193 | `Oktober`, 194 | `November`, 195 | `December` 196 | ], 197 | today: `I dag`, 198 | format: `D/M/Y` 199 | }, 200 | /* Spanish */ 201 | 'es': { 202 | days: [ 203 | `Dom`, 204 | `Lun`, 205 | `Mar`, 206 | `Mié`, 207 | `Jue`, 208 | `Vie`, 209 | `Sáb` 210 | ], 211 | months: [ 212 | `Enero`, 213 | `Febrero`, 214 | `Marzo`, 215 | `Abril`, 216 | `Mayo`, 217 | `Junio`, 218 | `Julio`, 219 | `Agosto`, 220 | `Septiembre`, 221 | `Octubre`, 222 | `Noviembre`, 223 | `Diciembre` 224 | ], 225 | today: `Hoy`, 226 | format: `D/M/Y` 227 | }, 228 | /* Hindi */ 229 | 'hi': { 230 | days: [ 231 | `रवि`, 232 | `सोम`, 233 | `मंगल`, 234 | `बुध`, 235 | `गुरु`, 236 | `शुक्र`, 237 | `शनि` 238 | ], 239 | months: [ 240 | `जनवरी`, 241 | `फरवरी`, 242 | `मार्च`, 243 | `अप्रेल`, 244 | `मै`, 245 | `जून`, 246 | `जूलाई`, 247 | `अगस्त`, 248 | `सितम्बर`, 249 | `आक्टोबर`, 250 | `नवम्बर`, 251 | `दिसम्बर` 252 | ], 253 | today: `आज`, 254 | format: `D/M/Y` 255 | }, 256 | /* Portuguese */ 257 | 'pt': { 258 | days: [ 259 | `Dom`, 260 | `Seg`, 261 | `Ter`, 262 | `Qua`, 263 | `Qui`, 264 | `Sex`, 265 | `Sáb` 266 | ], 267 | months: [ 268 | `Janeiro`, 269 | `Fevereiro`, 270 | `Março`, 271 | `Abril`, 272 | `Maio`, 273 | `Junho`, 274 | `Julho`, 275 | `Agosto`, 276 | `Setembro`, 277 | `Outubro`, 278 | `Novembro`, 279 | `Dezembro` 280 | ], 281 | today: `Hoje`, 282 | format: `D/M/Y` 283 | }, 284 | /* Japanese */ 285 | 'ja': { 286 | days: [ 287 | `日`, 288 | `月`, 289 | `火`, 290 | `水`, 291 | `木`, 292 | `金`, 293 | `土` 294 | ], 295 | months: [ 296 | `1月`, 297 | `2月`, 298 | `3月`, 299 | `4月`, 300 | `5月`, 301 | `6月`, 302 | `7月`, 303 | `8月`, 304 | `9月`, 305 | `10月`, 306 | `11月`, 307 | `12月` 308 | ], 309 | today: `今日`, 310 | format: `Y/M/D` 311 | }, 312 | /* Dutch */ 313 | 'nl_nl-NL_nl-BE': { 314 | days: [ 315 | `Zo`, 316 | `Ma`, 317 | `Di`, 318 | `Wo`, 319 | `Do`, 320 | `Vr`, 321 | `Za` 322 | ], 323 | months: [ 324 | `Januari`, 325 | `Februari`, 326 | `Maart`, 327 | `April`, 328 | `Mei`, 329 | `Juni`, 330 | `Juli`, 331 | `Augustus`, 332 | `September`, 333 | `Oktober`, 334 | `November`, 335 | `December` 336 | ], 337 | today: `Vandaag`, 338 | format: `D/M/Y` 339 | }, 340 | /* Turkish */ 341 | 'tr_tr-TR': { 342 | days: [ 343 | `Pzr`, 344 | `Pzt`, 345 | `Sal`, 346 | `Çrş`, 347 | `Prş`, 348 | `Cum`, 349 | `Cmt` 350 | ], 351 | months: [ 352 | `Ocak`, 353 | `Şubat`, 354 | `Mart`, 355 | `Nisan`, 356 | `Mayıs`, 357 | `Haziran`, 358 | `Temmuz`, 359 | `Ağustos`, 360 | `Eylül`, 361 | `Ekim`, 362 | `Kasım`, 363 | `Aralık` 364 | ], 365 | today: `Bugün`, 366 | format: `D/M/Y` 367 | }, 368 | /* French */ 369 | 'fr_fr-FR': { 370 | days: [ 371 | `Dim`, 372 | `Lun`, 373 | `Mar`, 374 | `Mer`, 375 | `Jeu`, 376 | `Ven`, 377 | `Sam` 378 | ], 379 | months: [ 380 | `Janvier`, 381 | `Février`, 382 | `Mars`, 383 | `Avril`, 384 | `Mai`, 385 | `Juin`, 386 | `Juillet`, 387 | `Août`, 388 | `Septembre`, 389 | `Octobre`, 390 | `Novembre`, 391 | `Décembre` 392 | ], 393 | today: `Auj.`, 394 | format: `D/M/Y` 395 | }, 396 | /* Ukrainian */ 397 | 'uk_uk-UA': { 398 | days: [ 399 | `Нд`, 400 | `Пн`, 401 | `Вт`, 402 | `Ср`, 403 | `Чт`, 404 | `Пт`, 405 | `Сб` 406 | ], 407 | months: [ 408 | `Січень`, 409 | `Лютий`, 410 | `Березень`, 411 | `Квітень`, 412 | `Травень`, 413 | `Червень`, 414 | `Липень`, 415 | `Серпень`, 416 | `Вересень`, 417 | `Жовтень`, 418 | `Листопад`, 419 | `Грудень` 420 | ], 421 | today: `Сьогодні`, 422 | format: `D.M.Y` 423 | }, 424 | /* Italian */ 425 | 'it': { 426 | days: [ 427 | `Dom`, 428 | `Lun`, 429 | `Mar`, 430 | `Mer`, 431 | `Gio`, 432 | `Ven`, 433 | `Sab` 434 | ], 435 | months: [ 436 | `Gennaio`, 437 | `Febbraio`, 438 | `Marzo`, 439 | `Aprile`, 440 | `Maggio`, 441 | `Giugno`, 442 | `Luglio`, 443 | `Agosto`, 444 | `Settembre`, 445 | `ottobre`, 446 | `Novembre`, 447 | `Dicembre` 448 | ], 449 | today: `Oggi`, 450 | format: `D/M/Y` 451 | }, 452 | /* Polish */ 453 | 'pl': { 454 | days: [ 455 | `Nie`, 456 | `Pon`, 457 | `Wto`, 458 | `Śro`, 459 | `Czw`, 460 | `Pt`, 461 | `Sob` 462 | ], 463 | months: [ 464 | `Styczeń`, 465 | `Luty`, 466 | `Marzec`, 467 | `Kwiecień`, 468 | `Maj`, 469 | `Czerwiec`, 470 | `Lipiec`, 471 | `Sierpień`, 472 | `Wrzesień`, 473 | `Październik`, 474 | `Listopad`, 475 | `Grudzień` 476 | ], 477 | today: `Dzisiaj`, 478 | format: `D.M.Y` 479 | }, 480 | /* Czech */ 481 | 'cs': { 482 | days: [ 483 | `Po`, 484 | `Út`, 485 | `St`, 486 | `Čt`, 487 | `Pá`, 488 | `So`, 489 | `Ne` 490 | ], 491 | months: [ 492 | `Leden`, 493 | `Únor`, 494 | `Březen`, 495 | `Duben`, 496 | `Květen`, 497 | `Červen`, 498 | `Červenec`, 499 | `Srpen`, 500 | `Září`, 501 | `Říjen`, 502 | `Listopad`, 503 | `Prosinec` 504 | ], 505 | today: `Dnes`, 506 | format: `D.M.Y` 507 | }, 508 | /* Russian */ 509 | 'ru': { 510 | days: [ 511 | `Вс`, 512 | `Пн`, 513 | `Вт`, 514 | `Ср`, 515 | `Чт`, 516 | `Пт`, 517 | `Сб` 518 | ], 519 | months: [ 520 | `Январь`, 521 | `Февраль`, 522 | `Март`, 523 | `Апрель`, 524 | `Май`, 525 | `Июнь`, 526 | `Июль`, 527 | `Август`, 528 | `Сентябрь`, 529 | `Октябрь`, 530 | `Ноябрь`, 531 | `Декабрь` 532 | ], 533 | today: `Сегодня`, 534 | format: `D.M.Y` 535 | } 536 | }; 537 | 538 | export default locales; 539 | -------------------------------------------------------------------------------- /nodep-date-input-polyfill.dist.js: -------------------------------------------------------------------------------- 1 | (function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b():'function'==typeof define&&define.amd?define(b):b()})(this,function(){'use strict';(function(a){if(a&&'undefined'!=typeof window){var b=document.createElement('style');return b.setAttribute('type','text/css'),b.innerHTML=a,document.head.appendChild(b),a}})('date-input-polyfill {\n background: #fff;\n color: #000;\n text-shadow: none;\n border: 0;\n padding: 0;\n height: auto;\n width: auto;\n line-height: normal;\n border-radius: 0;\n font-family: sans-serif;\n font-size: 14px;\n position: absolute !important;\n text-align: center;\n box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 12px 17px 2px rgba(0, 0, 0, 0.14), 0 5px 22px 4px rgba(0, 0, 0, 0.12);\n cursor: default;\n z-index: 1; }\n date-input-polyfill[data-open="false"] {\n display: none; }\n date-input-polyfill[data-open="true"] {\n display: block; }\n date-input-polyfill select, date-input-polyfill table, date-input-polyfill th, date-input-polyfill td {\n background: #fff;\n color: #000;\n text-shadow: none;\n border: 0;\n padding: 0;\n height: auto;\n width: auto;\n line-height: normal;\n border-radius: 0;\n font-family: sans-serif;\n font-size: 14px;\n box-shadow: none; }\n date-input-polyfill select, date-input-polyfill button {\n border: 0;\n border-bottom: 1px solid #E0E0E0;\n height: 24px;\n vertical-align: top; }\n date-input-polyfill select {\n width: 50%; }\n date-input-polyfill select:first-of-type {\n border-right: 1px solid #E0E0E0;\n width: 30%; }\n date-input-polyfill button {\n padding: 0;\n width: 20%;\n background: #E0E0E0; }\n date-input-polyfill table {\n border-collapse: collapse; }\n date-input-polyfill th, date-input-polyfill td {\n width: 32px;\n padding: 4px;\n text-align: center; }\n date-input-polyfill td[data-day] {\n cursor: pointer; }\n date-input-polyfill td[data-day]:hover {\n background: #E0E0E0; }\n date-input-polyfill [data-selected] {\n font-weight: bold;\n background: #D8EAF6; }\n\ninput[data-has-picker]::-ms-clear {\n display: none; }\n');var a=function(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')},b=function(){function a(a,b){for(var c,d=0;d'],b=0,d=this.input.localeText.days.length;b'+this.input.localeText.days[b]+'');this.daysHead.innerHTML=a.join(''),c.createRangeSelect(this.month,0,11,this.input.localeText.months,this.date.getMonth()),this.today.textContent=this.input.localeText.today}},{key:'refreshDaysMatrix',value:function(){this.refreshLocale();for(var a=this.date.getFullYear(),b=this.date.getMonth(),d=new Date(a,b,1).getDay(),e=new Date(this.date.getFullYear(),b+1,0).getDate(),f=c.absoluteDate(this.input.element.valueAsDate)||!1,g=f&&a===f.getFullYear()&&b===f.getMonth(),h=[],j=0;j')+'\n \n '),j+1<=d){h.push('');continue}var i=j+1-d,k=g&&f.getDate()===i;h.push('\n '+i+'\n ')}this.days.innerHTML=h.join('')}},{key:'pingInput',value:function(){var a,b;try{a=new Event('input'),b=new Event('change')}catch(c){a=document.createEvent('KeyboardEvent'),a.initEvent('input',!0,!1),b=document.createEvent('KeyboardEvent'),b.initEvent('change',!0,!1)}this.input.element.dispatchEvent(a),this.input.element.dispatchEvent(b)}}],[{key:'createRangeSelect',value:function(a,b,c,d,e){a.innerHTML='';for(var f,g=b;g<=c;++g){f=document.createElement('option'),a.appendChild(f);var h=d?d[g-b]:g;f.text=h,f.value=g,g===e&&(f.selected='selected')}return a}},{key:'absoluteDate',value:function(a){return a&&new Date(a.getTime()+1e3*(60*a.getTimezoneOffset()))}}]),c}();c.instance=null;var d={"en_en-US":{days:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],months:['January','February','March','April','May','June','July','August','September','October','November','December'],today:'Today',format:'M/D/Y'},"en-GB":{days:['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],months:['January','February','March','April','May','June','July','August','September','October','November','December'],today:'Today',format:'D/M/Y'},"zh_zh-CN":{days:['\u661F\u671F\u5929','\u661F\u671F\u4E00','\u661F\u671F\u4E8C','\u661F\u671F\u4E09','\u661F\u671F\u56DB','\u661F\u671F\u4E94','\u661F\u671F\u516D'],months:['\u4E00\u6708','\u4E8C\u6708','\u4E09\u6708','\u56DB\u6708','\u4E94\u6708','\u516D\u6708','\u4E03\u6708','\u516B\u6708','\u4E5D\u6708','\u5341\u6708','\u5341\u4E00\u6708','\u5341\u4E8C\u6708'],today:'\u4ECA\u5929',format:'Y/M/D'},"zh-Hans_zh-Hans-CN":{days:['\u5468\u65E5','\u5468\u4E00','\u5468\u4E8C','\u5468\u4E09','\u5468\u56DB','\u5468\u4E94','\u5468\u516D'],months:['\u4E00\u6708','\u4E8C\u6708','\u4E09\u6708','\u56DB\u6708','\u4E94\u6708','\u516D\u6708','\u4E03\u6708','\u516B\u6708','\u4E5D\u6708','\u5341\u6708','\u5341\u4E00\u6708','\u5341\u4E8C\u6708'],today:'\u4ECA\u5929',format:'Y/M/D'},"zh-Hant_zh-Hant-TW":{days:['\u9031\u65E5','\u9031\u4E00','\u9031\u4E8C','\u9031\u4E09','\u9031\u56DB','\u9031\u4E94','\u9031\u516D'],months:['\u4E00\u6708','\u4E8C\u6708','\u4E09\u6708','\u56DB\u6708','\u4E94\u6708','\u516D\u6708','\u4E03\u6708','\u516B\u6708','\u4E5D\u6708','\u5341\u6708','\u5341\u4E00\u6708','\u5341\u4E8C\u6708'],today:'\u4ECA\u5929',format:'Y/M/D'},"de_de-DE":{days:['So','Mo','Di','Mi','Do','Fr','Sa'],months:['Januar','Februar','M\xE4rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],today:'Heute',format:'D.M.Y'},"da_da-DA":{days:['S\xF8n','Man','Tirs','Ons','Tors','Fre','L\xF8r'],months:['Januar','Februar','Marts','April','Maj','Juni','Juli','August','September','Oktober','November','December'],today:'I dag',format:'D/M/Y'},es:{days:['Dom','Lun','Mar','Mi\xE9','Jue','Vie','S\xE1b'],months:['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre'],today:'Hoy',format:'D/M/Y'},hi:{days:['\u0930\u0935\u093F','\u0938\u094B\u092E','\u092E\u0902\u0917\u0932','\u092C\u0941\u0927','\u0917\u0941\u0930\u0941','\u0936\u0941\u0915\u094D\u0930','\u0936\u0928\u093F'],months:['\u091C\u0928\u0935\u0930\u0940','\u092B\u0930\u0935\u0930\u0940','\u092E\u093E\u0930\u094D\u091A','\u0905\u092A\u094D\u0930\u0947\u0932','\u092E\u0948','\u091C\u0942\u0928','\u091C\u0942\u0932\u093E\u0908','\u0905\u0917\u0938\u094D\u0924','\u0938\u093F\u0924\u092E\u094D\u092C\u0930','\u0906\u0915\u094D\u091F\u094B\u092C\u0930','\u0928\u0935\u092E\u094D\u092C\u0930','\u0926\u093F\u0938\u092E\u094D\u092C\u0930'],today:'\u0906\u091C',format:'D/M/Y'},pt:{days:['Dom','Seg','Ter','Qua','Qui','Sex','S\xE1b'],months:['Janeiro','Fevereiro','Mar\xE7o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],today:'Hoje',format:'D/M/Y'},ja:{days:['\u65E5','\u6708','\u706B','\u6C34','\u6728','\u91D1','\u571F'],months:['1\u6708','2\u6708','3\u6708','4\u6708','5\u6708','6\u6708','7\u6708','8\u6708','9\u6708','10\u6708','11\u6708','12\u6708'],today:'\u4ECA\u65E5',format:'Y/M/D'},"nl_nl-NL_nl-BE":{days:['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'],months:['Januari','Februari','Maart','April','Mei','Juni','Juli','Augustus','September','Oktober','November','December'],today:'Vandaag',format:'D/M/Y'},"tr_tr-TR":{days:['Pzr','Pzt','Sal','\xC7r\u015F','Pr\u015F','Cum','Cmt'],months:['Ocak','\u015Eubat','Mart','Nisan','May\u0131s','Haziran','Temmuz','A\u011Fustos','Eyl\xFCl','Ekim','Kas\u0131m','Aral\u0131k'],today:'Bug\xFCn',format:'D/M/Y'},"fr_fr-FR":{days:['Dim','Lun','Mar','Mer','Jeu','Ven','Sam'],months:['Janvier','F\xE9vrier','Mars','Avril','Mai','Juin','Juillet','Ao\xFBt','Septembre','Octobre','Novembre','D\xE9cembre'],today:'Auj.',format:'D/M/Y'},"uk_uk-UA":{days:['\u041D\u0434','\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431'],months:['\u0421\u0456\u0447\u0435\u043D\u044C','\u041B\u044E\u0442\u0438\u0439','\u0411\u0435\u0440\u0435\u0437\u0435\u043D\u044C','\u041A\u0432\u0456\u0442\u0435\u043D\u044C','\u0422\u0440\u0430\u0432\u0435\u043D\u044C','\u0427\u0435\u0440\u0432\u0435\u043D\u044C','\u041B\u0438\u043F\u0435\u043D\u044C','\u0421\u0435\u0440\u043F\u0435\u043D\u044C','\u0412\u0435\u0440\u0435\u0441\u0435\u043D\u044C','\u0416\u043E\u0432\u0442\u0435\u043D\u044C','\u041B\u0438\u0441\u0442\u043E\u043F\u0430\u0434','\u0413\u0440\u0443\u0434\u0435\u043D\u044C'],today:'\u0421\u044C\u043E\u0433\u043E\u0434\u043D\u0456',format:'D.M.Y'},it:{days:['Dom','Lun','Mar','Mer','Gio','Ven','Sab'],months:['Gennaio','Febbraio','Marzo','Aprile','Maggio','Giugno','Luglio','Agosto','Settembre','ottobre','Novembre','Dicembre'],today:'Oggi',format:'D/M/Y'},pl:{days:['Nie','Pon','Wto','\u015Aro','Czw','Pt','Sob'],months:['Stycze\u0144','Luty','Marzec','Kwiecie\u0144','Maj','Czerwiec','Lipiec','Sierpie\u0144','Wrzesie\u0144','Pa\u017Adziernik','Listopad','Grudzie\u0144'],today:'Dzisiaj',format:'D.M.Y'},cs:{days:['Po','\xDAt','St','\u010Ct','P\xE1','So','Ne'],months:['Leden','\xDAnor','B\u0159ezen','Duben','Kv\u011Bten','\u010Cerven','\u010Cervenec','Srpen','Z\xE1\u0159\xED','\u0158\xEDjen','Listopad','Prosinec'],today:'Dnes',format:'D.M.Y'},ru:{days:['\u0412\u0441','\u041F\u043D','\u0412\u0442','\u0421\u0440','\u0427\u0442','\u041F\u0442','\u0421\u0431'],months:['\u042F\u043D\u0432\u0430\u0440\u044C','\u0424\u0435\u0432\u0440\u0430\u043B\u044C','\u041C\u0430\u0440\u0442','\u0410\u043F\u0440\u0435\u043B\u044C','\u041C\u0430\u0439','\u0418\u044E\u043D\u044C','\u0418\u044E\u043B\u044C','\u0410\u0432\u0433\u0443\u0441\u0442','\u0421\u0435\u043D\u0442\u044F\u0431\u0440\u044C','\u041E\u043A\u0442\u044F\u0431\u0440\u044C','\u041D\u043E\u044F\u0431\u0440\u044C','\u0414\u0435\u043A\u0430\u0431\u0440\u044C'],today:'\u0421\u0435\u0433\u043E\u0434\u043D\u044F',format:'D.M.Y'}},e=function(){function e(b){var d=this;a(this,e),this.element=b,this.element.setAttribute('data-has-picker','');for(var f=this.element,g='';f.parentNode&&(g=f.getAttribute('lang'),!g);)f=f.parentNode;this.locale=g||'en',this.localeText=this.getLocaleText(),Object.defineProperties(this.element,{value:{get:function(){return d.element.polyfillValue},set:function(a){if(!/^\d{4}-\d{2}-\d{2}$/.test(a))return d.element.polyfillValue='',d.element.setAttribute('value',''),!1;d.element.polyfillValue=a;var b=a.split('-');d.element.setAttribute('value',d.localeText.format.replace('Y',b[0]).replace('M',b[1]).replace('D',b[2]))}},valueAsDate:{get:function(){return d.element.polyfillValue?new Date(d.element.polyfillValue):null},set:function(a){d.element.value=a.toISOString().slice(0,10)}},valueAsNumber:{get:function(){return d.element.value?d.element.valueAsDate.getTime():NaN},set:function(a){d.element.valueAsDate=new Date(a)}}}),this.element.value=this.element.getAttribute('value');var h=function(){c.instance.attachTo(d)};this.element.addEventListener('focus',h),this.element.addEventListener('mousedown',h),this.element.addEventListener('mouseup',h),this.element.addEventListener('keydown',function(a){var b=new Date;switch(a.keyCode){case 27:c.instance.hide();break;case 38:d.element.valueAsDate&&(b.setDate(d.element.valueAsDate.getDate()+1),d.element.valueAsDate=b,c.instance.pingInput());break;case 40:d.element.valueAsDate&&(b.setDate(d.element.valueAsDate.getDate()-1),d.element.valueAsDate=b,c.instance.pingInput());break;default:}c.instance.sync()})}return b(e,[{key:'getLocaleText',value:function(){var a=this.locale.toLowerCase();for(var b in d){var c=b.split('_').map(function(a){return a.toLowerCase()});if(!!~c.indexOf(a))return d[b]}for(var e in d){var f=e.split('_').map(function(a){return a.toLowerCase()});if(!!~f.indexOf(a.substr(0,2)))return d[e]}return this.locale='en',this.getLocaleText()}}],[{key:'supportsDateInput',value:function(){var a=document.createElement('input');a.setAttribute('type','date');var b='not-a-date';return a.setAttribute('value',b),document.currentScript&&!document.currentScript.hasAttribute('data-nodep-date-input-polyfill-debug')&&a.value!==b}},{key:'addPickerToDateInputs',value:function(){var a=document.querySelectorAll('input[type="date"]:not([data-has-picker]):not([readonly])'),b=a.length;if(!b)return!1;for(var c=0;c in the document, also on dynamically created ones. 6 | // Check if type="date" is supported. 7 | if(!Input.supportsDateInput()) { 8 | const init = ()=> { 9 | Picker.instance = new Picker(); 10 | Input.addPickerToDateInputs(); 11 | 12 | // This is also on mousedown event so it will capture new inputs that might 13 | // be added to the DOM dynamically. 14 | document.querySelector(`body`).addEventListener(`mousedown`, ()=> { 15 | Input.addPickerToDateInputs(); 16 | }); 17 | }; 18 | 19 | if(document.readyState === `complete`) { 20 | init(); 21 | } else { 22 | let DOMContentLoaded = false; 23 | 24 | document.addEventListener(`DOMContentLoaded`, ()=> { 25 | DOMContentLoaded = true; 26 | 27 | init(); 28 | }); 29 | 30 | window.addEventListener(`load`, ()=> { 31 | if(!DOMContentLoaded) { 32 | init(); 33 | } 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /nodep-date-input-polyfill.scss: -------------------------------------------------------------------------------- 1 | @mixin reset() { 2 | background: #fff; 3 | color: #000; 4 | text-shadow: none; 5 | border: 0; 6 | padding: 0; 7 | height: auto; 8 | width: auto; 9 | line-height: normal; 10 | border-radius: 0; 11 | font-family: sans-serif; 12 | font-size: 14px; 13 | } 14 | 15 | date-input-polyfill { 16 | @include reset(); 17 | position: absolute !important; 18 | text-align: center; 19 | box-shadow: 20 | 0 7px 8px -4px rgba(0,0,0,.2), 21 | 0 12px 17px 2px rgba(0,0,0,.14), 22 | 0 5px 22px 4px rgba(0,0,0,.12); 23 | cursor: default; 24 | z-index: 1; 25 | 26 | &[data-open="false"] { 27 | display: none; 28 | } 29 | &[data-open="true"] { 30 | display: block; 31 | } 32 | 33 | select, table, th, td { 34 | @include reset(); 35 | box-shadow: none; 36 | } 37 | 38 | select, button { 39 | border: 0; 40 | border-bottom: 1px solid #E0E0E0; 41 | height: 24px; 42 | vertical-align: top; 43 | } 44 | 45 | select { 46 | width: 50%; 47 | 48 | &:first-of-type { 49 | border-right: 1px solid #E0E0E0; 50 | width: 30%; 51 | } 52 | } 53 | button { 54 | padding: 0; 55 | width: 20%; 56 | background: #E0E0E0; 57 | } 58 | 59 | table { 60 | border-collapse: collapse; 61 | } 62 | th, td { 63 | width: 32px; 64 | padding: 4px; 65 | text-align: center; 66 | } 67 | td[data-day] { 68 | cursor: pointer; 69 | 70 | &:hover { 71 | background: #E0E0E0; 72 | } 73 | } 74 | [data-selected] { 75 | font-weight: bold; 76 | background: #D8EAF6; 77 | } 78 | } 79 | 80 | input[data-has-picker] { 81 | &::-ms-clear { 82 | display: none; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodep-date-input-polyfill", 3 | "version": "5.2.0", 4 | "description": "Automatically adds datepickers to input[type=date] on IE, Firefox, and macOS Safari.", 5 | "main": "nodep-date-input-polyfill.dist.js", 6 | "scripts": { 7 | "start": "rollup -c -w & httpster", 8 | "start-c9": "rollup -c -w & httpster -p $PORT", 9 | "build": "rollup -c" 10 | }, 11 | "author": { 12 | "name": "Brian Blakely", 13 | "email": "anewpage.media@gmail.com" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/brianblakely/nodep-date-input-polyfill.git" 18 | }, 19 | "bugs": "https://github.com/brianblakely/nodep-date-input-polyfill/issues", 20 | "keywords": [ 21 | "html5", 22 | "light", 23 | "lightweight", 24 | "input", 25 | "type", 26 | "date", 27 | "datepicker", 28 | "polyfill" 29 | ], 30 | "analyze": true, 31 | "license": "MIT", 32 | "devDependencies": { 33 | "babel-plugin-external-helpers": "^6.22.0", 34 | "babel-preset-env": "^1.6.1", 35 | "eslint": "^4.15.0", 36 | "httpster": "^1.0.3", 37 | "rollup": "^0.53.4", 38 | "rollup-plugin-babel": "^3.0.3", 39 | "rollup-plugin-babel-minify": "^3.1.2", 40 | "rollup-plugin-sass": "^0.5.3", 41 | "rollup-watch": "^4.3.1" 42 | }, 43 | "dependencies": {} 44 | } 45 | -------------------------------------------------------------------------------- /picker.js: -------------------------------------------------------------------------------- 1 | class Picker { 2 | constructor() { 3 | // This is a singleton. 4 | if(Picker.instance) { 5 | return Picker.instance; 6 | } 7 | 8 | this.date = new Date(); 9 | this.input = null; 10 | this.isOpen = false; 11 | 12 | // The picker element. Unique tag name attempts to protect against 13 | // generic selectors. 14 | this.container = document.createElement(`date-input-polyfill`); 15 | 16 | // Add controls. 17 | // Year picker. 18 | this.year = document.createElement(`select`); 19 | Picker.createRangeSelect( 20 | this.year, 21 | this.date.getFullYear() - 80, 22 | this.date.getFullYear() + 20 23 | ); 24 | this.year.className = `yearSelect`; 25 | this.year.addEventListener(`change`, ()=> { 26 | this.date.setYear(this.year.value); 27 | this.refreshDaysMatrix(); 28 | }); 29 | this.container.appendChild(this.year); 30 | 31 | // Month picker. 32 | this.month = document.createElement(`select`); 33 | this.month.className = `monthSelect`; 34 | this.month.addEventListener(`change`, ()=> { 35 | this.date.setMonth(this.month.value); 36 | this.refreshDaysMatrix(); 37 | }); 38 | this.container.appendChild(this.month); 39 | 40 | // Today button. 41 | this.today = document.createElement(`button`); 42 | this.today.textContent = `Today`; 43 | this.today.addEventListener(`click`, ()=> { 44 | this.date = new Date(); 45 | 46 | this.setInput(); 47 | }); 48 | this.container.appendChild(this.today); 49 | 50 | // Setup unchanging DOM for days matrix. 51 | const daysMatrix = document.createElement(`table`); 52 | this.daysHead = document.createElement(`thead`); 53 | this.days = document.createElement(`tbody`); 54 | 55 | // THIS IS THE BIG PART. 56 | // When the user clicks a day, set that day as the date. 57 | // Uses event delegation. 58 | this.days.addEventListener(`click`, e=> { 59 | const tgt = e.target; 60 | 61 | if(!tgt.hasAttribute(`data-day`)) { 62 | return false; 63 | } 64 | 65 | const curSel = this.days.querySelector(`[data-selected]`); 66 | if(curSel) { 67 | curSel.removeAttribute(`data-selected`); 68 | } 69 | tgt.setAttribute(`data-selected`, ``); 70 | 71 | this.date.setDate(parseInt(tgt.textContent)); 72 | this.setInput(); 73 | }); 74 | 75 | daysMatrix.appendChild(this.daysHead); 76 | daysMatrix.appendChild(this.days); 77 | this.container.appendChild(daysMatrix); 78 | 79 | this.hide(); 80 | document.body.appendChild(this.container); 81 | 82 | // Close the picker when clicking outside of a date input or picker. 83 | document.addEventListener(`click`, e=> { 84 | let el = e.target; 85 | let isPicker = el === this.container; 86 | 87 | while(!isPicker && (el = el.parentNode)) { 88 | isPicker = el === this.container; 89 | } 90 | 91 | e.target.getAttribute(`type`) !== `date` && !isPicker 92 | && this.hide(); 93 | }); 94 | } 95 | 96 | // Hide. 97 | hide() { 98 | this.container.setAttribute(`data-open`, this.isOpen = false); 99 | } 100 | 101 | // Show. 102 | show() { 103 | this.container.setAttribute(`data-open`, this.isOpen = true); 104 | } 105 | 106 | // Position picker below element. Align to element's left edge. 107 | goto(element) { 108 | const rekt = element.getBoundingClientRect(); 109 | this.container.style.top = `${ 110 | rekt.top + rekt.height 111 | + (document.documentElement.scrollTop || document.body.scrollTop) 112 | }px`; 113 | this.container.style.left = `${ 114 | rekt.left 115 | + (document.documentElement.scrollLeft || document.body.scrollLeft) 116 | }px`; 117 | 118 | this.show(); 119 | } 120 | 121 | // Initiate I/O with given date input. 122 | attachTo(input) { 123 | if( 124 | input === this.input 125 | && this.isOpen 126 | ) { 127 | return false; 128 | } 129 | 130 | this.input = input; 131 | this.sync(); 132 | this.goto(this.input.element); 133 | } 134 | 135 | // Match picker date with input date. 136 | sync() { 137 | if(this.input.element.valueAsDate) { 138 | this.date = Picker.absoluteDate(this.input.element.valueAsDate); 139 | } else { 140 | this.date = new Date(); 141 | } 142 | 143 | this.year.value = this.date.getFullYear(); 144 | this.month.value = this.date.getMonth(); 145 | this.refreshDaysMatrix(); 146 | } 147 | 148 | // Match input date with picker date. 149 | setInput() { 150 | this.input.element.value = 151 | `${ 152 | this.date.getFullYear() 153 | }-${ 154 | `0${this.date.getMonth()+1}`.slice(-2) 155 | }-${ 156 | `0${this.date.getDate()}`.slice(-2) 157 | }`; 158 | 159 | this.input.element.focus(); 160 | setTimeout(()=> { // IE wouldn't hide, so in a timeout you go. 161 | this.hide(); 162 | }, 100); 163 | 164 | this.pingInput(); 165 | } 166 | 167 | refreshLocale() { 168 | if(this.locale === this.input.locale) { 169 | return false; 170 | } 171 | 172 | this.locale = this.input.locale; 173 | 174 | const daysHeadHTML = [``]; 175 | for(let i = 0, len = this.input.localeText.days.length; i < len; ++i) { 176 | daysHeadHTML.push(`${this.input.localeText.days[i]}`); 177 | } 178 | this.daysHead.innerHTML = daysHeadHTML.join(``); 179 | 180 | Picker.createRangeSelect( 181 | this.month, 182 | 0, 183 | 11, 184 | this.input.localeText.months, 185 | this.date.getMonth() 186 | ); 187 | 188 | this.today.textContent = this.input.localeText.today; 189 | } 190 | 191 | refreshDaysMatrix() { 192 | this.refreshLocale(); 193 | 194 | // Determine days for this month and year, 195 | // as well as on which weekdays they lie. 196 | const year = this.date.getFullYear(); // Get the year (2016). 197 | const month = this.date.getMonth(); // Get the month number (0-11). 198 | const startDay = new Date(year, month, 1).getDay(); // First weekday of month (0-6). 199 | const maxDays = new Date( 200 | this.date.getFullYear(), 201 | month + 1, 202 | 0 203 | ).getDate(); // Get days in month (1-31). 204 | 205 | // The input's current date. 206 | const selDate = Picker.absoluteDate(this.input.element.valueAsDate) || false; 207 | 208 | // Are we in the input's currently-selected month and year? 209 | const selMatrix = 210 | selDate 211 | && year === selDate.getFullYear() 212 | && month === selDate.getMonth(); 213 | 214 | // Populate days matrix. 215 | const matrixHTML = []; 216 | for(let i = 0; i < maxDays + startDay; ++i) { 217 | // Add a row every 7 days. 218 | if(i % 7 === 0) { 219 | matrixHTML.push(` 220 | ${i !== 0 ? `` : ``} 221 | 222 | `); 223 | } 224 | 225 | // Add new column. 226 | // If no days from this month in this column, it will be empty. 227 | if(i + 1 <= startDay) { 228 | matrixHTML.push(``); 229 | continue; 230 | } 231 | 232 | // Populate day number. 233 | const dayNum = i + 1 - startDay; 234 | const selected = selMatrix && selDate.getDate() === dayNum; 235 | 236 | matrixHTML.push( 237 | ` 238 | ${dayNum} 239 | ` 240 | ); 241 | } 242 | 243 | this.days.innerHTML = matrixHTML.join(``); 244 | } 245 | 246 | pingInput() { 247 | // Dispatch DOM events to the input. 248 | let inputEvent; 249 | let changeEvent; 250 | 251 | // Modern event creation. 252 | try { 253 | inputEvent = new Event(`input`); 254 | changeEvent = new Event(`change`); 255 | } 256 | // Old-fashioned way. 257 | catch(e) { 258 | inputEvent = document.createEvent(`KeyboardEvent`); 259 | inputEvent.initEvent(`input`, true, false); 260 | changeEvent = document.createEvent(`KeyboardEvent`); 261 | changeEvent.initEvent(`change`, true, false); 262 | } 263 | 264 | this.input.element.dispatchEvent(inputEvent); 265 | this.input.element.dispatchEvent(changeEvent); 266 | } 267 | 268 | static createRangeSelect(theSelect, min, max, namesArray, selectedValue) { 269 | theSelect.innerHTML = ``; 270 | 271 | for(let i = min; i <= max; ++i) { 272 | const aOption = document.createElement(`option`); 273 | theSelect.appendChild(aOption); 274 | 275 | const theText = namesArray ? namesArray[i - min] : i; 276 | 277 | aOption.text = theText; 278 | aOption.value = i; 279 | 280 | if(i === selectedValue) { 281 | aOption.selected = `selected`; 282 | } 283 | } 284 | 285 | return theSelect; 286 | } 287 | 288 | static absoluteDate(date) { 289 | return date && new Date(date.getTime() + date.getTimezoneOffset()*60*1000); 290 | } 291 | } 292 | 293 | Picker.instance = null; 294 | 295 | export default Picker; 296 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import minify from 'rollup-plugin-babel-minify'; 3 | import sass from 'rollup-plugin-sass'; 4 | 5 | export default { 6 | entry: `nodep-date-input-polyfill.js`, 7 | dest: `nodep-date-input-polyfill.dist.js`, 8 | 9 | plugins: [ 10 | babel({ 11 | include: `*.js`, 12 | exclude: `node_modules/**`, 13 | presets: [[`env`, {modules: false}]], 14 | plugins: [`external-helpers`] 15 | }), 16 | minify({ 17 | comments: false, 18 | sourceMap: false 19 | }), 20 | sass({ 21 | insert: true 22 | }) 23 | ], 24 | 25 | format: 'umd' 26 | }; 27 | --------------------------------------------------------------------------------