├── .babelrc ├── .eslintrc ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.MD ├── dist ├── mc-calendar.min.css └── mc-calendar.min.js ├── package-lock.json ├── package.json ├── sandbox ├── index.js ├── style.css └── template.html ├── src ├── js │ ├── checker.js │ ├── defaults.js │ ├── emiters.js │ ├── events.js │ ├── handlers.js │ ├── instance.js │ ├── listeners.js │ ├── mc-calendar.js │ ├── render.js │ ├── template.js │ ├── utils.js │ └── validators.js └── scss │ ├── _components.buttons.scss │ ├── _components.calendar.scss │ ├── _components.date.scss │ ├── _components.display.scss │ ├── _components.month_year_preview.scss │ ├── _components.picker.scss │ ├── _components.selector.scss │ ├── _components.table.scss │ ├── _elements.all.scss │ ├── _generic.resets.scss │ ├── _objects.layout.scss │ ├── _settings.animations.scss │ ├── _settings.breakpoints.scss │ ├── _settings.colors.scss │ ├── _settings.layout.scss │ ├── _settings.typography.scss │ ├── _tools.mixins.scss │ ├── _tools.transitions.scss │ ├── _utilities.display.scss │ └── main.scss └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb", "prettier", "plugin:node/recommended"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "prettier/prettier": [ 6 | "error", 7 | { 8 | "endOfLine": "auto" 9 | } 10 | ], 11 | "no-unused-vars": "warn", 12 | "no-console": "warn", 13 | "func-names": "off", 14 | "no-process-exit": "off", 15 | "object-shorthand": "off", 16 | "class-methods-use-this": "off", 17 | "import/extensions": ["ignorePackages"] 18 | }, 19 | "parserOptions": { 20 | "sourceType": "module" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | assets 3 | tester 4 | 5 | # File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig 6 | 7 | # Created by https://www.toptal.com/developers/gitignore/api/node,sass,test 8 | # Edit at https://www.toptal.com/developers/gitignore?templates=node,sass,test 9 | 10 | ### Node ### 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | lerna-debug.log* 18 | .pnpm-debug.log* 19 | 20 | # Diagnostic reports (https://nodejs.org/api/report.html) 21 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Dependency directories 34 | node_modules/ 35 | 36 | # Optional npm cache directory 37 | .npm 38 | 39 | # Optional eslint cache 40 | .eslintcache 41 | 42 | # dotenv environment variables file 43 | .env 44 | .env.test 45 | .env.production 46 | 47 | # Stores VSCode versions used for testing VSCode extensions 48 | .vscode-test 49 | 50 | 51 | ### Sass ### 52 | .sass-cache/ 53 | *.css.map 54 | *.sass.map 55 | *.scss.map 56 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "useTabs": true, 4 | "trailingComma": "none", 5 | "jsxBracketSameLine": true, 6 | "printWidth": 100 7 | } 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.6.5 4 | 5 | --- 6 | 7 | - fixed focus of the datepicker linked input 8 | 9 | ## 0.6.3 10 | 11 | --- 12 | 13 | - fixed `autoclose` on month/year preview selection 14 | 15 | ## 0.6.2 16 | 17 | --- 18 | 19 | - fixed `null` reference on `setFullDate` 20 | 21 | ## 0.6.1 22 | 23 | --- 24 | 25 | - fixed `null` context 26 | 27 | ## 0.6.0 28 | 29 | --- 30 | 31 | - added `context` option 32 | - added `theme` option 33 | - added `onClear()` method 34 | 35 | ## 0.5.1 36 | 37 | --- 38 | 39 | -fixed JSON circular structure of the instance 40 | 41 | ## 0.5.0 42 | 43 | --- 44 | 45 | - added `autoClose` option 46 | - added `closeOnBlur` option 47 | - added `closeOndblclick` option 48 | - added `setFullDate()` method 49 | - added `setDate()` method 50 | - added `setMonth()` method 51 | - added `setYear()` method 52 | - fixed styles for safari 53 | - fixed animations for safari 54 | 55 | ## 0.4.0 56 | 57 | --- 58 | 59 | - changed the `el` setting to optional 60 | - added month & year preview 61 | - added `jumpOverDisabled` option 62 | - added `allowedMonths` option 63 | - added `allowedYears` option 64 | - added `disableMonths` option 65 | - added `disableYears` option 66 | - added `onCancel()` method 67 | 68 | ## 0.3.1 69 | 70 | --- 71 | 72 | - Fixed header height & padding 73 | 74 | ## 0.3.0 75 | 76 | --- 77 | 78 | - added `jumpToMinMax` option 79 | - added `customOkBTN` option 80 | - added `customClearBTN` option 81 | - added `customCancelBTN` option 82 | 83 | ## 0.2.2 84 | 85 | --- 86 | 87 | - fixed modal positioning 88 | - fixed inline positioning 89 | - fixed `customWeekdays` validation 90 | - fixed `customMonths` validation 91 | 92 | ## 0.2.1 93 | 94 | --- 95 | 96 | - fixed year check for `minDate` 97 | - fixed year check for `maxDate` 98 | 99 | ## 0.2.0 100 | 101 | --- 102 | 103 | - added `minDate` option. 104 | - added `maxDate` option. 105 | 106 | ## 0.1.0 107 | 108 | --- 109 | 110 | ### Options 111 | 112 | - `el` 113 | - `dateFormat` 114 | - `bodyType` 115 | - `showCalendarDisplay` 116 | - `customWeekDays` 117 | - `customMonths` 118 | - `firstWeekday` 119 | - `selectedDate` 120 | - `disableWeekends` 121 | - `disableWeekDays` 122 | - `disableDates` 123 | - `markDates` 124 | 125 | ### Methods 126 | 127 | - `open()` 128 | - `close()` 129 | - `reset()` 130 | - `destroy()` 131 | - `onOpen()` 132 | - `onClose()` 133 | - `onSelect()` 134 | - `onMonthChange()` 135 | - `onYearChange()` 136 | - `getDay()` 137 | - `getDate()` 138 | - `getMonth()` 139 | - `getYear()` 140 | - `getFullDate()` 141 | - `getFormatedDate()` 142 | - `markDatesCustom()` 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2020 Mike Cojocari 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # MCDatepicker 2 | 3 | Framework-agnostic, highly-customizable Vanilla JavaScript datepicker with zero dependencies. 4 | 5 | ### Features 6 | 7 | - 2 Modes: (modal, inline) 8 | - Dependency free 9 | - Custom weekday and month names 10 | - Disable weekdays and specific dates 11 | - Fully responsive/ Mobile friendly 12 | 13 | ## Installation 14 | 15 | #### Via NPM 16 | 17 | ``` 18 | npm install mc-datepicker --save 19 | ``` 20 | 21 | #### Using CDN 22 | 23 | Include CDN links in the ``... 24 | 25 | ```html 26 | 27 | 28 | 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```html 34 | 35 | ``` 36 | 37 | If you’re using a bundler, e.g. webpack, you’ll need to import ` MCDatepicker` 38 | 39 | ```javascript 40 | import MCDatepicker from 'mc-datepicker'; 41 | ``` 42 | 43 | 1. Create a new instance, and attach it to an input field 44 | 45 | ```javascript 46 | const picker = MCDatepicker.create({ 47 | el: '#datepicker' 48 | }); 49 | ``` 50 | 51 | 2. Customize the datepicker by adding more options 52 | 53 | ```javascript 54 | const picker = MCDatepicker.create({ 55 | el: '#datepicker', 56 | disableWeekends: true 57 | }); 58 | ``` 59 | 60 | 3. Use methods to manipulate the datepicker 61 | 62 | ```javascript 63 | btn.onclick = () => picker.open(); 64 | ``` 65 | 66 | ## Documentation 67 | 68 | [Online Docs](https://mcdatepicker.netlify.app/docs/) 69 | 70 | ## License 71 | 72 | [MIT](LICENSE) 73 | -------------------------------------------------------------------------------- /dist/mc-calendar.min.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;500;600;700;800;900&display=swap);@-webkit-keyframes slide-left{0%{transform:translateX(0)}to{transform:translateX(-.5em)}}@keyframes slide-left{0%{transform:translateX(0)}to{transform:translateX(-.5em)}}@-webkit-keyframes slide-right{0%{transform:translateX(0)}to{transform:translateX(.5em)}}@keyframes slide-right{0%{transform:translateX(0)}to{transform:translateX(.5em)}}@-webkit-keyframes slide-down{0%{transform:translate(-50%,-400px)}to{transform:translate(-50%)}}@keyframes slide-down{0%{transform:translate(-50%,-400px)}to{transform:translate(-50%)}}@-webkit-keyframes zoom-in{0%{transform:translate(-50%,-50%) scale(.9)}to{transform:translate(-50%,-50%) scale(1)}}@keyframes zoom-in{0%{transform:translate(-50%,-50%) scale(.9)}to{transform:translate(-50%,-50%) scale(1)}}@-webkit-keyframes stretch{0%{max-height:0}50%{max-height:0}to{max-height:175px}}@keyframes stretch{0%{max-height:0}50%{max-height:0}to{max-height:175px}}@-webkit-keyframes slide-left-in{0%{transform:translateX(100px)}to{transform:translateX(0)}}@keyframes slide-left-in{0%{transform:translateX(100px)}to{transform:translateX(0)}}@-webkit-keyframes slide-left-out{0%{transform:translateX(0)}to{transform:translateX(-100px)}}@keyframes slide-left-out{0%{transform:translateX(0)}to{transform:translateX(-100px)}}@-webkit-keyframes slide-right-in{0%{transform:translateX(-100px)}to{transform:translateX(0)}}@keyframes slide-right-in{0%{transform:translateX(-100px)}to{transform:translateX(0)}}@-webkit-keyframes slide-right-out{0%{transform:translateX(0)}to{transform:translateX(100px)}}@keyframes slide-right-out{0%{transform:translateX(0)}to{transform:translateX(100px)}}.mc-calendar,.mc-calendar *{line-height:1.2}.mc-calendar,.mc-calendar *,.mc-calendar:after,.mc-calendar :after,.mc-calendar:before,.mc-calendar :before{box-sizing:border-box;margin:0;padding:0}.mc-btn,.mc-select__nav{background:none;border:0;cursor:pointer;font:inherit;line-height:normal;overflow:visible;padding:0}.mc-btn::-moz-focus-inner,.mc-select__nav::-moz-focus-inner{border:0;padding:0}.mc-calendar h1,.mc-calendar h2,.mc-calendar h3{font-weight:500}.mc-container{position:relative;margin:0 auto;width:90%}.mc-calendar{--mc-theme-color:#38ada9;--mc-main-bg:#f5f5f6;--mc-active-text-color:#000;--mc-inactive-text-color:rgba(0,0,0,0.8);--mc-display-foreground:hsla(0,0%,100%,0.8);--mc-display-background:#38ada9;--mc-picker-foreground:#000;--mc-picker-background:#f5f5f6;--mc-picker-header-active:#818181;--mc-picker-header-inactive:rgba(0,0,0,0.2);--mc-weekday-foreground:#38ada9;--mc-btn-success-foreground:#38ada9;--mc-btn-danger-foreground:#e65151;--mc-date-active-def-foreground:#000;--mc-date-active-pick-foreground:#fff;--mc-date-active-pick-background:#38ada9;--mc-date-active-today-foreground:#000;--mc-date-active-today-background:rgba(0,0,0,0.2);--mc-date-inactive-def-foreground:rgba(0,0,0,0.2);--mc-date-inactive-pick-foreground:#38ada9;--mc-date-inactive-pick-background:#38ada9;--mc-date-inactive-today-foreground:rgba(0,0,0,0.2);--mc-date-inactive-today-background:rgba(0,0,0,0.2);--mc-date-marcked-foreground:#38ada9;--mc-prev-active-def-foreground:#000;--mc-prev-active-pick-foreground:#000;--mc-prev-active-pick-background:rgba(0,0,0,0.2);--mc-prev-inactive-def-foreground:rgba(0,0,0,0.2);--mc-prev-inactive-pick-foreground:rgba(0,0,0,0.2);--mc-prev-inactive-pick-background:rgba(0,0,0,0.2);display:flex;position:absolute;top:-100vh;left:50vw;flex-direction:column;font-family:Maven Pro,sans-serif;font-weight:500;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;visibility:hidden;background-color:var(--mc-main-bg)}.mc-calendar:focus{outline:none}.mc-calendar--opened{visibility:visible}.mc-calendar--inline{width:100%;max-width:300px;height:100%;max-height:325px;background:none;font-size:1rem;box-shadow:0 0 1px var(--mc-date-inactive-def-foreground);border-radius:10px;z-index:1002}@media(min-width:625px){.mc-calendar--inline{transform:unset}}.mc-calendar--modal{position:fixed;width:100%;max-width:425px;border-radius:0 0 30px 30px;box-shadow:0 10px 10px 5px rgba(0,0,0,.25);background-color:var(--mc-theme-color);z-index:1002;transform:translate(-50%,-65vh);transition:transform .4s linear .2s,visibility 0s linear .5s,top 0s linear .5s}@media(min-width:625px){.mc-calendar--modal{flex-direction:row;width:auto;max-width:unset;height:400px;border-radius:10px;box-shadow:0 0 30px rgba(0,0,0,.3);opacity:0;transform:translate(-50%,-50%) scale(.9);transition:transform .3s,opacity .3s,visibility 0s linear .3s,top 0s linear .3s}}.mc-calendar--modal.mc-calendar--opened{top:0;transform:translate(-50%);transition:unset;-webkit-animation:slide-down .3s linear;animation:slide-down .3s linear}@media(min-width:625px){.mc-calendar--modal.mc-calendar--opened{top:50vh;opacity:1;visibility:visible;height:95vh;max-height:400px;transform:translate(-50%,-50%) scale(1);-webkit-animation:zoom-in .1s linear;animation:zoom-in .1s linear}}.mc-calendar--permanent{position:relative;top:0;left:0;width:100%;height:100%}.mc-display{display:none;color:var(--mc-display-foreground)}.mc-calendar--modal .mc-display{display:flex;flex-direction:column;max-height:0;transition:max-height .2s linear}@media(min-width:625px){.mc-calendar--modal .mc-display{width:200px;height:100%;max-height:unset;background-color:var(--mc-display-background)}}.mc-calendar--modal.mc-calendar--opened .mc-display{max-height:175px;-webkit-animation:stretch .4s;animation:stretch .4s}@media(min-width:625px){.mc-calendar--modal.mc-calendar--opened .mc-display{transition:unset;max-height:unset;-webkit-animation:unset;animation:unset}}.mc-display__body{display:flex;justify-content:space-between;align-items:center;padding:.5em 0}@media(min-width:625px){.mc-display__body{flex-direction:column;height:100%;padding:0}}.mc-display__header{background-color:rgba(0,0,0,.2)}@media(min-width:625px){.mc-display[data-target=month] .mc-display__header,.mc-display[data-target=year] .mc-display__header{display:none}}.mc-display__day{text-align:center;line-height:1;padding:.5em 0}@supports not (font-size:clamp(1rem,8vw,1.25rem)){.mc-display__day{font-size:8vw}@media(max-width:12.5rem){.mc-display__day{font-size:1rem}}@media(min-width:15.625rem){.mc-display__day{font-size:1.25rem}}}@supports(font-size:clamp(1rem,8vw,1.25rem)){.mc-display__day{font-size:clamp(1rem,8vw,1.25rem)}}.mc-display[data-target=month] .mc-display__day,.mc-display[data-target=year] .mc-display__day{visibility:hidden}@media(min-width:625px){.mc-display__day{padding:1em 0}}.mc-display__data{display:flex;width:50%}@media(min-width:625px){.mc-display__data{width:100%;height:50%;text-align:center}}.mc-display__data--primary{justify-content:flex-end}.mc-display[data-target=month] .mc-display__data--primary,.mc-display[data-target=year] .mc-display__data--primary{display:none}@media(min-width:625px){.mc-display__data--primary{justify-content:center;align-items:flex-end}}.mc-display__data--secondary{flex-direction:column}.mc-display[data-target=month] .mc-display__data--secondary,.mc-display[data-target=year] .mc-display__data--secondary{width:100%;text-align:center}@media(min-width:625px){.mc-display[data-target=month] .mc-display__data--secondary,.mc-display[data-target=year] .mc-display__data--secondary{justify-content:center;height:100%}}@media(min-width:625px){.mc-display__data--secondary{justify-content:space-between}}.mc-display__date{line-height:1}@supports not (font-size:clamp(5rem,40vw,7rem)){.mc-display__date{font-size:40vw}@media(max-width:12.5rem){.mc-display__date{font-size:5rem}}@media(min-width:17.5rem){.mc-display__date{font-size:7rem}}}@supports(font-size:clamp(5rem,40vw,7rem)){.mc-display__date{font-size:clamp(5rem,40vw,7rem)}}@supports not (font-size:clamp(1.2rem,9vw,1.5rem)){.mc-display__month{font-size:9vw}@media(max-width:13.3333333333rem){.mc-display__month{font-size:1.2rem}}@media(min-width:16.6666666667rem){.mc-display__month{font-size:1.5rem}}}@supports(font-size:clamp(1.2rem,9vw,1.5rem)){.mc-display__month{font-size:clamp(1.2rem,9vw,1.5rem)}}.mc-display[data-target=year] .mc-display__month{display:none}@media(min-width:625px){.mc-display__month{height:auto}}.mc-display__year{line-height:.8}@supports not (font-size:clamp(2.4rem,18vw,3rem)){.mc-display__year{font-size:18vw}@media(max-width:13.3333333333rem){.mc-display__year{font-size:2.4rem}}@media(min-width:16.6666666667rem){.mc-display__year{font-size:3rem}}}@supports(font-size:clamp(2.4rem,18vw,3rem)){.mc-display__year{font-size:clamp(2.4rem,18vw,3rem)}}.mc-display[data-target=year] .mc-display__year{padding:.3em 0}@media(min-width:625px){.mc-display__year{height:auto;padding:.5em 0}}.mc-picker{display:flex;flex-direction:column;width:100%;height:100%;background-color:var(--mc-picker-background);color:var(--mc-picker-foreground)}.mc-calendar--modal .mc-picker{height:65vh;max-height:400px;border-radius:30px 30px 0 0}@media(min-width:625px){.mc-calendar--modal .mc-picker{justify-content:center;align-items:flex-end;height:100%;width:425px;border-radius:unset}}.mc-calendar--inline .mc-picker{border-radius:unset}.mc-picker__header{display:flex;justify-content:center;padding:1em 0 .5em;color:var(--mc-picker-header-active)}@supports not (font-size:clamp(1rem,8vw,1.25rem)){.mc-picker__header{font-size:8vw}@media(max-width:12.5rem){.mc-picker__header{font-size:1rem}}@media(min-width:15.625rem){.mc-picker__header{font-size:1.25rem}}}@supports(font-size:clamp(1rem,8vw,1.25rem)){.mc-picker__header{font-size:clamp(1rem,8vw,1.25rem)}}@media(min-width:625px){.mc-calendar--modal .mc-picker__header{padding:.7em 0;justify-content:space-between}}.mc-calendar--inline .mc-picker__header{font-size:1rem;padding:.7em 0 0}.mc-picker__header .icon-angle{height:calc(8vw + .25rem);min-height:1.75rem;max-height:2rem}.mc-picker__body{position:relative;flex:1 0;width:100%}.mc-picker__footer{display:flex;justify-content:space-between;padding:1em 0 2em}.mc-calendar--inline .mc-picker__footer{padding:.5em 0 1em}.mc-select[data-target=year] .mc-select__month{display:none}.mc-select[data-target=year] .mc-select__year{width:100%}.mc-select[data-target=year] .mc-select__year .mc-select__data--year{width:75%;max-width:unset;min-width:unset;justify-content:center}.mc-select[data-target=year] .mc-select__year .mc-select__nav{display:flex;position:relative}.mc-select__month,.mc-select__nav,.mc-select__year{display:flex;align-items:center}.mc-select__nav{outline:revert;position:absolute;text-decoration:none;color:var(--mc-picker-header-active);padding:0 1em}@media(min-width:625px){.mc-calendar--modal .mc-select__nav{position:relative}}.mc-select__nav:focus{-webkit-tap-highlight-color:transparent;touch-action:manipulation}.mc-select__nav--inactive{color:var(--mc-picker-header-inactive);cursor:default}.mc-select__nav--inactive:active{transform:none!important}.mc-select__nav--next,.mc-select__nav--prev{transition:transform .2s ease-in-out}.mc-select__nav--prev:active{transform:translateX(-.5em)}.mc-calendar--inline .mc-select__month .mc-select__nav--prev,.mc-select__nav--prev{left:0}.mc-select__nav--next:active{transform:translateX(.5em)}.mc-calendar--inline .mc-select__month .mc-select__nav--next,.mc-select__nav--next{right:0}.mc-calendar--inline .mc-select__year .mc-select__nav,.mc-calendar--permanent .mc-select__year .mc-select__nav,.mc-select__year .mc-select__nav{display:none}@media(min-width:625px){.mc-select__year .mc-select__nav{display:flex}}.mc-select__data{display:flex;align-items:center;height:calc(8vw + .25rem);min-height:1.75rem;max-height:2rem;overflow:hidden;position:relative;cursor:pointer}@supports not (font-size:clamp(1rem,8vw,1.25rem)){.mc-select__data{font-size:8vw}@media(max-width:12.5rem){.mc-select__data{font-size:1rem}}@media(min-width:15.625rem){.mc-select__data{font-size:1.25rem}}}@supports(font-size:clamp(1rem,8vw,1.25rem)){.mc-select__data{font-size:clamp(1rem,8vw,1.25rem)}}.mc-select__data:after{content:"";position:absolute;top:0;left:0;right:0;bottom:0;opacity:0}.mc-select__data span{line-height:1.2;text-align:center;position:absolute}.mc-select[data-target=year] .mc-select__data span{position:relative}.mc-select__data--month,.mc-select__data--month span{width:40vw;min-width:5rem;max-width:6.25rem}.mc-calendar--inline .mc-select__data--month,.mc-calendar--inline .mc-select__data--month span{width:6.4rem}.mc-select__data--year,.mc-select__data--year span{width:22vw;min-width:3rem;max-width:3.5rem}.mc-calendar--inline .mc-select__data--year,.mc-calendar--inline .mc-select__data--year span{width:3.2rem}.slide-right--in{-webkit-animation:slide-right-in .2s ease;animation:slide-right-in .2s ease}.slide-right--out{-webkit-animation:slide-right-out .2s ease;animation:slide-right-out .2s ease}.slide-left--in{-webkit-animation:slide-left-in .2s ease;animation:slide-left-in .2s ease}.slide-left--out{-webkit-animation:slide-left-out .2s ease;animation:slide-left-out .2s ease}.mc-date{text-align:center;border-radius:5px;font-weight:300;width:14.28571%}.mc-date--active{cursor:pointer;color:var(--mc-date-active-def-foreground)}.mc-date--active.mc-date--today{color:var(--mc-date-active-today-foreground);background-color:var(--mc-date-active-today-background)}.mc-date--active.mc-date--picked{color:var(--mc-date-active-pick-foreground);background-color:var(--mc-date-active-pick-background)}.mc-date--inactive{color:var(--mc-date-inactive-def-foreground);cursor:default}.mc-date--inactive.mc-date--today{box-shadow:0 0 0 1px var(--mc-date-inactive-today-background);color:var(--mc-date-inactive-today-foreground);box-shadow:none}.mc-date--inactive.mc-date--picked{box-shadow:0 0 0 1px var(--mc-date-inactive-pick-background);color:var(--mc-date-inactive-pick-foreground);box-shadow:none}.mc-date--marked{color:var(--mc-date-marcked-foreground);font-weight:500}.mc-table{height:100%;border-collapse:unset}@media(min-width:625px){.mc-calendar--modal .mc-table{border-top:none}}.mc-calendar--inline .mc-table{border-top:none}.mc-table__weekday{text-align:center;padding:.5em 0;color:var(--mc-weekday-foreground);width:14.28571%}.mc-month-year__preview{position:absolute;display:flex;flex-wrap:wrap;justify-content:space-evenly;align-items:center;top:0;left:0;height:100%;width:90%;margin:0 5%;overflow:hidden;visibility:hidden;opacity:0;background-color:var(--mc-picker-background)}.mc-month-year__preview--opened{visibility:visible;opacity:1}.mc-month-year__cell{position:relative;display:flex;justify-content:center;align-items:center;width:30%;height:20%;text-align:center;border-radius:10px;cursor:pointer;color:var(--mc-prev-active-def-foreground)}.mc-month-year__cell:after{content:"";position:absolute;top:0;left:0;right:0;bottom:0;opacity:0;border-radius:10px}.mc-month-year__cell--picked{color:var(--mc-prev-active-pick-foreground);background-color:var(--mc-prev-active-pick-background)}.mc-month-year__cell--inactive{color:var(--mc-prev-inactive-def-foreground);cursor:default}.mc-month-year__cell--inactive.mc-month-year__cell--picked{color:var(--mc-prev-inactive-pick-foreground);box-shadow:0 0 0 1px var(--mc-prev-inactive-pick-background);background-color:transparent}.mc-btn{display:inline-block;font-weight:500;padding:0 .5em}.mc-btn--success{color:var(--mc-btn-success-foreground);margin-left:.5em}.mc-btn--danger{color:var(--mc-btn-danger-foreground);margin-right:.5em}.mc-btn:active{transform:scale3d(.95,.95,.95)}.mc-btn:focus{-webkit-tap-highlight-color:transparent;touch-action:manipulation}@media(min-width:625px){.u-display-none{display:none!important}}.mc-picker-vhidden{border:0;clip:rect(1px,1px,1px,1px);height:1px;overflow:hidden;padding:0;position:absolute;top:0;width:1px} 2 | -------------------------------------------------------------------------------- /dist/mc-calendar.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.MCDatepicker=t():e.MCDatepicker=t()}(self,(function(){return(()=>{"use strict";var e={422:(e,t,n)=>{n.d(t,{default:()=>Oe});var a={theme_color:"#38ada9",main_background:"#f5f5f6",active_text_color:"rgb(0, 0, 0)",inactive_text_color:"rgba(0, 0, 0, 0.2)",display:{foreground:"rgba(255, 255, 255, 0.8)",background:"#38ada9"},picker:{foreground:"rgb(0, 0, 0)",background:"#f5f5f6"},picker_header:{active:"#818181",inactive:"rgba(0, 0, 0, 0.2)"},weekday:{foreground:"#38ada9"},button:{success:{foreground:"#38ada9"},danger:{foreground:"#e65151"}},date:{active:{default:{foreground:"rgb(0, 0, 0)"},picked:{foreground:"#ffffff",background:"#38ada9"},today:{foreground:"rgb(0, 0, 0)",background:"rgba(0, 0, 0, 0.2)"}},inactive:{default:{foreground:"rgba(0, 0, 0, 0.2)"},picked:{foreground:"#38ada9",background:"#38ada9"},today:{foreground:"rgba(0, 0, 0, 0.2)",background:"rgba(0, 0, 0, 0.2)"}},marcked:{foreground:"#38ada9"}},month_year_preview:{active:{default:{foreground:"rgb(0, 0, 0)"},picked:{foreground:"rgb(0, 0, 0)",background:"rgba(0, 0, 0,0.2)"}},inactive:{default:{foreground:"rgba(0, 0, 0, 0.2)"},picked:{foreground:"rgba(0, 0, 0, 0.2)",background:"rgba(0, 0, 0, 0.2)"}}}},r={DMY:["calendar","month","year"],DY:["calendar","month","year"],D:["calendar","month","year"],MY:["month","year"],M:["month"],Y:["year"]};const c={el:null,context:null,dateFormat:"DD-MMM-YYYY",bodyType:"modal",autoClose:!1,closeOndblclick:!0,closeOnBlur:!1,showCalendarDisplay:!0,customWeekDays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],customMonths:["January","February","March","April","May","June","July","August","September","October","November","December"],customOkBTN:"OK",customClearBTN:"Clear",customCancelBTN:"CANCEL",firstWeekday:0,selectedDate:null,minDate:null,maxDate:null,jumpToMinMax:!0,jumpOverDisabled:!0,disableWeekends:!1,disableWeekDays:[],disableDates:[],allowedMonths:[],allowedYears:[],disableMonths:[],disableYears:[],markDates:[],theme:a};var i="show-calendar",o="hide-calendar",l="update-calendar",d="update-display",s="update-header",u="update-preview",v="date-pick",m="preview-pick",f="month-change",h="year-change",p="set-date",y="cancel-calendar",g=function(e){e.dispatchEvent(new CustomEvent(u,{bubbles:!0}))},b=function(e){e.dispatchEvent(new CustomEvent(s,{bubbles:!0}))},_=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];e.dispatchEvent(new CustomEvent(v,{bubbles:!0,detail:{dblclick:t,date:new Date(e.getAttribute("data-val-date"))}}))},k=function(e,t){e.dispatchEvent(new CustomEvent(f,{bubbles:!0,detail:{direction:t}}))},w=function(e,t){e.dispatchEvent(new CustomEvent(h,{bubbles:!0,detail:{direction:t}}))},D=function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];e.dispatchEvent(new CustomEvent(m,{bubbles:!0,detail:{dblclick:t,data:e.children[0].innerHTML}}))},x=function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{instance:null,date:null};e.dispatchEvent(new CustomEvent(p,{bubbles:!0,detail:t}))},M=function(e){e.dispatchEvent(new CustomEvent(y,{bubbles:!0}))};var E=function(e,t,n){var a=(t+1)%e.length,r=((t-1)%e.length+e.length)%e.length,c=(t+1)/e.length,i=(t-e.length)/e.length;return{newIndex:"next"===n?a:r,overlap:"next"===n?~~c:~~i}},L=function(e){return new Promise((function(t,n){setTimeout(t,e)}))},C=function(){var e=null;return{slide:function(t,n,a){var r="prev"===a?"slide-right--out":"slide-left--out",c="prev"===a?"slide-right--in":"slide-left--in";t.classList.add(r),n.classList.add(c),e=L(150).then((function(){t.remove(),n.removeAttribute("style"),n.classList.remove(c)}))},onFinish:function(t){!e&&t(),e&&e.then((function(){return t()})),e=null}}},T=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:new Date,t=arguments.length>1?arguments[1]:void 0,n=t.customWeekDays,a=t.customMonths,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"dd-mmm-yyyy";if(W(e).date()&&$(r.toLocaleLowerCase()).isValid()){var c=e.getDay(),i=e.getDate(),o=e.getMonth(),l=e.getFullYear(),d={d:String(i),dd:String(i).padStart(2,"0"),ddd:n[c].substr(0,3),dddd:n[c],m:String(o+1),mm:String(o+1).padStart(2,"0"),mmm:a[o].substr(0,3),mmmm:a[o],yy:String(l).substr(2),yyyy:String(l)};return $(r.toLocaleLowerCase()).replaceMatch(d)}throw new Error(e+" Is not a Date object.")},O=function(e){return e.setHours(0,0,0,0).valueOf()},S=function(e){var t=e.getBoundingClientRect();return{t:Math.ceil(t.top),l:Math.ceil(t.left),b:Math.ceil(t.bottom),r:Math.ceil(t.right),w:Math.ceil(t.width),h:Math.ceil(t.height)}},Y=function(e,t){var n=function(e,t){var n=window.innerWidth,a=window.innerHeight,r=document.body.offsetHeight,c=S(t),i=S(e);return{vw:n,vh:a,dh:r,elementOffsetTop:c.t+ +window.scrollY,elementOffsetleft:c.l+window.scrollX,elem:c,cal:i}}(e,t),a=n.cal,r=n.elem,c=n.vw,i=n.vh,o=n.dh,l=n.elementOffsetTop,d=n.elementOffsetleft,s=function(e){var t=e.elem,n=e.cal;return{t:t.t-n.h-10,b:t.b+n.h+10,l:t.w>n.w?t.l:t.l-n.w,r:t.w>n.w?t.r:t.r+n.w}}(n),u=function(e){var t=e.elementOffsetTop,n=e.elem,a=e.cal;return{t:t-a.h-10,b:t+n.h+a.h+10}}(n),v=s.l>0,m=c>s.r,f=s.t>0,h=i>s.b,p=u.t>0,y=o>u.b,g=null,b=null;return m&&(b=d),!m&&v&&(b=d+r.w-a.w),m||v||(b=(c-a.w)/2),h&&(g=l+r.h+5),!h&&f&&(g=l-a.h-5),h||f||(y&&(g=l+r.h+5),!y&&p&&(g=l-a.h-5),y||p||(g=(i-a.h)/2)),{top:g,left:b}},j=function(e){return{active:function(){e.classList.remove("mc-select__nav--inactive")},inactive:function(){e.classList.add("mc-select__nav--inactive")}}},N=function(e,t){var n=e.calendar,a=e.calendarDisplay,r=e.calendarHeader,c=e.monthYearPreview;return{display:{target:t,date:null,set setDate(e){this.date=e,a.dispatchEvent(new CustomEvent(d,{bubbles:!0}))}},header:{target:t,month:null,year:null,set setTarget(e){this.target=e,b(r)},set setMonth(e){this.month=e,b(r)},set setYear(e){this.year=e,b(r)}},preview:{target:null,month:null,year:null,set setTarget(e){this.target=e,g(c)},set setMonth(e){this.month=e,g(c)},set setYear(e){this.year=e,g(c)}},calendar:{date:null,set setDate(e){this.date=e,n.dispatchEvent(new CustomEvent(l,{bubbles:!0}))}}}},A=function(e){var t=null,n=null,a=null,r=!1;return{opened:!1,closed:!0,blured:!1,isOpening:!1,isClosing:!1,isBluring:!1,open:function(n){var c=this;this.isClosing||(r=a&&a._id===n._id,this.isOpening=!0,clearTimeout(t),function(e,t){e.dispatchEvent(new CustomEvent(i,{bubbles:!0,detail:{instance:t}}))}(e,n),t=setTimeout((function(){c.isOpening=!1,c.opened=!0,c.closed=!1,a=n}),200))},close:function(){var t=this;this.closed||this.isOpening||this.isClosing||(r=!1,this.isClosing=!0,clearTimeout(n),e.dispatchEvent(new CustomEvent(o,{bubbles:!0})),n=setTimeout((function(){t.isClosing=!1,t.opened=!1,t.closed=!0}),200))},blur:function(){var e=this;return this.isBluring=!0,L(100).then((function(){return e.closed||e.isOpening||e.isClosing?!r:!(a&&!a.options.closeOnBlur)&&(e.close(),e.isBluring=!1,e.blured=!0,!0)}))}}},P=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:16;return parseInt(Math.ceil(Math.random()*Date.now()).toPrecision(e)).toString(16)};function F(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,a)}return n}function V(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,a=new Array(t);n1?t-1:0),r=1;r=O(e.maxDate)&&n.push(new Error("maxDate should be greater than minDate")),a.length>0&&n.push.apply(n,q(a)),n.length>0?n.forEach((function(e){return console.error(e)})):(t.context=document.body,V(V({},t),e))},Q=function(e,t){return'').concat(t,"")};var ee=function(e){e.linkedElement&&(e.linkedElement.onfocus=function(t){e.open()})},te=function(e,t){return!(!e||!t)&&O(e)O(t)},ae=function(e,t){var n=e.allowedMonths,a=e.disableMonths;return n.length?n.includes(t):!a.includes(t)},re=function(e,t){var n=e.disableYears,a=e.allowedYears;return a.length?a.includes(t):!n.includes(t)},ce=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;return{date:e,day:e.getDay(),dateNumb:e.getDate(),month:e.getMonth(),year:e.getFullYear(),classList:[]}},ie=function(e,t){var n=e.options,a=e.pickedDate,r=new Date(t.getFullYear(),t.getMonth(),1),c=r.getMonth(),i=function(t){var r=["mc-date"];return t.ariaLabel=t.date.toDateString(),!function(e,t){return t.month===e}(c,t)||!ae(n,t.month)||!re(n,t.year)||function(e,t){var n=e.prevLimitDate,a=e.nextLimitDate,r=t.date,c=!!n&&O(r)-6?r:1;n.length<42;){var c=new Date(t),i=new Date(c.setDate(r++));n.push(ce(i))}return n}(n,r).map((function(e){return i(e)}))};function oe(e){var t=document.createElement("div");t.className="mc-calendar",t.setAttribute("tabindex",0),t.innerHTML='
\n
\n

Thursday

\n
\n
\n
\n

1

\n
\n
\n

January

\n

1970

\n
\n
\n
\n
\n
\n
\n\n\n\n
\n
\n\n\n\n
\n
January 1970
\n
\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
SMTWTFS
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567
\n\n
\n\n
',document.body.appendChild(t);var n=se(t);return function(e){var t=null,n=!0,a=e.calendarStates,r=e.calendar,c=e.calendarDisplay,g=e.calendarPicker,b=e.calendarHeader,x=e.currentMonthSelect,E=e.currentYearSelect,L=e.monthYearPreview,T=e.monthNavPrev,O=e.monthNavNext,S=e.yearNavPrev,Y=e.yearNavNext,j=e.dateCells,N=e.previewCells,A=e.cancelButton,P=e.okButton,F=e.clearButton;r.addEventListener(i,(function(n){t=n.detail.instance,xe(e,t),r.classList.add("mc-calendar--opened"),t.onOpenCallbacks.forEach((function(e){return e.apply(null)}))})),r.addEventListener(o,(function(){var e=t,n=e.options,a=e.onCloseCallbacks;r.classList.remove("mc-calendar--opened"),"inline"==n.bodyType&&r.removeAttribute("style"),t=null,a.forEach((function(e){return e.apply(null)}))})),r.addEventListener(v,(function(e){if(t){var n=t.options,r=n.autoClose,c=n.closeOndblclick;if(!e.target.classList.contains("mc-date--inactive")){if(e.detail.dblclick){if(!c)return;return pe(t,a)}t.pickedDate=e.detail.date,t.store.display.setDate=e.detail.date,j.forEach((function(e){return e.classList.remove("mc-date--picked")})),e.target.classList.add("mc-date--picked"),r&&pe(t,a)}}})),r.addEventListener(m,(function(e){if(t){var n=e.detail,r=n.data,c=n.dblclick,i=t,o=i.store,l=i.options,d=i.viewLayers,s=l.customMonths,u=l.autoClose,v=l.closeOndblclick,m=o.preview.target;if(!e.target.classList.contains("mc-month-year__cell--inactive")){if(N.forEach((function(e){return e.classList.remove("mc-month-year__cell--picked")})),e.target.classList.add("mc-month-year__cell--picked"),c&&o.preview.target===d[0]){if(!v)return;return pe(t,a)}var f=o.preview.year,h=s[o.header.month];"year"===d[0]&&(h=s[0]),"month"===m&&(h=r),"year"===m&&(f=Number(r));var p=s.findIndex((function(e){return e.includes(h)})),y=ve(t,new Date(f,p));if(o.header.month=y.getMonth(),o.preview.year=y.getFullYear(),"year"!==d[0]&&(o.header.year=y.getFullYear()),o.preview.month=y.getMonth(),"calendar"!==d[0]&&(t.pickedDate=y),"calendar"!==d[0]&&(o.display.setDate=y),"calendar"===d[0]&&(o.calendar.setDate=y),u&&o.preview.target===d[0])return pe(t,a);o.preview.setTarget=d[0],o.header.setTarget=d[0],x.setAttribute("aria-expanded",!1),E.setAttribute("aria-expanded",!1),"month"==m&&x.focus(),"year"==m&&E.focus()}}})),r.addEventListener(p,(function(e){var n,a=e.detail,r=a.instance,c=a.date;if(r.pickedDate=c,ye(r),(null===(n=t)||void 0===n?void 0:n._id)===r._id){var i=t.store;i.display.setDate=c,i.calendar.setDate=i.calendar.date,"calendar"!==i.preview.target&&(i.preview.month=c.getMonth(),i.preview.year=c.getFullYear(),i.preview.setTarget=i.preview.target),"month"===i.header.target&&(i.header.month=c.getMonth(),i.header.year=c.getFullYear(),i.header.setTarget=i.header.target)}})),r.addEventListener(l,(function(n){return t&&be(e,t)})),document.addEventListener("click",(function(e){var n,c=e.target,i=r.contains(c),o=(null===(n=t)||void 0===n?void 0:n.linkedElement)===c;i||o||!t||a.blur()})),r.addEventListener(y,(function(e){t&&(t.onCancelCallbacks.forEach((function(e){return e.apply(null)})),a.close())})),c.addEventListener(d,(function(n){t&&ge(e,t)})),b.addEventListener(s,(function(n){return t&&_e(e,t)})),L.addEventListener(u,(function(n){return t&&ke(e,t)})),x.addEventListener(f,(function(e){if(n&&t){n=!n;var a=C(),r=t,c=r.store,i=r.viewLayers,o=r.options,l=r.onMonthChangeCallbacks,d=r.onYearChangeCallbacks,s=o.customMonths,u=e.detail.direction,v=s[c.header.month],m=c.header.year,f=me(t,v,u),h=f.newMonth,p=f.overlap,y=0!==p?fe(o,m,u):m,g=new Date(y,h.index,1);0!==p&&(E.innerHTML+=Q(u,y),a.slide(E.children[0],E.children[1],u),d.forEach((function(e){return e.apply(null)}))),e.target.innerHTML+=Q(u,h.name),a.slide(e.target.children[0],e.target.children[1],u),a.onFinish((function(){"calendar"===i[0]&&(c.calendar.setDate=g),"calendar"!==i[0]&&(c.display.setDate=g),"month"===i[0]&&(t.pickedDate=g),c.header.year=g.getFullYear(),c.header.setMonth=g.getMonth(),c.preview.year=g.getFullYear(),c.preview.setMonth=g.getMonth(),l.forEach((function(e){return e.apply(null)})),n=!n}))}})),E.addEventListener(h,(function(e){if(n&&t){n=!n;var a=e.detail.direction,r=t,c=r.store,i=r.viewLayers,o=r.options,l=r.onMonthChangeCallbacks,d=r.onYearChangeCallbacks,s=r.prevLimitDate,u=r.nextLimitDate,v=o.customMonths,m=C(),f="next"===a,h=c.header.year,p=c.header.month,y=c.header.target,g=fe(o,h,a),b=null,_=g&&ve(t,new Date(g,p,1));if(g||(_=f?u:s),_.getMonth()!==p&&(b=v[_.getMonth()]),"year"===y){var k=c.header.year,w=f?k+12:k-12;return c.header.setYear=w,c.preview.setTarget="year",void(n=!n)}b&&(x.innerHTML+=Q(a,b),m.slide(x.children[0],x.children[1],a),l.forEach((function(e){return e.apply(null)}))),g&&(e.target.innerHTML+=Q(a,g),m.slide(e.target.children[0],e.target.children[1],a),d.forEach((function(e){return e.apply(null)}))),m.onFinish((function(){"calendar"===i[0]&&(c.calendar.setDate=_),"calendar"!==i[0]&&(c.display.setDate=_),"calendar"!==i[0]&&(t.pickedDate=_),c.preview.year=_.getFullYear(),c.preview.setMonth=_.getMonth(),c.header.year=_.getFullYear(),c.header.setMonth=_.getMonth(),n=!n}))}})),x.addEventListener("click",(function(){return t&&we(t,e)})),E.addEventListener("keydown",(function(n){"Enter"==n.key&&we(t,e,"keyboard"),"Tab"!=n.key||n.shiftKey||(n.preventDefault(),E.focus())})),E.addEventListener("click",(function(){return t&&De(t,e)})),E.addEventListener("keydown",(function(n){if("Enter"==n.key&&De(t,e,"keyboard"),"Tab"==n.key){if(n.preventDefault(),n.shiftKey)return x.focus();O.focus()}})),N.forEach((function(e){e.addEventListener("click",(function(e){return 1===e.detail&&D(e.currentTarget)})),e.addEventListener("dblclick",(function(e){return 2===e.detail&&D(e.currentTarget,!0)})),e.addEventListener("keydown",(function(e){return"Enter"===e.key&&D(e.currentTarget)}))})),j.forEach((function(e){e.addEventListener("click",(function(e){return 1===e.detail&&_(e.target)})),e.addEventListener("dblclick",(function(e){return 2===e.detail&&_(e.target,!0)})),e.addEventListener("keydown",(function(e){"Enter"===e.key&&_(e.target),"End"===e.key&&F.focus()}))})),T.addEventListener("click",(function(e){e.currentTarget.classList.contains("mc-select__nav--inactive")||k(x,"prev")})),O.addEventListener("click",(function(e){e.currentTarget.classList.contains("mc-select__nav--inactive")||k(x,"next")})),O.addEventListener("keydown",(function(e){if("Tab"==e.key){if(e.preventDefault(),e.shiftKey)return E.focus();b.nextElementSibling.querySelector('[tabindex="0"]').focus()}})),S.addEventListener("click",(function(e){e.currentTarget.classList.contains("mc-select__nav--inactive")||w(E,"prev")})),Y.addEventListener("click",(function(e){e.currentTarget.classList.contains("mc-select__nav--inactive")||w(E,"next")})),A.addEventListener("click",(function(e){return M(r)})),g.addEventListener("keyup",(function(e){return"Escape"==e.key&&M(r)})),P.addEventListener("click",(function(e){return t&&pe(t,a)})),F.addEventListener("click",(function(e){if(t){var n=t,a=n.linkedElement,r=n.onClearCallbacks;j.forEach((function(e){return e.classList.remove("mc-date--picked")})),t.pickedDate=null,a&&(a.value=null),r.forEach((function(e){return e.apply(null)}))}}))}(n),n}function le(e){return function(e){if(Array.isArray(e))return de(e)}(e)||function(e){if("undefined"!=typeof Symbol&&Symbol.iterator in Object(e))return Array.from(e)}(e)||function(e,t){if(!e)return;if("string"==typeof e)return de(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return de(e,t)}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function de(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=new Array(t);n1&&void 0!==arguments[1]?arguments[1]:null,n=e.options,a=e.pickedDate,r=e.prevLimitDate,c=e.nextLimitDate,i=e.activeMonths,o=a||new Date,l=o.getMonth();if(!ae(n,l)){var d=i.reduce((function(e,t){return Math.abs(t.index-l)f-1&&y.inactive(),void(l&&l.getFullYear()".concat(o[s],""),a.innerHTML="".concat(u,""),c.innerText="".concat(o[s]," ").concat(u);else{var v=u;a.innerHTML="".concat(v," - ").concat(v+11,"")}},ke=function(e,t){if(t){var n=e.monthYearPreview,a=t.store.preview.target,r=t.store.header.year;if("calendar"===a)return n.classList.remove("mc-month-year__preview--opened");n.setAttribute("data-target",a),n.classList.add("mc-month-year__preview--opened"),"month"==a&&function(e,t){var n=e.previewCells,a=e.currentMonthSelect,r=t.store,c=t.prevLimitDate,i=t.nextLimitDate,o=t.options,l=o.customMonths,d=l[r.preview.month],s=r.preview.year;a.setAttribute("aria-expanded",!0),l.map((function(e,t){var a=["mc-month-year__cell"],r=new Date(Number(s),t),l=new Date(Number(s),t+1,0),u=c&&O(l)O(i),m=e;e===d&&(a.push("mc-month-year__cell--picked"),m="Current Month: ".concat(m)),u||v||!ae(o,t)||!re(o,Number(s))?(a.push("mc-month-year__cell--inactive"),n[t].setAttribute("tabindex",-1)):n[t].setAttribute("tabindex",0),n[t].classList=a.join(" "),n[t].innerHTML="".concat(e.substr(0,3),""),n[t].setAttribute("aria-label",e)}))}(e,t),"year"==a&&function(e,t,n){var a=e.previewCells,r=e.currentYearSelect,c=t.store,i=t.prevLimitDate,o=t.nextLimitDate,l=t.options,d=i&&i.getFullYear(),s=o&&o.getFullYear(),u=c.preview.year;r.setAttribute("aria-expanded",!0),a.forEach((function(e,t){var r=["mc-month-year__cell"],c=n+t,v=i&&cs;c===u&&r.push("mc-month-year__cell--picked"),v||m||!re(l,c)?(r.push("mc-month-year__cell--inactive"),a[t].setAttribute("tabindex",-1)):a[t].setAttribute("tabindex",0),e.classList=r.join(" "),e.innerHTML="".concat(c,"")}))}(e,t,r)}},we=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"click",a=e.store,r=e.viewLayers;if("month"!==r[0]){var c=t.monthYearPreview,i=c.classList.contains("mc-month-year__preview--opened"),o="month"===a.preview.target;i&&o?a.preview.setTarget=r[0]:(a.header.setTarget="month",a.preview.setTarget="month","keyboard"==n&&c.querySelector('[tabindex="0"]').focus())}},De=function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"click",a=e.store,r=e.viewLayers;if("year"!==r[0]){var c=t.monthYearPreview,i=c.classList.contains("mc-month-year__preview--opened"),o=a.preview.target,l="year"===o;if(i&&l)return a.header.year=a.preview.year,a.preview.setTarget=r[0],void(a.header.setTarget=r[0]);a.header.year=a.preview.year-4,a.header.setTarget="year",a.preview.setTarget="year","keyboard"==n&&c.querySelector('[tabindex="0"]').focus()}},xe=function(e,t){var n=e.calendar,a=t.store,r=t.viewLayers,c=t.options,i=t.pickedDate,o=c.bodyType,l=c.theme,d=ve(t),s=d.getFullYear(),u=d.getMonth();n.classList="mc-calendar",n.classList.add("mc-calendar--".concat(o)),a.display.target=r[0],a.display.setDate=i||new Date,a.calendar.setDate=d,a.header.month=u,a.header.year="year"===r[0]?s-4:s,a.preview.month=u,a.preview.year=s,a.header.setTarget=r[0],a.preview.setTarget=r[0],function(e,t){Object.values(t).forEach((function(t){return e.style.setProperty(t.cssVar,t.color)}))}(n,l),function(e,t){var n=e.weekdays,a=t.customWeekDays,r=t.firstWeekday;n.forEach((function(e,t){var n=(r+t)%a.length;e.innerText=a[n].substr(0,2),e.setAttribute("aria-label",a[n])}))}(e,c),function(e,t){var n=t.customOkBTN,a=t.customClearBTN,r=t.customCancelBTN,c=e.okButton,i=e.clearButton,o=e.cancelButton;c.innerText=n,i.innerText=a,o.innerText=r}(e,c),function(e,t){var n=e.calendar,a=t.options,r=t.linkedElement;if("inline"===a.bodyType){var c=Y(n,r),i=c.top,o=c.left;n.style.top="".concat(i,"px"),n.style.left="".concat(o,"px")}else n.style.removeProperty("top"),n.style.removeProperty("left")}(e,t)};function Me(e,t,n){n.allowedYears.sort((function(e,t){return e-t}));var r=null!==n.el?n.context.querySelector(n.el):null,c=he(n),i=function(e){var t=e.minDate,n=e.maxDate,a=e.allowedYears,r=null,c=null,i=he(e),o=i[0],l=i[i.length-1],d=a.length?Math.min.apply(Math,le(a)):null,s=a.length?Math.max.apply(Math,le(a)):null,u=d?new Date(d,o.index,1):null,v=s?new Date(s,l.index+1,0):null;return t&&u&&(r=new Date(Math.max(t,u))),n&&v&&(c=new Date(Math.min(n,v))),r||(r=t||u),c||(c=n||v),{prevLimitDate:r,nextLimitDate:c}}(n),o=i.prevLimitDate,l=i.nextLimitDate,d=ue(n),s=N(t,d[0]),u=function(e,t){var n,a,r,c,i,o,l,d,s,u,v,m,f,h,p,y,g,b,_,k,w,D,x,M,E,L,C,T,O,S,Y,j,N,A,P,F,V,B,q,H,W,I,$,z,J,K,U,R,X,G,Z,Q,ee,te,ne,ae,re,ce,ie,oe,le;return{theme_color:{cssVar:"--mc-theme-color",color:(null==t?void 0:t.theme_color)||e.theme_color},main_background:{cssVar:"--mc-main-bg",color:(null==t?void 0:t.main_background)||e.main_background},active_text_color:{cssVar:"--mc-active-text-color",color:(null==t?void 0:t.active_text_color)||e.active_text_color},inactive_text_color:{cssVar:"--mc-inactive-text-color",color:(null==t?void 0:t.inactive_text_color)||e.inactive_text_color},display_foreground:{cssVar:"--mc-display-foreground",color:(null==t||null===(n=t.display)||void 0===n?void 0:n.foreground)||e.display.foreground},display_background:{cssVar:"--mc-display-background",color:(null==t||null===(a=t.display)||void 0===a?void 0:a.background)||(null==t?void 0:t.theme_color)||e.display.background},picker_foreground:{cssVar:"--mc-picker-foreground",color:(null==t||null===(r=t.picker)||void 0===r?void 0:r.foreground)||(null==t?void 0:t.active_text_color)||e.picker.foreground},picker_background:{cssVar:"--mc-picker-background",color:(null==t||null===(c=t.picker)||void 0===c?void 0:c.background)||(null==t?void 0:t.main_background)||e.picker.background},picker_header_active:{cssVar:"--mc-picker-header-active",color:(null==t||null===(i=t.picker_header)||void 0===i?void 0:i.active)||e.picker_header.active},picker_header_inactive:{cssVar:"--mc-picker-header-inactive",color:(null==t||null===(o=t.picker_header)||void 0===o?void 0:o.inactive)||(null==t?void 0:t.inactive_text_color)||e.picker_header.inactive},weekday_foreground:{cssVar:"--mc-weekday-foreground",color:(null==t||null===(l=t.weekday)||void 0===l?void 0:l.foreground)||(null==t?void 0:t.theme_color)||e.weekday.foreground},button_success_foreground:{cssVar:"--mc-btn-success-foreground",color:(null==t||null===(d=t.button)||void 0===d||null===(s=d.success)||void 0===s?void 0:s.foreground)||(null==t?void 0:t.theme_color)||e.button.success.foreground},button_danger_foreground:{cssVar:"--mc-btn-danger-foreground",color:(null==t||null===(u=t.button)||void 0===u||null===(v=u.danger)||void 0===v?void 0:v.foreground)||e.button.danger.foreground},date_active_default_foreground:{cssVar:"--mc-date-active-def-foreground",color:(null==t||null===(m=t.date)||void 0===m||null===(f=m.active)||void 0===f||null===(h=f.default)||void 0===h?void 0:h.foreground)||(null==t?void 0:t.active_text_color)||e.date.active.default.foreground},date_active_picked_foreground:{cssVar:"--mc-date-active-pick-foreground",color:(null==t||null===(p=t.date)||void 0===p||null===(y=p.active)||void 0===y||null===(g=y.picked)||void 0===g?void 0:g.foreground)||e.date.active.picked.foreground},date_active_picked_background:{cssVar:"--mc-date-active-pick-background",color:(null==t||null===(b=t.date)||void 0===b||null===(_=b.active)||void 0===_||null===(k=_.picked)||void 0===k?void 0:k.background)||(null==t?void 0:t.theme_color)||e.date.active.picked.background},date_active_today_foreground:{cssVar:"--mc-date-active-today-foreground",color:(null==t||null===(w=t.date)||void 0===w||null===(D=w.active)||void 0===D||null===(x=D.today)||void 0===x?void 0:x.foreground)||(null==t?void 0:t.active_text_color)||e.date.active.today.foreground},date_active_today_background:{cssVar:"--mc-date-active-today-background",color:(null==t||null===(M=t.date)||void 0===M||null===(E=M.active)||void 0===E||null===(L=E.today)||void 0===L?void 0:L.background)||(null==t?void 0:t.inactive_text_color)||e.date.active.today.background},date_inactive_default_foreground:{cssVar:"--mc-date-inactive-def-foreground",color:(null==t||null===(C=t.date)||void 0===C||null===(T=C.inactive)||void 0===T||null===(O=T.default)||void 0===O?void 0:O.foreground)||(null==t?void 0:t.inactive_text_color)||e.date.inactive.default.foreground},date_inactive_picked_foreground:{cssVar:"--mc-date-inactive-pick-foreground",color:(null==t||null===(S=t.date)||void 0===S||null===(Y=S.inactive)||void 0===Y||null===(j=Y.picked)||void 0===j?void 0:j.foreground)||(null==t?void 0:t.theme_color)||e.date.inactive.picked.foreground},date_inactive_picked_background:{cssVar:"--mc-date-inactive-pick-background",color:(null==t||null===(N=t.date)||void 0===N||null===(A=N.inactive)||void 0===A||null===(P=A.picked)||void 0===P?void 0:P.background)||(null==t?void 0:t.theme_color)||e.date.inactive.picked.background},date_inactive_today_foreground:{cssVar:"--mc-date-inactive-today-foreground",color:(null==t||null===(F=t.date)||void 0===F||null===(V=F.inactive)||void 0===V||null===(B=V.today)||void 0===B?void 0:B.foreground)||(null==t?void 0:t.inactive_text_color)||e.date.inactive.today.foreground},date_inactive_today_background:{cssVar:"--mc-date-inactive-today-background",color:(null==t||null===(q=t.date)||void 0===q||null===(H=q.inactive)||void 0===H||null===(W=H.today)||void 0===W?void 0:W.background)||(null==t?void 0:t.inactive_text_color)||e.date.inactive.today.background},date_marcked_foreground:{cssVar:"--mc-date-marcked-foreground",color:(null==t||null===(I=t.date)||void 0===I||null===($=I.marcked)||void 0===$?void 0:$.foreground)||(null==t?void 0:t.theme_color)||e.date.marcked.foreground},month_year_preview_active_default_foreground:{cssVar:"--mc-prev-active-def-foreground",color:(null==t||null===(z=t.month_year_preview)||void 0===z||null===(J=z.active)||void 0===J||null===(K=J.default)||void 0===K?void 0:K.foreground)||(null==t?void 0:t.active_text_color)||e.month_year_preview.active.default.foreground},month_year_preview_active_picked_foreground:{cssVar:"--mc-prev-active-pick-foreground",color:(null==t||null===(U=t.month_year_preview)||void 0===U||null===(R=U.active)||void 0===R||null===(X=R.picked)||void 0===X?void 0:X.foreground)||(null==t?void 0:t.active_text_color)||e.month_year_preview.active.picked.foreground},month_year_preview_active_picked_background:{cssVar:"--mc-prev-active-pick-background",color:(null==t||null===(G=t.month_year_preview)||void 0===G||null===(Z=G.active)||void 0===Z||null===(Q=Z.picked)||void 0===Q?void 0:Q.background)||e.month_year_preview.active.picked.background},month_year_preview_inactive_default_foreground:{cssVar:"--mc-prev-inactive-def-foreground",color:(null==t||null===(ee=t.month_year_preview)||void 0===ee||null===(te=ee.inactive)||void 0===te||null===(ne=te.default)||void 0===ne?void 0:ne.foreground)||(null==t?void 0:t.inactive_text_color)||e.month_year_preview.inactive.default.foreground},month_year_preview_inactive_picked_foreground:{cssVar:"--mc-prev-inactive-pick-foreground",color:(null==t||null===(ae=t.month_year_preview)||void 0===ae||null===(re=ae.inactive)||void 0===re||null===(ce=re.picked)||void 0===ce?void 0:ce.foreground)||(null==t?void 0:t.inactive_text_color)||e.month_year_preview.inactive.picked.foreground},month_year_preview_inactive_picked_background:{cssVar:"--mc-prev-inactive-pick-background",color:(null==t||null===(ie=t.month_year_preview)||void 0===ie||null===(oe=ie.inactive)||void 0===oe||null===(le=oe.picked)||void 0===le?void 0:le.background)||(null==t?void 0:t.inactive_text_color)||e.month_year_preview.inactive.picked.background}}}(a,n.theme);return n.theme=u,{_id:P(),datepicker:e,el:n.el,context:n.context,linkedElement:r,pickedDate:n.selectedDate,viewLayers:d,activeMonths:c,prevLimitDate:o,nextLimitDate:l,options:n,onOpenCallbacks:[],onCloseCallbacks:[],onSelectCallbacks:[],onCancelCallbacks:[],onClearCallbacks:[],onMonthChangeCallbacks:[],onYearChangeCallbacks:[],markCustomCallbacks:[],store:s,open:function(){e.open(this._id)},close:function(){e.close()},reset:function(){this.pickedDate=null,this.linkedElement&&(this.linkedElement.value=null)},destroy:function(){e.remove(this._id)},onOpen:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){};this.onOpenCallbacks.push(e)},onClose:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:function(){};this.onCloseCallbacks.push(e)},onSelect:function(e){this.onSelectCallbacks.push(e)},onCancel:function(e){this.onCancelCallbacks.push(e)},onClear:function(e){this.onClearCallbacks.push(e)},onMonthChange:function(e){this.onMonthChangeCallbacks.push(e)},onYearChange:function(e){this.onYearChangeCallbacks.push(e)},getDay:function(){return this.pickedDate?this.pickedDate.getDay():null},getDate:function(){return this.pickedDate?this.pickedDate.getDate():null},getMonth:function(){return this.pickedDate?this.pickedDate.getMonth():null},getYear:function(){return this.pickedDate?this.pickedDate.getFullYear():null},getFullDate:function(){return this.pickedDate},getFormatedDate:function(){return this.pickedDate?T(this.pickedDate,this.options,this.options.dateFormat):null},markDatesCustom:function(e){this.markCustomCallbacks.push(e)},setFullDate:function(e){if(!W(e).date())throw new TypeError("Parameter of setFullDate() is not of type date");x(t.calendar,{instance:this,date:e})},setDate:function(e){if(!W(e).number())throw new TypeError("Parameter 'date' of setDate() is not of type number");var n=this.pickedDate?new Date(this.pickedDate):new Date;n.setDate(e),x(t.calendar,{instance:this,date:n})},setMonth:function(e){if(!W(e).number())throw new TypeError("Parameter 'month' of setMonth() is not of type number");var n=this.pickedDate?new Date(this.pickedDate):new Date;n.setMonth(e),x(t.calendar,{instance:this,date:n})},setYear:function(e){if(!W(e).number())throw new TypeError("Parameter 'year' of setYear() is not of type number");var n=this.pickedDate?new Date(this.pickedDate):new Date;n.setFullYear(e),x(t.calendar,{instance:this,date:n})}}}var Ee,Le,Ce,Te=(Ee=[],Le=null,Ce=function(e){Le||(Le=oe())},{create:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=Z(e,c);Ce();var n=Me(Te,Le,t);return Ee.push(n),ee(n),n},remove:function(e){var t,n=Ee.find((function(t){return t._id===e}));if(Ee.length&&n&&((t=n.linkedElement)&&(t.onfocus=null),Ee.splice(Ee.indexOf(n),1),!Ee.length)){var a=Le.calendar;a.parentNode.removeChild(a),Le=null}},open:function(e){var t=Ee.find((function(t){return t._id===e}));(t||Le)&&Le.calendarStates.open(t)},close:function(){Le&&Le.calendarStates.close()}});const Oe=Te}},t={};function n(a){if(t[a])return t[a].exports;var r=t[a]={exports:{}};return e[a](r,r.exports,n),r.exports}return n.d=(e,t)=>{for(var a in t)n.o(t,a)&&!n.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},n.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),n(422)})().default})); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mc-datepicker", 3 | "version": "0.6.5", 4 | "description": "Framework-agnostic, highly-customizable Vanilla JavaScript datepicker with zero dependencies.", 5 | "main": "./src/js/mc-calendar.js", 6 | "scripts": { 7 | "esrbnb": "npm i -D eslint prettier eslint-plugin-prettier eslint-config-prettier eslint-plugin-node eslint-config-node", 8 | "start": "webpack-dev-server --env.dev --open", 9 | "build": "webpack --env.prod" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/mikecoj/Datepicker.git" 14 | }, 15 | "keywords": [], 16 | "author": "Mike Cojocari", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/mikecoj/Datepicker/issues" 20 | }, 21 | "homepage": "https://mcdatepicker.netlify.app/", 22 | "devDependencies": { 23 | "@babel/core": "^7.12.10", 24 | "@babel/preset-env": "^7.12.11", 25 | "autoprefixer": "^10.1.0", 26 | "babel-loader": "^8.2.2", 27 | "clean-webpack-plugin": "^3.0.0", 28 | "css-loader": "^5.0.1", 29 | "css-minimizer-webpack-plugin": "^1.1.5", 30 | "eslint": "^7.16.0", 31 | "eslint-config-node": "^4.1.0", 32 | "eslint-config-prettier": "^7.1.0", 33 | "eslint-plugin-node": "^11.1.0", 34 | "eslint-plugin-prettier": "^3.3.0", 35 | "extract-loader": "^5.1.0", 36 | "html-loader": "^1.3.2", 37 | "html-webpack-plugin": "^4.5.0", 38 | "mini-css-extract-plugin": "^1.3.3", 39 | "postcss": "^8.2.1", 40 | "postcss-loader": "^4.1.0", 41 | "prettier": "^2.2.1", 42 | "sass": "^1.32.6", 43 | "sass-loader": "^11.0.0", 44 | "style-loader": "^2.0.0", 45 | "webpack": "^5.21.2", 46 | "webpack-cli": "^3.3.12", 47 | "webpack-dev-server": "^3.11.0" 48 | }, 49 | "dependencies": {} 50 | } 51 | -------------------------------------------------------------------------------- /sandbox/index.js: -------------------------------------------------------------------------------- 1 | import MCDatepicker from '../src/js/mc-calendar'; 2 | import './style.css'; 3 | 4 | window.MCDatepicker = MCDatepicker; 5 | 6 | const datepickerBTN = document.querySelector('#datepicker_btn'); 7 | const inputFourDatepicker = document.querySelector('#datepicker_four'); 8 | const setDateInput = document.querySelector('#set_date_input_two'); 9 | const setDayInput = document.querySelector('#set_day_input_two'); 10 | const setMonthInput = document.querySelector('#set_month_input_two'); 11 | const setYearInput = document.querySelector('#set_year_input_two'); 12 | const setDateBtn = document.querySelector('#set_date_btn'); 13 | const setDayBtn = document.querySelector('#set_day_btn'); 14 | const setMonthBtn = document.querySelector('#set_month_btn'); 15 | const setYearBtn = document.querySelector('#set_year_btn'); 16 | 17 | let setDateValue = null; 18 | let setDayValue = null; 19 | let setMonthValue = null; 20 | let setYearValue = null; 21 | 22 | const calendarTheme = { 23 | theme_color: '#19212b', //#19212b 24 | main_background: '#1f2936', 25 | active_text_color: '#cfe6ff', 26 | inactive_text_color: 'rgba(62,85,110,0.85)', 27 | picker_header: { 28 | active: '#8ea5bd' 29 | }, 30 | weekday: { 31 | foreground: '#53A6FA' //rgba(35,68,97,0.8) 32 | }, 33 | display: { 34 | foreground: '#8ea5bd' 35 | }, 36 | date: { 37 | active: { 38 | picked: { 39 | background: '#53a6fa' 40 | } 41 | }, 42 | marcked: { 43 | foreground: '#53a6fa' 44 | } 45 | }, 46 | button: { 47 | danger: { 48 | foreground: '#af002a' 49 | }, 50 | success: { 51 | foreground: '#53a6e8' 52 | } 53 | } 54 | }; 55 | 56 | const firstDatePicker = MCDatepicker.create({ 57 | el: '#datepicker_one', 58 | bodyType: 'inline', 59 | autoClose: true, 60 | closeOnBlur: true, 61 | dateFormat: 'dddd, dd mmmm yyyy', 62 | disableDates: [new Date(2021, 1, 12), new Date(2021, 4, 15)], 63 | customWeekDays: ['Duminică', 'Luni', 'Marți', 'Miercuri', 'Joi', 'Vineri', 'Sâmbătă'], 64 | customMonths: [ 65 | 'Ianuarie', 66 | 'Februarie', 67 | 'Martie', 68 | 'Aprilie', 69 | 'Mai', 70 | 'Iunie', 71 | 'Iulie', 72 | 'August', 73 | 'Septembrie', 74 | 'Octombrie', 75 | 'Noiembrie', 76 | 'Decembrie' 77 | ], 78 | customOkBTN: 'ok', 79 | customClearBTN: 'Șterge', 80 | customCancelBTN: 'Anulează', 81 | selectedDate: new Date(112, 1, 18), 82 | firstWeekday: 1 83 | // minDate: new Date(2019, 2, 22), 84 | // maxDate: new Date(2023, 3, 22), 85 | // markDates: [new Date(2021, 2, 21), new Date(2021, 3, 1)], 86 | // disableYears: [2020], 87 | // disableMonths: [8] 88 | // theme: calendarTheme 89 | }); 90 | 91 | firstDatePicker.markDatesCustom((date) => date.getDate() == 5); 92 | 93 | firstDatePicker.onSelect((date) => console.log('OK button clicked!')); 94 | firstDatePicker.onSelect((date) => console.log(date)); 95 | firstDatePicker.onCancel(() => console.log('Cancel button clicked!')); 96 | 97 | const secundDatePicker = MCDatepicker.create({ 98 | el: '#datepicker_two', 99 | // dateFormat: 'yyyy', 100 | // dateFormat: 'mm-yyyy', 101 | bodyType: 'modal', 102 | selectInactiveDays: true, 103 | // theme: calendarTheme, 104 | // closeOndblclick: false, 105 | // showCalendarDisplay: false, 106 | minDate: new Date(2020, 11, 5), 107 | maxDate: new Date(2021, 0, 16), 108 | theme: calendarTheme 109 | }); 110 | // console.log(JSON.stringify(secundDatePicker.options.theme, 0, 4)); 111 | const thirdDatePicker = MCDatepicker.create({ 112 | el: '#datepicker_three', 113 | dateFormat: 'mm-yyyy', 114 | autoClose: true, 115 | // dateFormat: 'dd-mm-yyyy', 116 | customWeekDays: ['Søndag', 'Måneder', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag'], 117 | customMonths: [ 118 | 'Januar', 119 | 'Februar', 120 | 'Marts', 121 | 'April', 122 | 'Maj', 123 | 'Juni', 124 | 'Juli', 125 | 'August', 126 | 'September', 127 | 'Oktober', 128 | 'November', 129 | 'December' 130 | ], 131 | minDate: new Date('2017-09-25'), 132 | maxDate: new Date('2020-03-22'), 133 | jumpToMinMax: false, 134 | // jumpOverDisabled: false, 135 | selectedDate: new Date('2020-03-22'), 136 | disableMonths: [5, 7] 137 | }); 138 | 139 | const forthDatePicker = MCDatepicker.create({ 140 | dateFormat: 'yyyy', 141 | selectedDate: new Date('2020-04-04'), 142 | closeOndblclick: false, 143 | closeOnBlur: true, 144 | allowedMonths: [5, 7, 9], 145 | allowedYears: [2016, 2018, 2020, 2022, 2024, 2026] 146 | }); 147 | 148 | forthDatePicker.onSelect((date, formatedDate) => { 149 | inputFourDatepicker.value = formatedDate; 150 | }); 151 | 152 | datepickerBTN.onclick = () => { 153 | forthDatePicker.open(); 154 | }; 155 | 156 | forthDatePicker.onCancel(() => alert('The datepicker was closed using CANCEL button')); 157 | 158 | // --------------------- 159 | 160 | setDateInput.onchange = (e) => { 161 | // converts string into code ex: new Date(2021, 0, 1) 162 | setDateValue = eval(e.target.value); 163 | }; 164 | 165 | setDateBtn.onclick = () => { 166 | setDateValue !== null && secundDatePicker.setFullDate(setDateValue); 167 | }; 168 | 169 | // --------------------- 170 | 171 | setDayInput.onchange = (e) => { 172 | setDayValue = Number(e.target.value); 173 | }; 174 | 175 | setDayBtn.onclick = () => { 176 | setDayValue !== null && secundDatePicker.setDate(setDayValue); 177 | }; 178 | 179 | // --------------------- 180 | 181 | setMonthInput.onchange = (e) => { 182 | setMonthValue = Number(e.target.value); 183 | }; 184 | 185 | setMonthBtn.onclick = () => { 186 | console.log(setMonthValue); 187 | setMonthValue !== null && secundDatePicker.setMonth(setMonthValue); 188 | }; 189 | 190 | // --------------------- 191 | 192 | setYearInput.onchange = (e) => { 193 | setYearValue = Number(e.target.value); 194 | }; 195 | 196 | setYearBtn.onclick = () => { 197 | setYearValue !== null && secundDatePicker.setYear(setYearValue); 198 | }; 199 | 200 | // Test Shadow Dom usage 201 | document.addEventListener('DOMContentLoaded', setupShadowDOM); 202 | 203 | function setupShadowDOM() { 204 | const el = document.querySelector('#shadow-dom'); 205 | if (document.head.attachShadow) { 206 | el.attachShadow({ mode: 'open' }); 207 | const shadowRoot = el.shadowRoot; 208 | 209 | // Add input to shadow dom 210 | const el2 = document.createElement('input'); 211 | el2.id = 'shadow-picker-el'; 212 | shadowRoot.append(el2); 213 | 214 | // Add styles to shadow dom 215 | const styleEl = document.querySelector('head link').cloneNode(); 216 | shadowRoot.append(styleEl); 217 | 218 | setupShadowPicker(shadowRoot); 219 | } 220 | } 221 | 222 | function setupShadowPicker(shadowRoot) { 223 | window.shadowDatePicker = MCDatepicker.create({ 224 | el: '#shadow-picker-el', 225 | context: shadowRoot, 226 | bodyType: 'inline' 227 | }); 228 | } 229 | -------------------------------------------------------------------------------- /sandbox/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | min-height: 100vh; 9 | min-width: 100%; 10 | } 11 | 12 | .card { 13 | margin: 2rem; 14 | } 15 | 16 | .row { 17 | display: flex; 18 | } 19 | /* #box { 20 | position: absolute; 21 | z-index: 2; 22 | } */ 23 | .container { 24 | flex-wrap: wrap; 25 | /* flex-direction: column; */ 26 | /* align-items: flex-end; */ 27 | width: min(80%, 1000px); 28 | /* width: 100%; */ 29 | margin: 0 auto; 30 | justify-content: center; 31 | } 32 | .col { 33 | width: 50%; 34 | } 35 | .main__header { 36 | height: 10vh; 37 | background-color: #38ada9; 38 | color: #fff; 39 | text-align: center; 40 | } 41 | 42 | .main__header h1 { 43 | padding: 0.8em; 44 | } 45 | 46 | .main__body { 47 | min-height: 90vh; 48 | height: 200vh; 49 | background-color: #e4eefa; 50 | } 51 | 52 | .form-field__label, 53 | .form-field__input { 54 | display: block; 55 | } 56 | 57 | .form-field__input { 58 | padding: 0.5em 0.7em; 59 | border: 1px solid #919fae; 60 | border-radius: 5px; 61 | outline: none; 62 | } 63 | 64 | .input__wrapper { 65 | display: flex; 66 | } 67 | 68 | /* #datepicker_btn { 69 | padding: 0 0.6em; 70 | border: 1px solid #919fae; 71 | border-radius: 0; 72 | border-top-right-radius: 5px; 73 | border-bottom-right-radius: 5px; 74 | outline: none; 75 | cursor: pointer; 76 | } */ 77 | 78 | .btn { 79 | border: 1px solid #919fae; 80 | border-radius: 0; 81 | border-top-right-radius: 5px; 82 | border-bottom-right-radius: 5px; 83 | outline: none; 84 | cursor: pointer; 85 | } 86 | 87 | .btn--large { 88 | padding: 0.6em 1em; 89 | } 90 | 91 | .btn--small { 92 | padding: 0 0.6em; 93 | } 94 | 95 | .calendarIcon { 96 | height: 1rem; 97 | } 98 | 99 | .form-field__input--coupled { 100 | border-top-right-radius: 0; 101 | border-bottom-right-radius: 0; 102 | border-right: none; 103 | } 104 | -------------------------------------------------------------------------------- /sandbox/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Calendar 8 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |

MCDatepicker

17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 |
36 | 38 | 46 |
47 |
48 |
49 | 50 |
51 | 52 | 53 |
54 |
55 |
56 | 57 |
58 | 59 | 60 |
61 |
62 |
63 | 64 |
65 | 66 | 67 |
68 |
69 |
70 | 71 |
72 | 73 | 74 |
75 |
76 |
77 |
78 |
79 | 80 | 81 | -------------------------------------------------------------------------------- /src/js/checker.js: -------------------------------------------------------------------------------- 1 | import { valueOfDate } from './utils'; 2 | 3 | export const isLessThanMinDate = (targetDate, prevMinDate) => { 4 | if (targetDate && prevMinDate) return valueOfDate(targetDate) < valueOfDate(prevMinDate); 5 | return false; 6 | }; 7 | 8 | export const isMoreThanMaxDate = (targetDate, nextMaxDate) => { 9 | if (targetDate && nextMaxDate) return valueOfDate(targetDate) > valueOfDate(nextMaxDate); 10 | return false; 11 | }; 12 | 13 | export const isActiveMonth = (options, monthTarget) => { 14 | const { allowedMonths, disableMonths } = options; 15 | return allowedMonths.length 16 | ? allowedMonths.includes(monthTarget) 17 | : !disableMonths.includes(monthTarget); 18 | }; 19 | 20 | export const isActiveYear = (options, YearTarget) => { 21 | const { disableYears, allowedYears } = options; 22 | return allowedYears.length 23 | ? allowedYears.includes(YearTarget) 24 | : !disableYears.includes(YearTarget); 25 | }; 26 | 27 | export const isSelectable = (instance, dayObject) => { 28 | const { prevLimitDate, nextLimitDate } = instance; 29 | const { date } = dayObject; 30 | const smallerTanMin = prevLimitDate ? valueOfDate(date) < valueOfDate(prevLimitDate) : false; 31 | const biggerTanMax = nextLimitDate ? valueOfDate(nextLimitDate) < valueOfDate(date) : false; 32 | return smallerTanMin || biggerTanMax; 33 | }; 34 | 35 | export const isInActiveMonth = (activeMonth, dayObject) => { 36 | const { month } = dayObject; 37 | return month !== activeMonth ? false : true; 38 | }; 39 | 40 | export const isExcludedWeekend = (options, dayObject) => { 41 | const { disableWeekends } = options; 42 | const { day } = dayObject; 43 | if (!disableWeekends) return false; 44 | return day === 0 || day === 6 ? true : false; 45 | }; 46 | 47 | export const isDisabledWeekDay = (options, dayObject) => { 48 | const { disableWeekDays } = options; 49 | const { day } = dayObject; 50 | if (!disableWeekDays.length) return false; 51 | return disableWeekDays.some((weekDay) => weekDay === day); 52 | }; 53 | 54 | export const isDisabledDate = (options, dayObject) => { 55 | const { disableDates } = options; 56 | const { date } = dayObject; 57 | if (!disableDates.length) return false; 58 | return disableDates.some((disabledDate) => valueOfDate(disabledDate) === valueOfDate(date)); 59 | }; 60 | 61 | export const isPicked = (pickedDate, dayObject) => { 62 | const { date } = dayObject; 63 | 64 | if (pickedDate === null) return false; 65 | 66 | return valueOfDate(pickedDate) === valueOfDate(date) ? true : false; 67 | }; 68 | 69 | export const isMarked = (instance, dayObject) => { 70 | const { options, markCustomCallbacks } = instance; 71 | const { date } = dayObject; 72 | const optionMark = options.markDates.some( 73 | (markedDate) => valueOfDate(markedDate) === valueOfDate(date) 74 | ); 75 | const customMark = markCustomCallbacks.some((callback) => callback.apply(null, [date])); 76 | 77 | return optionMark || customMark; 78 | }; 79 | 80 | export const isToday = (dayObject) => { 81 | const { date } = dayObject; 82 | return valueOfDate(date) === valueOfDate(new Date()) ? true : false; 83 | }; 84 | -------------------------------------------------------------------------------- /src/js/defaults.js: -------------------------------------------------------------------------------- 1 | export const defaultEventColorType = [ 2 | { type: 'high', color: '#f03048' }, 3 | { type: 'medium', color: '#008E84' }, 4 | { type: 'low', color: '#f0d818' }, 5 | { type: 'slight', color: '#00DDFF' } 6 | ]; 7 | 8 | export const defaultTheme = { 9 | theme_color: '#38ada9', 10 | main_background: '#f5f5f6', 11 | active_text_color: 'rgb(0, 0, 0)', 12 | inactive_text_color: 'rgba(0, 0, 0, 0.2)', 13 | display: { 14 | foreground: 'rgba(255, 255, 255, 0.8)', 15 | background: '#38ada9' 16 | }, 17 | picker: { 18 | foreground: 'rgb(0, 0, 0)', 19 | background: '#f5f5f6' 20 | }, 21 | picker_header: { 22 | active: '#818181', 23 | inactive: 'rgba(0, 0, 0, 0.2)' 24 | }, 25 | weekday: { 26 | foreground: '#38ada9' 27 | }, 28 | button: { 29 | success: { 30 | foreground: '#38ada9' 31 | }, 32 | danger: { 33 | foreground: '#e65151' 34 | } 35 | }, 36 | date: { 37 | active: { 38 | default: { 39 | foreground: 'rgb(0, 0, 0)' 40 | }, 41 | picked: { 42 | foreground: '#ffffff', 43 | background: '#38ada9' 44 | }, 45 | today: { 46 | foreground: 'rgb(0, 0, 0)', 47 | background: 'rgba(0, 0, 0, 0.2)' 48 | } 49 | }, 50 | inactive: { 51 | default: { 52 | foreground: 'rgba(0, 0, 0, 0.2)' 53 | }, 54 | picked: { 55 | foreground: '#38ada9', 56 | background: '#38ada9' 57 | }, 58 | today: { 59 | foreground: 'rgba(0, 0, 0, 0.2)', 60 | background: 'rgba(0, 0, 0, 0.2)' 61 | } 62 | }, 63 | marcked: { 64 | foreground: '#38ada9' 65 | } 66 | }, 67 | month_year_preview: { 68 | active: { 69 | default: { 70 | foreground: 'rgb(0, 0, 0)' 71 | }, 72 | picked: { 73 | foreground: 'rgb(0, 0, 0)', 74 | background: 'rgba(0, 0, 0,0.2)' 75 | } 76 | }, 77 | inactive: { 78 | default: { 79 | foreground: 'rgba(0, 0, 0, 0.2)' 80 | }, 81 | picked: { 82 | foreground: 'rgba(0, 0, 0, 0.2)', 83 | background: 'rgba(0, 0, 0, 0.2)' 84 | } 85 | } 86 | } 87 | }; 88 | 89 | export const viewLayers = { 90 | DMY: ['calendar', 'month', 'year'], 91 | DY: ['calendar', 'month', 'year'], 92 | D: ['calendar', 'month', 'year'], 93 | MY: ['month', 'year'], 94 | M: ['month'], 95 | Y: ['year'] 96 | }; 97 | 98 | export const weekDays = [ 99 | 'Sunday', 100 | 'Monday', 101 | 'Tuesday', 102 | 'Wednesday', 103 | 'Thursday', 104 | 'Friday', 105 | 'Saturday' 106 | ]; 107 | 108 | export const months = [ 109 | 'January', 110 | 'February', 111 | 'March', 112 | 'April', 113 | 'May', 114 | 'June', 115 | 'July', 116 | 'August', 117 | 'September', 118 | 'October', 119 | 'November', 120 | 'December' 121 | ]; 122 | 123 | const defaultOptions = { 124 | el: null, 125 | context: null, 126 | dateFormat: 'DD-MMM-YYYY', 127 | bodyType: 'modal', // ['modal', 'inline', 'permanent'] 128 | autoClose: false, 129 | closeOndblclick: true, 130 | closeOnBlur: false, 131 | hideInactiveDays: false, 132 | selectInactiveDays: false, 133 | showCalendarDisplay: true, 134 | customWeekDays: weekDays, 135 | customMonths: months, 136 | customOkBTN: 'OK', 137 | customClearBTN: 'Clear', 138 | customCancelBTN: 'CANCEL', 139 | firstWeekday: 0, // ex: 1 accept numbers 0-6; 140 | selectedDate: null, 141 | minDate: null, 142 | maxDate: null, 143 | jumpToMinMax: true, 144 | jumpOverDisabled: true, 145 | disableWeekends: false, 146 | disableWeekDays: [], // ex: [0,2,5] accept numbers 0-6; 147 | disableDates: [], // ex: [new Date(2019,11, 25), new Date(2019, 11, 26)] 148 | allowedMonths: [], // ex: [0,1] accept numbers 0-11; 149 | allowedYears: [], // ex: [2022, 2023] 150 | disableMonths: [], /// ex: [3,11] accept numbers 0-11; 151 | disableYears: [], // ex: [2010, 2011] 152 | markDates: [], // ex: [new Date(2019,11, 25), new Date(2019, 11, 26)] 153 | theme: defaultTheme 154 | // TODO: Integrate Daterange Feature 155 | // daterange: false, // currently not supported 156 | // TODO: Integrate Events Feature 157 | // events: [], // currently not supported 158 | // eventColorScheme: defaultEventColorType // currently not supported 159 | }; 160 | 161 | export default defaultOptions; 162 | -------------------------------------------------------------------------------- /src/js/emiters.js: -------------------------------------------------------------------------------- 1 | import { 2 | CALENDAR_HIDE, 3 | CALENDAR_SHOW, 4 | CALENDAR_UPDATE, 5 | DISPLAY_UPDATE, 6 | PREVIEW_UPDATE, 7 | HEADER_UPDATE, 8 | CHANGE_MONTH, 9 | CHANGE_YEAR, 10 | DATE_PICK, 11 | PREVIEW_PICK, 12 | SET_DATE, 13 | CANCEL 14 | } from './events'; 15 | 16 | export const dispatchCalendarShow = (elem, instance) => { 17 | elem.dispatchEvent( 18 | new CustomEvent(CALENDAR_SHOW, { 19 | bubbles: true, 20 | detail: { 21 | instance 22 | } 23 | }) 24 | ); 25 | }; 26 | 27 | export const dispatchCalendarHide = (elem) => { 28 | elem.dispatchEvent(new CustomEvent(CALENDAR_HIDE, { bubbles: true })); 29 | }; 30 | 31 | export const dispatchDisplayUpdate = (elem) => { 32 | elem.dispatchEvent(new CustomEvent(DISPLAY_UPDATE, { bubbles: true })); 33 | }; 34 | 35 | export const dispatchCalendarUpdate = (elem) => { 36 | elem.dispatchEvent(new CustomEvent(CALENDAR_UPDATE, { bubbles: true })); 37 | }; 38 | 39 | export const dispatchPreviewUpdate = (elem) => { 40 | elem.dispatchEvent(new CustomEvent(PREVIEW_UPDATE, { bubbles: true })); 41 | }; 42 | 43 | export const dispatchHeaderUpdate = (elem) => { 44 | elem.dispatchEvent(new CustomEvent(HEADER_UPDATE, { bubbles: true })); 45 | }; 46 | 47 | export const dispatchDatePick = (elem, dblclick = false) => { 48 | elem.dispatchEvent( 49 | new CustomEvent(DATE_PICK, { 50 | bubbles: true, 51 | detail: { 52 | dblclick, 53 | date: new Date(Number(elem.getAttribute('data-val-date'))) 54 | } 55 | }) 56 | ); 57 | }; 58 | 59 | export const dispatchChangeMonth = (elem, direction) => { 60 | elem.dispatchEvent( 61 | new CustomEvent(CHANGE_MONTH, { 62 | bubbles: true, 63 | detail: { direction } 64 | }) 65 | ); 66 | }; 67 | 68 | export const dispatchChangeYear = (elem, direction) => { 69 | elem.dispatchEvent( 70 | new CustomEvent(CHANGE_YEAR, { 71 | bubbles: true, 72 | detail: { direction } 73 | }) 74 | ); 75 | }; 76 | 77 | export const dispatchPreviewCellPick = (elem, dblclick = false) => { 78 | elem.dispatchEvent( 79 | new CustomEvent(PREVIEW_PICK, { 80 | bubbles: true, 81 | detail: { 82 | dblclick, 83 | data: elem.children[0].innerHTML 84 | } 85 | }) 86 | ); 87 | }; 88 | 89 | export const dispatchSetDate = (elem, detail = { instance: null, date: null }) => { 90 | elem.dispatchEvent( 91 | new CustomEvent(SET_DATE, { 92 | bubbles: true, 93 | detail 94 | }) 95 | ); 96 | }; 97 | 98 | export const dispatchCancel = (elem) => { 99 | elem.dispatchEvent(new CustomEvent(CANCEL, { bubbles: true })); 100 | }; 101 | -------------------------------------------------------------------------------- /src/js/events.js: -------------------------------------------------------------------------------- 1 | export const CALENDAR_SHOW = 'show-calendar'; 2 | export const CALENDAR_HIDE = 'hide-calendar'; 3 | export const CALENDAR_UPDATE = 'update-calendar'; 4 | export const DISPLAY_UPDATE = 'update-display'; 5 | export const HEADER_UPDATE = 'update-header'; 6 | export const PREVIEW_UPDATE = 'update-preview'; 7 | export const DATE_PICK = 'date-pick'; 8 | export const PREVIEW_PICK = 'preview-pick'; 9 | export const CHANGE_MONTH = 'month-change'; 10 | export const CHANGE_YEAR = 'year-change'; 11 | export const SET_DATE = 'set-date'; 12 | export const CANCEL = 'cancel-calendar'; 13 | -------------------------------------------------------------------------------- /src/js/handlers.js: -------------------------------------------------------------------------------- 1 | import { viewLayers, defaultTheme } from './defaults'; 2 | import { renderCalendar, renderMonthPreview, renderYearPreview } from './render'; 3 | import { isActiveMonth, isActiveYear, isLessThanMinDate, isMoreThanMaxDate } from './checker'; 4 | import { 5 | calculateCalendarPosition, 6 | CalendarStateManager, 7 | HandleArrowClass, 8 | dateFormatParser, 9 | valueOfDate, 10 | getNewIndex, 11 | createNewDate 12 | } from './utils'; 13 | 14 | export const getDOMNodes = (calendar) => { 15 | const nodes = { 16 | calendar, 17 | calendarDisplay: calendar.querySelector('.mc-display'), 18 | calendarPicker: calendar.querySelector('.mc-picker'), 19 | displayDay: calendar.querySelector('.mc-display__day'), 20 | displayDate: calendar.querySelector('.mc-display__date'), 21 | displayMonth: calendar.querySelector('.mc-display__month'), 22 | displayYear: calendar.querySelector('.mc-display__year'), 23 | accessibilityMonthYear: calendar.querySelector('#mc-picker__month-year'), 24 | calendarHeader: calendar.querySelector('.mc-picker__header'), 25 | currentMonthSelect: calendar.querySelector('#mc-current--month'), 26 | currentYearSelect: calendar.querySelector('#mc-current--year'), 27 | monthNavPrev: calendar.querySelector('#mc-picker__month--prev'), 28 | monthNavNext: calendar.querySelector('#mc-picker__month--next'), 29 | yearNavPrev: calendar.querySelector('#mc-picker__year--prev'), 30 | yearNavNext: calendar.querySelector('#mc-picker__year--next'), 31 | weekdays: calendar.querySelectorAll('.mc-table__weekday'), 32 | okButton: calendar.querySelector('#mc-btn__ok'), 33 | cancelButton: calendar.querySelector('#mc-btn__cancel'), 34 | clearButton: calendar.querySelector('#mc-btn__clear'), 35 | dateCells: calendar.querySelectorAll('.mc-date'), 36 | monthYearPreview: calendar.querySelector('.mc-month-year__preview'), 37 | previewCells: calendar.querySelectorAll('.mc-month-year__cell'), 38 | calendarStates: CalendarStateManager(calendar) 39 | }; 40 | return nodes; 41 | }; 42 | 43 | export const getViewLayers = (options) => { 44 | const { dateFormat } = options; 45 | const splitTest = dateFormat.split(/(?:(?:,\s)|[.-\s\/]{1})/); 46 | const firstChars = splitTest.map((group) => group.charAt(0).toUpperCase()); 47 | const result = [...new Set(firstChars)].sort().join(''); 48 | return viewLayers[result]; 49 | }; 50 | 51 | export const getActiveDate = (pickedDate, minDate, maxDate) => { 52 | let targetDate = pickedDate === null ? new Date() : pickedDate; 53 | targetDate = minDate !== null && lessThanMinDate ? minDate : targetDate; 54 | targetDate = 55 | maxDate !== null && valueOfDate(targetDate) > valueOfDate(maxDate) ? maxDate : targetDate; 56 | return targetDate; 57 | }; 58 | 59 | export const getTargetDate = (instance, newDate = null) => { 60 | const { options, pickedDate, prevLimitDate, nextLimitDate, activeMonths } = instance; 61 | let targetDate = pickedDate ? pickedDate : new Date(); 62 | const targetMonth = targetDate.getMonth(); 63 | if (!isActiveMonth(options, targetMonth)) { 64 | const closestMonth = activeMonths.reduce((result, month) => { 65 | return Math.abs(month.index - targetMonth) < Math.abs(result.index - targetMonth) 66 | ? month 67 | : result; 68 | }); 69 | targetDate.setMonth(closestMonth.index); 70 | } 71 | 72 | if (newDate) targetDate = newDate; 73 | if (prevLimitDate && isLessThanMinDate(targetDate, prevLimitDate)) targetDate = prevLimitDate; 74 | if (nextLimitDate && isMoreThanMaxDate(targetDate, nextLimitDate)) targetDate = nextLimitDate; 75 | return targetDate; 76 | }; 77 | 78 | export const getNewMonth = (instance, currentMonth, direction) => { 79 | const { activeMonths, options } = instance; 80 | const { customMonths, jumpOverDisabled } = options; 81 | if (!jumpOverDisabled) { 82 | const { newIndex, overlap } = getNewIndex( 83 | customMonths, 84 | customMonths.indexOf(currentMonth), 85 | direction 86 | ); 87 | const newMonth = { 88 | name: customMonths[newIndex], 89 | index: newIndex 90 | }; 91 | return { newMonth, overlap }; 92 | } 93 | const currentMonthIndex = activeMonths.findIndex(({ name }) => name === currentMonth); 94 | const { newIndex, overlap } = getNewIndex(activeMonths, currentMonthIndex, direction); 95 | const newMonth = activeMonths[newIndex]; 96 | return { newMonth, overlap }; 97 | }; 98 | 99 | export const getNewYear = (options, currentYear, direction) => { 100 | const { allowedYears, jumpOverDisabled } = options; 101 | let newYear = direction === 'next' ? currentYear + 1 : currentYear - 1; 102 | 103 | if (!jumpOverDisabled) return newYear; 104 | 105 | if (allowedYears.length) { 106 | const { newIndex, overlap } = getNewIndex( 107 | allowedYears, 108 | allowedYears.indexOf(currentYear), 109 | direction 110 | ); 111 | newYear = overlap !== 0 ? null : allowedYears[newIndex]; 112 | return newYear; 113 | } 114 | 115 | while (!isActiveYear(options, newYear)) { 116 | direction === 'next' ? newYear++ : newYear--; 117 | } 118 | return newYear; 119 | }; 120 | export const getActiveMonths = (options) => { 121 | const { customMonths } = options; 122 | return customMonths 123 | .map((month, index) => { 124 | if (isActiveMonth(options, index)) return { name: month, index }; 125 | return null; 126 | }) 127 | .filter((item) => item); 128 | }; 129 | 130 | export const getLimitDates = (options) => { 131 | const { minDate, maxDate, allowedYears } = options; 132 | let prevLimitDate = null; 133 | let nextLimitDate = null; 134 | const activeMonths = getActiveMonths(options); 135 | const minMonth = activeMonths[0]; 136 | const maxMonth = activeMonths[activeMonths.length - 1]; 137 | const minYear = allowedYears.length ? Math.min(...allowedYears) : null; 138 | const maxYear = allowedYears.length ? Math.max(...allowedYears) : null; 139 | const minAllowedDate = minYear ? createNewDate(minYear, minMonth.index, 1) : null; 140 | const maxAllowedDate = maxYear ? createNewDate(maxYear, maxMonth.index + 1, 0) : null; 141 | if (minDate && minAllowedDate) prevLimitDate = new Date(Math.max(minDate, minAllowedDate)); 142 | if (maxDate && maxAllowedDate) nextLimitDate = new Date(Math.min(maxDate, maxAllowedDate)); 143 | if (!prevLimitDate) prevLimitDate = minDate ? minDate : minAllowedDate; 144 | if (!nextLimitDate) nextLimitDate = maxDate ? maxDate : maxAllowedDate; 145 | 146 | return { prevLimitDate, nextLimitDate }; 147 | }; 148 | 149 | export const updatePickedDateValue = (activeInstance, calendarStates) => { 150 | if (!activeInstance) return; 151 | const { pickedDate, linkedElement, onSelectCallbacks, options } = activeInstance; 152 | const { dateFormat } = options; 153 | let pickedDateValue = pickedDate ? dateFormatParser(pickedDate, options, dateFormat) : null; 154 | if (linkedElement) linkedElement.value = pickedDateValue; 155 | onSelectCallbacks.forEach((callback) => callback.apply(null, [pickedDate, pickedDateValue])); 156 | calendarStates.close(); 157 | }; 158 | 159 | export const updateLinkedInputValue = (instance) => { 160 | const { pickedDate, linkedElement, options } = instance; 161 | const { dateFormat } = options; 162 | if (linkedElement && pickedDate) { 163 | linkedElement.value = dateFormatParser(pickedDate, options, dateFormat); 164 | } 165 | }; 166 | 167 | export const updateCalendarPosition = (calendarNodes, instance) => { 168 | const { calendar } = calendarNodes; 169 | const { options, linkedElement } = instance; 170 | const { bodyType } = options; 171 | if (bodyType === 'inline') { 172 | const { top, left } = calculateCalendarPosition(calendar, linkedElement); 173 | calendar.style.top = `${top}px`; 174 | calendar.style.left = `${left}px`; 175 | } else { 176 | calendar.style.removeProperty('top'); 177 | calendar.style.removeProperty('left'); 178 | } 179 | }; 180 | 181 | export const updateNavs = (calendarNodes, instance) => { 182 | const { monthNavPrev, monthNavNext, yearNavPrev, yearNavNext } = calendarNodes; 183 | const { store, prevLimitDate, nextLimitDate, options } = instance; 184 | const { customMonths, jumpToMinMax } = options; 185 | const viewTarget = store.header.target; 186 | const currentMonth = store.header.month; 187 | const currentYear = store.header.year; 188 | const monthNavPrevState = HandleArrowClass(monthNavPrev); 189 | const monthNavNextState = HandleArrowClass(monthNavNext); 190 | const yearNavPrevState = HandleArrowClass(yearNavPrev); 191 | const yearNavNextState = HandleArrowClass(yearNavNext); 192 | 193 | yearNavPrevState.active(); 194 | yearNavNextState.active(); 195 | monthNavPrevState.active(); 196 | monthNavNextState.active(); 197 | 198 | if (viewTarget === 'year') { 199 | monthNavPrevState.inactive(); 200 | monthNavNextState.inactive(); 201 | prevLimitDate && prevLimitDate.getFullYear() > currentYear - 1 && yearNavPrevState.inactive(); 202 | nextLimitDate && nextLimitDate.getFullYear() < currentYear + 12 && yearNavNextState.inactive(); 203 | return; 204 | } 205 | 206 | const prevMonth = getNewMonth(instance, customMonths[currentMonth], 'prev'); 207 | const nextMonth = getNewMonth(instance, customMonths[currentMonth], 'next'); 208 | const prevYear = viewTarget !== 'year' && getNewYear(options, currentYear, 'prev'); 209 | const nextYear = viewTarget !== 'year' && getNewYear(options, currentYear, 'next'); 210 | 211 | viewTarget === 'calendar' && prevMonth.overlap !== 0 && !prevYear && monthNavPrevState.inactive(); 212 | viewTarget === 'calendar' && prevMonth.overlap !== 0 && !prevYear && yearNavPrevState.inactive(); 213 | viewTarget === 'calendar' && nextMonth.overlap !== 0 && !nextYear && monthNavNextState.inactive(); 214 | viewTarget === 'calendar' && nextMonth.overlap !== 0 && !nextYear && yearNavNextState.inactive(); 215 | 216 | if (prevLimitDate) { 217 | const currentMonthFirstDay = createNewDate(currentYear, currentMonth, 1); 218 | const prevYearLastDay = createNewDate(prevYear, currentMonth + 1, 0); 219 | const inactivePrevMonth = isLessThanMinDate(currentMonthFirstDay, prevLimitDate); 220 | const inactivePrevYear = isLessThanMinDate(prevYearLastDay, prevLimitDate); 221 | if (jumpToMinMax && inactivePrevMonth) yearNavPrevState.inactive(); 222 | if (!jumpToMinMax && (inactivePrevYear || !nextYear)) yearNavPrevState.inactive(); 223 | if (inactivePrevMonth) monthNavPrevState.inactive(); 224 | } 225 | if (nextLimitDate) { 226 | const currentMonthLastDay = createNewDate(currentYear, currentMonth + 1, 0); 227 | const nextYearFirstDay = createNewDate(nextYear, currentMonth, 1); 228 | const inactiveNextMonth = isMoreThanMaxDate(currentMonthLastDay, nextLimitDate); 229 | const inactiveNextYear = isMoreThanMaxDate(nextYearFirstDay, nextLimitDate); 230 | if (jumpToMinMax && inactiveNextMonth) yearNavNextState.inactive(); 231 | if (!jumpToMinMax && (inactiveNextYear || !nextYear)) yearNavNextState.inactive(); 232 | if (inactiveNextMonth) monthNavNextState.inactive(); 233 | } 234 | }; 235 | 236 | export const updateButtons = (calendarNodes, options) => { 237 | const { customOkBTN, customClearBTN, customCancelBTN } = options; 238 | const { okButton, clearButton, cancelButton } = calendarNodes; 239 | okButton.innerText = customOkBTN; 240 | clearButton.innerText = customClearBTN; 241 | cancelButton.innerText = customCancelBTN; 242 | }; 243 | 244 | export const updateWeekdays = (calendarNodes, options) => { 245 | const { weekdays } = calendarNodes; 246 | const { customWeekDays, firstWeekday } = options; 247 | weekdays.forEach((wDay, index) => { 248 | const nextElement = (firstWeekday + index) % customWeekDays.length; 249 | wDay.innerText = customWeekDays[nextElement].substr(0, 2); 250 | wDay.setAttribute('aria-label', customWeekDays[nextElement]); 251 | }); 252 | }; 253 | 254 | export const updateDisplay = (calendarNodes, instance) => { 255 | const { displayDay, displayDate, displayMonth, displayYear, calendarDisplay } = calendarNodes; 256 | const { store, options } = instance; 257 | const { target, date } = store.display; 258 | const { customWeekDays, customMonths, showCalendarDisplay } = options; 259 | 260 | if (!showCalendarDisplay) { 261 | calendarDisplay.classList.add('u-display-none'); 262 | } else { 263 | calendarDisplay.classList.remove('u-display-none'); 264 | } 265 | calendarDisplay.setAttribute('data-target', store.display.target); 266 | displayYear.innerText = date.getFullYear(); 267 | if (target === 'year') return; 268 | displayMonth.innerText = customMonths[date.getMonth()]; 269 | if (target === 'month') return; 270 | displayDay.innerText = customWeekDays[date.getDay()]; 271 | displayDate.innerText = date.getDate(); 272 | }; 273 | 274 | export const updateCalendarTable = (calendarNodes, instance) => { 275 | const { dateCells } = calendarNodes; 276 | const { store, viewLayers } = instance; 277 | const activeDate = store.calendar.date; 278 | 279 | if (viewLayers[0] !== 'calendar') return; 280 | // render the new calendar array 281 | const datesArray = renderCalendar(instance, activeDate); 282 | // update the DOM for each date cell 283 | dateCells.forEach((cell, index) => { 284 | cell.innerText = datesArray[index].dateNumb; 285 | cell.classList = datesArray[index].classList; 286 | cell.setAttribute('data-val-date', datesArray[index].date.valueOf()); 287 | cell.setAttribute('tabindex', datesArray[index].tabindex); 288 | cell.setAttribute('aria-label', datesArray[index].ariaLabel); 289 | }); 290 | }; 291 | 292 | export const updateCalendarHeader = (calendarNodes, instance) => { 293 | const { 294 | currentMonthSelect, 295 | currentYearSelect, 296 | calendarHeader, 297 | accessibilityMonthYear 298 | } = calendarNodes; 299 | const { store, options } = instance; 300 | const { customMonths } = options; 301 | const { target, month, year } = store.header; 302 | 303 | calendarHeader.setAttribute('data-target', target); 304 | 305 | updateNavs(calendarNodes, instance); 306 | 307 | if (target === 'year') { 308 | const firstYear = year; 309 | currentYearSelect.innerHTML = `${firstYear} - ${ 310 | firstYear + 11 311 | }`; 312 | return; 313 | } 314 | currentMonthSelect.innerHTML = `${customMonths[month]}`; 315 | currentYearSelect.innerHTML = `${year}`; 316 | accessibilityMonthYear.innerText = `${customMonths[month]} ${year}`; 317 | }; 318 | 319 | export const updateMonthYearPreview = (calendarNodes, instance) => { 320 | if (!instance) return; 321 | const { monthYearPreview } = calendarNodes; 322 | const { target } = instance.store.preview; 323 | const { year } = instance.store.header; 324 | if (target === 'calendar') 325 | return monthYearPreview.classList.remove('mc-month-year__preview--opened'); 326 | monthYearPreview.setAttribute('data-target', target); 327 | monthYearPreview.classList.add('mc-month-year__preview--opened'); 328 | if (target == 'month') renderMonthPreview(calendarNodes, instance); 329 | if (target == 'year') renderYearPreview(calendarNodes, instance, year); 330 | }; 331 | 332 | export const updateMonthSelect = (activeInstance, calendarNodes, arrivalMethod = 'click') => { 333 | const { store, viewLayers } = activeInstance; 334 | if (viewLayers[0] === 'month') return; 335 | const { monthYearPreview } = calendarNodes; 336 | const isOpened = monthYearPreview.classList.contains('mc-month-year__preview--opened'); 337 | const isMonthTarget = store.preview.target === 'month' ? true : false; 338 | if (isOpened && isMonthTarget) { 339 | store.preview.setTarget = viewLayers[0]; 340 | return; 341 | } 342 | store.header.setTarget = 'month'; 343 | store.preview.setTarget = 'month'; 344 | if (arrivalMethod == 'keyboard') monthYearPreview.querySelector('[tabindex="0"]').focus(); 345 | }; 346 | export const updateYearSelect = (activeInstance, calendarNodes, arrivalMethod = 'click') => { 347 | const { store, viewLayers } = activeInstance; 348 | if (viewLayers[0] === 'year') return; 349 | const { monthYearPreview } = calendarNodes; 350 | const isOpened = monthYearPreview.classList.contains('mc-month-year__preview--opened'); 351 | const currentTarget = store.preview.target; 352 | const isYearTarget = currentTarget === 'year' ? true : false; 353 | if (isOpened && isYearTarget) { 354 | store.header.year = store.preview.year; 355 | store.preview.setTarget = viewLayers[0]; 356 | store.header.setTarget = viewLayers[0]; 357 | return; 358 | } 359 | store.header.year = store.preview.year - 4; 360 | store.header.setTarget = 'year'; 361 | store.preview.setTarget = 'year'; 362 | if (arrivalMethod == 'keyboard') monthYearPreview.querySelector('[tabindex="0"]').focus(); 363 | }; 364 | 365 | const updateCalendarTheme = (calendar, theme) => { 366 | Object.values(theme).forEach((value) => calendar.style.setProperty(value.cssVar, value.color)); 367 | }; 368 | 369 | export const updateCalendarUI = (calendarNodes, instance) => { 370 | const { calendar } = calendarNodes; 371 | const { store, viewLayers, options, pickedDate } = instance; 372 | const { bodyType, theme } = options; 373 | const activeDate = getTargetDate(instance); 374 | const activeYear = activeDate.getFullYear(); 375 | const activeMonth = activeDate.getMonth(); 376 | calendar.classList = 'mc-calendar'; 377 | calendar.classList.add(`mc-calendar--${bodyType}`); 378 | store.display.target = viewLayers[0]; 379 | store.display.setDate = pickedDate || new Date(); 380 | store.calendar.setDate = activeDate; 381 | store.header.month = activeMonth; 382 | store.header.year = viewLayers[0] === 'year' ? activeYear - 4 : activeYear; 383 | store.preview.month = activeMonth; 384 | store.preview.year = activeYear; 385 | store.header.setTarget = viewLayers[0]; 386 | store.preview.setTarget = viewLayers[0]; 387 | updateCalendarTheme(calendar, theme); 388 | updateWeekdays(calendarNodes, options); 389 | updateButtons(calendarNodes, options); 390 | updateCalendarPosition(calendarNodes, instance); 391 | }; 392 | -------------------------------------------------------------------------------- /src/js/instance.js: -------------------------------------------------------------------------------- 1 | // import { validateRequired, eventSchema, eventColorTypeSchema } from './validators'; 2 | import { Is } from './validators'; 3 | import { dispatchSetDate } from './emiters'; 4 | import { defaultTheme } from './defaults'; 5 | import { dateFormatParser, Store, uniqueId, themeParser } from './utils'; 6 | import { getActiveMonths, getLimitDates, getViewLayers } from './handlers'; 7 | 8 | export default function createInstance(datepicker, calendarNodes, instanceOptions) { 9 | instanceOptions.allowedYears.sort((first, next) => first - next); 10 | const linkedElement = 11 | instanceOptions.el !== null ? instanceOptions.context.querySelector(instanceOptions.el) : null; 12 | const activeMonths = getActiveMonths(instanceOptions); 13 | const { prevLimitDate, nextLimitDate } = getLimitDates(instanceOptions); 14 | const viewLayers = getViewLayers(instanceOptions); 15 | const store = Store(calendarNodes, viewLayers[0]); 16 | const parsedTheme = themeParser(defaultTheme, instanceOptions.theme); 17 | instanceOptions.theme = parsedTheme; 18 | 19 | return { 20 | _id: uniqueId(), 21 | datepicker: datepicker, 22 | el: instanceOptions.el, 23 | context: instanceOptions.context, 24 | linkedElement: linkedElement, 25 | pickedDate: instanceOptions.selectedDate, 26 | viewLayers: viewLayers, 27 | activeMonths: activeMonths, 28 | prevLimitDate: prevLimitDate, 29 | nextLimitDate: nextLimitDate, 30 | options: instanceOptions, 31 | onOpenCallbacks: [], 32 | onCloseCallbacks: [], 33 | onSelectCallbacks: [], 34 | onCancelCallbacks: [], 35 | onClearCallbacks: [], 36 | onMonthChangeCallbacks: [], 37 | onYearChangeCallbacks: [], 38 | markCustomCallbacks: [], 39 | store: store, 40 | // Methods 41 | open() { 42 | datepicker.open(this._id); 43 | }, 44 | close() { 45 | datepicker.close(); 46 | }, 47 | reset() { 48 | this.pickedDate = null; 49 | if (this.linkedElement) this.linkedElement.value = null; 50 | }, 51 | destroy() { 52 | datepicker.remove(this._id); 53 | }, 54 | // Event callbacks 55 | onOpen(callback = () => {}) { 56 | this.onOpenCallbacks.push(callback); 57 | }, 58 | onClose(callback = () => {}) { 59 | this.onCloseCallbacks.push(callback); 60 | }, 61 | onSelect(callback) { 62 | this.onSelectCallbacks.push(callback); 63 | }, 64 | onCancel(callback) { 65 | this.onCancelCallbacks.push(callback); 66 | }, 67 | onClear(callback) { 68 | this.onClearCallbacks.push(callback); 69 | }, 70 | onMonthChange(callback) { 71 | this.onMonthChangeCallbacks.push(callback); 72 | }, 73 | onYearChange(callback) { 74 | this.onYearChangeCallbacks.push(callback); 75 | }, 76 | // Getters 77 | getDay() { 78 | return this.pickedDate ? this.pickedDate.getDay() : null; 79 | }, 80 | getDate() { 81 | return this.pickedDate ? this.pickedDate.getDate() : null; 82 | }, 83 | getMonth() { 84 | return this.pickedDate ? this.pickedDate.getMonth() : null; 85 | }, 86 | getYear() { 87 | return this.pickedDate ? this.pickedDate.getFullYear() : null; 88 | }, 89 | getFullDate() { 90 | return this.pickedDate; 91 | }, 92 | getFormatedDate() { 93 | return this.pickedDate 94 | ? dateFormatParser(this.pickedDate, this.options, this.options.dateFormat) 95 | : null; 96 | }, 97 | markDatesCustom(callback) { 98 | this.markCustomCallbacks.push(callback); 99 | }, 100 | // getEvents: () => { 101 | // return instance.options.events; 102 | // }, 103 | 104 | // Setters 105 | setFullDate(date) { 106 | if (!Is(date).date()) throw new TypeError('Parameter of setFullDate() is not of type date'); 107 | dispatchSetDate(calendarNodes.calendar, { instance: this, date }); 108 | }, 109 | setDate(date) { 110 | if (!Is(date).number()) 111 | throw new TypeError(`Parameter 'date' of setDate() is not of type number`); 112 | const newDate = this.pickedDate ? new Date(this.pickedDate) : new Date(); 113 | newDate.setDate(date); 114 | dispatchSetDate(calendarNodes.calendar, { instance: this, date: newDate }); 115 | }, 116 | setMonth(month) { 117 | if (!Is(month).number()) 118 | throw new TypeError(`Parameter 'month' of setMonth() is not of type number`); 119 | const newDate = this.pickedDate ? new Date(this.pickedDate) : new Date(); 120 | newDate.setMonth(month); 121 | dispatchSetDate(calendarNodes.calendar, { instance: this, date: newDate }); 122 | }, 123 | setYear(year) { 124 | if (!Is(year).number()) 125 | throw new TypeError(`Parameter 'year' of setYear() is not of type number`); 126 | const newDate = this.pickedDate ? new Date(this.pickedDate) : new Date(); 127 | newDate.setFullYear(year); 128 | dispatchSetDate(calendarNodes.calendar, { instance: this, date: newDate }); 129 | } 130 | 131 | // TODO: Add Events Integration 132 | // customizeEvents: (eventsType) => { 133 | // if (!validateRequired(eventsType, eventColorTypeSchema)) return; 134 | // instance.options.eventColorScheme.push(...eventsType); 135 | // }, 136 | // addEvents: (events) => { 137 | // if (!validateRequired(events, eventSchema)) return; 138 | // instance.options.events.push(...events); 139 | // } 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /src/js/listeners.js: -------------------------------------------------------------------------------- 1 | import { spanTemplate } from './template'; 2 | import { Animation, createNewDate } from './utils'; 3 | import { 4 | CALENDAR_HIDE, 5 | CALENDAR_SHOW, 6 | CALENDAR_UPDATE, 7 | DISPLAY_UPDATE, 8 | PREVIEW_UPDATE, 9 | HEADER_UPDATE, 10 | CHANGE_MONTH, 11 | CHANGE_YEAR, 12 | DATE_PICK, 13 | PREVIEW_PICK, 14 | SET_DATE, 15 | CANCEL 16 | } from './events'; 17 | import { 18 | dispatchDatePick, 19 | dispatchChangeMonth, 20 | dispatchChangeYear, 21 | dispatchPreviewCellPick, 22 | dispatchCancel 23 | } from './emiters'; 24 | 25 | import { 26 | updateCalendarTable, 27 | updateCalendarHeader, 28 | updateMonthYearPreview, 29 | updateCalendarUI, 30 | updateDisplay, 31 | updatePickedDateValue, 32 | updateLinkedInputValue, 33 | updateMonthSelect, 34 | updateYearSelect, 35 | getTargetDate, 36 | getNewMonth, 37 | getNewYear 38 | } from './handlers'; 39 | 40 | export const applyListeners = (calendarNodes) => { 41 | let activeInstance = null; 42 | let clickable = true; 43 | const { 44 | calendarStates, 45 | calendar, 46 | calendarDisplay, 47 | calendarPicker, 48 | calendarHeader, 49 | currentMonthSelect, 50 | currentYearSelect, 51 | monthYearPreview, 52 | monthNavPrev, 53 | monthNavNext, 54 | yearNavPrev, 55 | yearNavNext, 56 | dateCells, 57 | previewCells, 58 | cancelButton, 59 | okButton, 60 | clearButton 61 | } = calendarNodes; 62 | 63 | calendar.addEventListener(CALENDAR_SHOW, (e) => { 64 | activeInstance = e.detail.instance; 65 | // update the calendar UI 66 | updateCalendarUI(calendarNodes, activeInstance); 67 | // show the calendar 68 | calendar.classList.add('mc-calendar--opened'); 69 | // run all custom onOpen callbacks added by the user 70 | activeInstance.onOpenCallbacks.forEach((callback) => callback.apply(null)); 71 | }); 72 | 73 | calendar.addEventListener(CALENDAR_HIDE, () => { 74 | const { options, onCloseCallbacks } = activeInstance; 75 | // hide the calendar 76 | calendar.classList.remove('mc-calendar--opened'); 77 | // delete the style attribute for inline calendar 78 | if (options.bodyType == 'inline') calendar.removeAttribute('style'); 79 | // reset the active instance 80 | activeInstance = null; 81 | // run all custom onClose callbacks added by the user 82 | onCloseCallbacks.forEach((callback) => callback.apply(null)); 83 | }); 84 | calendar.addEventListener(DATE_PICK, (e) => { 85 | if (!activeInstance) return; 86 | const { options } = activeInstance; 87 | const { autoClose, closeOndblclick } = options; 88 | if (!e.target.classList.contains('mc-date--selectable')) return; 89 | 90 | if (e.detail.dblclick) { 91 | if (!closeOndblclick) return; 92 | return updatePickedDateValue(activeInstance, calendarStates); 93 | } 94 | // update the instance picked date 95 | activeInstance.pickedDate = e.detail.date; 96 | // update display store data 97 | activeInstance.store.display.setDate = e.detail.date; 98 | // update the classlist of the picked cell 99 | dateCells.forEach((cell) => cell.classList.remove('mc-date--picked')); 100 | e.target.classList.add('mc-date--picked'); 101 | 102 | if (autoClose) updatePickedDateValue(activeInstance, calendarStates); 103 | }); 104 | 105 | calendar.addEventListener(PREVIEW_PICK, (e) => { 106 | if (!activeInstance) return; 107 | const { data, dblclick } = e.detail; 108 | const { store, options, viewLayers } = activeInstance; 109 | const { customMonths, autoClose, closeOndblclick } = options; 110 | const { target } = store.preview; 111 | 112 | if (e.target.classList.contains('mc-month-year__cell--inactive')) return; 113 | 114 | previewCells.forEach((cell) => cell.classList.remove('mc-month-year__cell--picked')); 115 | e.target.classList.add('mc-month-year__cell--picked'); 116 | 117 | if (dblclick && store.preview.target === viewLayers[0]) { 118 | if (!closeOndblclick) return; 119 | return updatePickedDateValue(activeInstance, calendarStates); 120 | } 121 | 122 | let targetYear = store.preview.year; 123 | let targetMonth = customMonths[store.header.month]; 124 | 125 | if (viewLayers[0] === 'year') targetMonth = customMonths[0]; 126 | if (target === 'month') targetMonth = data; 127 | if (target === 'year') targetYear = Number(data); 128 | 129 | const targetMonthIndex = customMonths.findIndex((month) => month.includes(targetMonth)); 130 | const nextCalendarDate = getTargetDate( 131 | activeInstance, 132 | createNewDate(targetYear, targetMonthIndex, 1) 133 | ); 134 | 135 | store.header.month = nextCalendarDate.getMonth(); 136 | store.preview.year = nextCalendarDate.getFullYear(); 137 | if (viewLayers[0] !== 'year') store.header.year = nextCalendarDate.getFullYear(); 138 | store.preview.month = nextCalendarDate.getMonth(); 139 | 140 | if (viewLayers[0] !== 'calendar') activeInstance.pickedDate = nextCalendarDate; 141 | if (viewLayers[0] !== 'calendar') store.display.setDate = nextCalendarDate; 142 | if (viewLayers[0] === 'calendar') store.calendar.setDate = nextCalendarDate; 143 | 144 | if (autoClose && store.preview.target === viewLayers[0]) { 145 | return updatePickedDateValue(activeInstance, calendarStates); 146 | } 147 | store.preview.setTarget = viewLayers[0]; 148 | store.header.setTarget = viewLayers[0]; 149 | 150 | currentMonthSelect.setAttribute('aria-expanded', false); 151 | currentYearSelect.setAttribute('aria-expanded', false); 152 | // Return focus to correct location 153 | if (target == 'month') currentMonthSelect.focus(); 154 | if (target == 'year') currentYearSelect.focus(); 155 | }); 156 | 157 | calendar.addEventListener(SET_DATE, (e) => { 158 | const { instance, date } = e.detail; 159 | instance.pickedDate = date; 160 | updateLinkedInputValue(instance); 161 | if (activeInstance?._id !== instance._id) return; 162 | const { store } = activeInstance; 163 | store.display.setDate = date; 164 | store.calendar.setDate = store.calendar.date; 165 | if (store.preview.target !== 'calendar') { 166 | store.preview.month = date.getMonth(); 167 | store.preview.year = date.getFullYear(); 168 | store.preview.setTarget = store.preview.target; 169 | } 170 | if (store.header.target === 'month') { 171 | store.header.month = date.getMonth(); 172 | store.header.year = date.getFullYear(); 173 | store.header.setTarget = store.header.target; 174 | } 175 | }); 176 | 177 | calendar.addEventListener( 178 | CALENDAR_UPDATE, 179 | (e) => activeInstance && updateCalendarTable(calendarNodes, activeInstance) 180 | ); 181 | 182 | document.addEventListener('click', (e) => { 183 | const targetElement = e.target; 184 | const isTargetCalendar = calendar.contains(targetElement); 185 | const isTargetInput = activeInstance?.linkedElement === targetElement; 186 | if (!isTargetCalendar && !isTargetInput && activeInstance) calendarStates.blur(); 187 | }); 188 | 189 | calendar.addEventListener(CANCEL, (e) => { 190 | if (!activeInstance) return; 191 | const { onCancelCallbacks } = activeInstance; 192 | onCancelCallbacks.forEach((callback) => callback.apply(null)); 193 | calendarStates.close(); 194 | }); 195 | 196 | calendarDisplay.addEventListener(DISPLAY_UPDATE, (e) => { 197 | activeInstance && updateDisplay(calendarNodes, activeInstance); 198 | }); 199 | 200 | calendarHeader.addEventListener( 201 | HEADER_UPDATE, 202 | (e) => activeInstance && updateCalendarHeader(calendarNodes, activeInstance) 203 | ); 204 | 205 | monthYearPreview.addEventListener( 206 | PREVIEW_UPDATE, 207 | (e) => activeInstance && updateMonthYearPreview(calendarNodes, activeInstance) 208 | ); 209 | 210 | currentMonthSelect.addEventListener(CHANGE_MONTH, function (e) { 211 | // check if the button is clickable 212 | if (!clickable || !activeInstance) return; 213 | clickable = !clickable; 214 | const slider = Animation(); 215 | const { 216 | store, 217 | viewLayers, 218 | options, 219 | onMonthChangeCallbacks, 220 | onYearChangeCallbacks 221 | } = activeInstance; 222 | const { customMonths } = options; 223 | const { direction } = e.detail; 224 | // get the value of active month 225 | const selectedMonth = customMonths[store.header.month]; 226 | // get the value of active Year 227 | let selectedYear = store.header.year; 228 | // get the next ot prev month and the overlap value 229 | const { newMonth, overlap } = getNewMonth(activeInstance, selectedMonth, direction); 230 | const newYear = overlap !== 0 ? getNewYear(options, selectedYear, direction) : selectedYear; 231 | const newCalendarDate = createNewDate(newYear, newMonth.index, 1); 232 | // add a new span tah with the new month to the months div 233 | if (overlap !== 0) { 234 | // add a new span with the new year to the years div 235 | currentYearSelect.innerHTML += spanTemplate(direction, newYear); 236 | // apply slide animation to years span tags 237 | slider.slide(currentYearSelect.children[0], currentYearSelect.children[1], direction); 238 | onYearChangeCallbacks.forEach((callback) => callback.apply(null)); 239 | } 240 | 241 | e.target.innerHTML += spanTemplate(direction, newMonth.name); 242 | // apply slide animation to months span tags 243 | slider.slide(e.target.children[0], e.target.children[1], direction); 244 | 245 | slider.onFinish(() => { 246 | // update the calendar table 247 | if (viewLayers[0] === 'calendar') store.calendar.setDate = newCalendarDate; 248 | if (viewLayers[0] !== 'calendar') store.display.setDate = newCalendarDate; 249 | if (viewLayers[0] === 'month') activeInstance.pickedDate = newCalendarDate; 250 | store.header.year = newCalendarDate.getFullYear(); 251 | store.header.setMonth = newCalendarDate.getMonth(); 252 | store.preview.year = newCalendarDate.getFullYear(); 253 | store.preview.setMonth = newCalendarDate.getMonth(); 254 | // run all custom onMonthChangeCallbacks added by the user 255 | onMonthChangeCallbacks.forEach((callback) => callback.apply(null)); 256 | 257 | clickable = !clickable; 258 | }); 259 | }); 260 | 261 | currentYearSelect.addEventListener(CHANGE_YEAR, function (e) { 262 | if (!clickable || !activeInstance) return; 263 | clickable = !clickable; 264 | const { direction } = e.detail; 265 | const { 266 | store, 267 | viewLayers, 268 | options, 269 | onMonthChangeCallbacks, 270 | onYearChangeCallbacks, 271 | prevLimitDate, 272 | nextLimitDate 273 | } = activeInstance; 274 | const { customMonths } = options; 275 | const slider = Animation(); 276 | const next = direction === 'next' ? true : false; 277 | const selectedYear = store.header.year; 278 | const currentMonthIndex = store.header.month; 279 | const viewTarget = store.header.target; 280 | const newYear = getNewYear(options, selectedYear, direction); 281 | 282 | let newMonth = null; 283 | let newCalendarDate = 284 | newYear && getTargetDate(activeInstance, createNewDate(newYear, currentMonthIndex, 1)); 285 | if (!newYear) newCalendarDate = next ? nextLimitDate : prevLimitDate; 286 | if (newCalendarDate.getMonth() !== currentMonthIndex) 287 | newMonth = customMonths[newCalendarDate.getMonth()]; 288 | 289 | if (viewTarget === 'year') { 290 | const firstTableYear = store.header.year; 291 | const targetYear = next ? firstTableYear + 12 : firstTableYear - 12; 292 | store.header.setYear = targetYear; 293 | store.preview.setTarget = 'year'; 294 | clickable = !clickable; 295 | return; 296 | } 297 | 298 | if (newMonth) { 299 | currentMonthSelect.innerHTML += spanTemplate(direction, newMonth); 300 | slider.slide(currentMonthSelect.children[0], currentMonthSelect.children[1], direction); 301 | onMonthChangeCallbacks.forEach((callback) => callback.apply(null)); 302 | } 303 | if (newYear) { 304 | e.target.innerHTML += spanTemplate(direction, newYear); 305 | slider.slide(e.target.children[0], e.target.children[1], direction); 306 | onYearChangeCallbacks.forEach((callback) => callback.apply(null)); 307 | } 308 | slider.onFinish(() => { 309 | if (viewLayers[0] === 'calendar') store.calendar.setDate = newCalendarDate; 310 | if (viewLayers[0] !== 'calendar') store.display.setDate = newCalendarDate; 311 | if (viewLayers[0] !== 'calendar') activeInstance.pickedDate = newCalendarDate; 312 | store.preview.year = newCalendarDate.getFullYear(); 313 | store.preview.setMonth = newCalendarDate.getMonth(); 314 | store.header.year = newCalendarDate.getFullYear(); 315 | store.header.setMonth = newCalendarDate.getMonth(); 316 | clickable = !clickable; 317 | }); 318 | }); 319 | 320 | currentMonthSelect.addEventListener( 321 | 'click', 322 | () => activeInstance && updateMonthSelect(activeInstance, calendarNodes) 323 | ); 324 | 325 | currentYearSelect.addEventListener('keydown', (e) => { 326 | if (e.key == 'Enter') updateMonthSelect(activeInstance, calendarNodes, 'keyboard'); 327 | if (e.key == 'Tab' && !e.shiftKey) { 328 | // Correct focus order 329 | e.preventDefault(); 330 | currentYearSelect.focus(); 331 | } 332 | }); 333 | 334 | currentYearSelect.addEventListener( 335 | 'click', 336 | () => activeInstance && updateYearSelect(activeInstance, calendarNodes) 337 | ); 338 | 339 | currentYearSelect.addEventListener('keydown', (e) => { 340 | if (e.key == 'Enter') updateYearSelect(activeInstance, calendarNodes, 'keyboard'); 341 | if (e.key == 'Tab') { 342 | // Correct focus order 343 | e.preventDefault(); 344 | if (e.shiftKey) return currentMonthSelect.focus(); 345 | monthNavNext.focus(); 346 | } 347 | }); 348 | 349 | // Dispatch custom events 350 | previewCells.forEach((cell) => { 351 | cell.addEventListener( 352 | 'click', 353 | (e) => e.detail === 1 && dispatchPreviewCellPick(e.currentTarget) 354 | ); 355 | cell.addEventListener( 356 | 'dblclick', 357 | (e) => e.detail === 2 && dispatchPreviewCellPick(e.currentTarget, true) 358 | ); 359 | cell.addEventListener( 360 | 'keydown', 361 | (e) => e.key === 'Enter' && dispatchPreviewCellPick(e.currentTarget) 362 | ); 363 | }); 364 | 365 | // add click event that dispatch a custom DATE_PICK event, to every calendar cell 366 | dateCells.forEach((cell) => { 367 | cell.addEventListener('click', (e) => e.detail === 1 && dispatchDatePick(e.target)); 368 | cell.addEventListener('dblclick', (e) => e.detail === 2 && dispatchDatePick(e.target, true)); 369 | cell.addEventListener('keydown', (e) => { 370 | e.key === 'Enter' && dispatchDatePick(e.target); 371 | e.key === 'End' && clearButton.focus(); 372 | }); 373 | }); 374 | 375 | monthNavPrev.addEventListener('click', (e) => { 376 | if (e.currentTarget.classList.contains('mc-select__nav--inactive')) return; 377 | dispatchChangeMonth(currentMonthSelect, 'prev'); 378 | }); 379 | 380 | monthNavNext.addEventListener('click', (e) => { 381 | if (e.currentTarget.classList.contains('mc-select__nav--inactive')) return; 382 | dispatchChangeMonth(currentMonthSelect, 'next'); 383 | }); 384 | monthNavNext.addEventListener('keydown', (e) => { 385 | // correct focus order 386 | if (e.key == 'Tab') { 387 | e.preventDefault(); 388 | if (e.shiftKey) return currentYearSelect.focus(); 389 | calendarHeader.nextElementSibling.querySelector('[tabindex="0"]').focus(); 390 | } 391 | }); 392 | 393 | yearNavPrev.addEventListener('click', (e) => { 394 | if (e.currentTarget.classList.contains('mc-select__nav--inactive')) return; 395 | dispatchChangeYear(currentYearSelect, 'prev'); 396 | }); 397 | 398 | yearNavNext.addEventListener('click', (e) => { 399 | if (e.currentTarget.classList.contains('mc-select__nav--inactive')) return; 400 | dispatchChangeYear(currentYearSelect, 'next'); 401 | }); 402 | 403 | cancelButton.addEventListener('click', (e) => dispatchCancel(calendar)); 404 | 405 | calendarPicker.addEventListener('keyup', (e) => e.key == 'Escape' && dispatchCancel(calendar)); 406 | 407 | okButton.addEventListener( 408 | 'click', 409 | (e) => activeInstance && updatePickedDateValue(activeInstance, calendarStates) 410 | ); 411 | 412 | clearButton.addEventListener('click', (e) => { 413 | if (!activeInstance) return; 414 | const { linkedElement, onClearCallbacks } = activeInstance; 415 | dateCells.forEach((cell) => cell.classList.remove('mc-date--picked')); 416 | activeInstance.pickedDate = null; 417 | if (linkedElement) linkedElement.value = null; 418 | onClearCallbacks.forEach((callback) => callback.apply(null)); 419 | }); 420 | }; 421 | 422 | export const applyOnFocusListener = (instance) => { 423 | if (!instance.linkedElement) return; 424 | instance.linkedElement.onfocus = (e) => { 425 | instance.open(); 426 | }; 427 | }; 428 | export const removeOnFocusListener = ({ linkedElement }) => { 429 | if (linkedElement) linkedElement.onfocus = null; 430 | }; 431 | -------------------------------------------------------------------------------- /src/js/mc-calendar.js: -------------------------------------------------------------------------------- 1 | import defaultOptions from './defaults'; 2 | import createInstance from './instance'; 3 | import { writeTemplate } from './render'; 4 | import { validateOptions } from './validators'; 5 | import { applyOnFocusListener, removeOnFocusListener } from './listeners'; 6 | 7 | import '../scss/main.scss'; 8 | 9 | const MCDatepicker = (() => { 10 | let datepickers = []; 11 | let calendarNodes = null; 12 | 13 | const initCalendar = (instanceOptions) => { 14 | if (calendarNodes) return; 15 | calendarNodes = writeTemplate(instanceOptions); 16 | }; 17 | 18 | const open = (uid) => { 19 | // find the instance based on it's unique id 20 | const activeInstance = datepickers.find(({ _id }) => _id === uid); 21 | if (!activeInstance && !calendarNodes) return; 22 | calendarNodes.calendarStates.open(activeInstance); 23 | }; 24 | 25 | const close = () => { 26 | if (!calendarNodes) return; 27 | const { calendarStates } = calendarNodes; 28 | calendarStates.close(); 29 | }; 30 | 31 | const create = (options = {}) => { 32 | // validate options and merge them with de default Options 33 | const instanceOptions = validateOptions(options, defaultOptions); 34 | // initiate the calendar instance once 35 | initCalendar(instanceOptions); 36 | // create instance 37 | const instance = createInstance(MCDatepicker, calendarNodes, instanceOptions); 38 | // push fresh created instance to instances array 39 | datepickers.push(instance); 40 | // add event listener to the linked input 41 | applyOnFocusListener(instance); 42 | 43 | return instance; 44 | }; 45 | const remove = (uid) => { 46 | // find the instance based on it's unique id 47 | const instance = datepickers.find(({ _id }) => _id === uid); 48 | if (!datepickers.length || !instance) return; 49 | // remove the onFocus listener 50 | removeOnFocusListener(instance); 51 | // Remove the instance from the datepickers array 52 | datepickers.splice(datepickers.indexOf(instance), 1); 53 | 54 | if (!datepickers.length) { 55 | const { calendar } = calendarNodes; 56 | calendar.parentNode.removeChild(calendar); 57 | calendarNodes = null; 58 | } 59 | }; 60 | return { create, remove, open, close }; 61 | })(); 62 | export default MCDatepicker; 63 | -------------------------------------------------------------------------------- /src/js/render.js: -------------------------------------------------------------------------------- 1 | import template from './template'; 2 | import { valueOfDate, createNewDate } from './utils'; 3 | import { getDOMNodes } from './handlers'; 4 | import { applyListeners } from './listeners'; 5 | import { 6 | isActiveMonth, 7 | isActiveYear, 8 | isSelectable, 9 | isInActiveMonth, 10 | isExcludedWeekend, 11 | isDisabledWeekDay, 12 | isDisabledDate, 13 | isPicked, 14 | isMarked, 15 | isToday 16 | } from './checker'; 17 | 18 | const dayObj = (dateVal = null) => { 19 | return { 20 | date: dateVal, 21 | day: dateVal.getDay(), 22 | dateNumb: dateVal.getDate(), 23 | month: dateVal.getMonth(), 24 | year: dateVal.getFullYear(), 25 | classList: [] 26 | }; 27 | }; 28 | 29 | const getCalendarArray = (options, firstMonthDate) => { 30 | let calendarArray = []; 31 | // get the day of the first date of the first table cell 32 | const { firstWeekday } = options; 33 | const wDay = firstMonthDate.getDay(); 34 | const wDays = 7; 35 | const offset = (firstWeekday - wDays) % wDays; 36 | let firstCalendarDate = ((wDay - offset - 1) * -1) % wDays; 37 | firstCalendarDate = firstCalendarDate > -6 ? firstCalendarDate : 1; 38 | // generate the calendar array 39 | while (calendarArray.length < 42) { 40 | // regenerate date object based on first active month day 41 | let modifiableDate = new Date(firstMonthDate); 42 | // generate a new date based on the iteration of the first calendar day 43 | let newDate = new Date(modifiableDate.setDate(firstCalendarDate++)); 44 | calendarArray.push(dayObj(newDate)); 45 | } 46 | return calendarArray; 47 | }; 48 | 49 | export const renderMonthPreview = (calendarNodes, instance) => { 50 | const { previewCells, currentMonthSelect } = calendarNodes; 51 | const { store, prevLimitDate, nextLimitDate, options } = instance; 52 | const { customMonths } = options; 53 | const currentMonth = customMonths[store.preview.month]; 54 | const selectedYear = store.preview.year; 55 | currentMonthSelect.setAttribute('aria-expanded', true); 56 | customMonths.map((month, index) => { 57 | let monthClasslist = ['mc-month-year__cell']; 58 | const firstMonthDate = createNewDate(Number(selectedYear), index); 59 | const lastMonthDate = createNewDate(Number(selectedYear), index + 1, 0); 60 | const lessThanMinDate = 61 | prevLimitDate && valueOfDate(lastMonthDate) < valueOfDate(prevLimitDate); 62 | const moreThanMaxDate = 63 | nextLimitDate && valueOfDate(firstMonthDate) > valueOfDate(nextLimitDate); 64 | let ariaLabel = month; 65 | if (month === currentMonth) { 66 | monthClasslist.push('mc-month-year__cell--picked'); 67 | ariaLabel = `Current Month: ${ariaLabel}`; 68 | } 69 | if ( 70 | lessThanMinDate || 71 | moreThanMaxDate || 72 | !isActiveMonth(options, index) || 73 | !isActiveYear(options, Number(selectedYear)) 74 | ) { 75 | monthClasslist.push('mc-month-year__cell--inactive'); 76 | previewCells[index].setAttribute('tabindex', -1); 77 | } else { 78 | previewCells[index].setAttribute('tabindex', 0); 79 | } 80 | 81 | previewCells[index].classList = monthClasslist.join(' '); 82 | previewCells[index].innerHTML = `${month.substr(0, 3)}`; 83 | previewCells[index].setAttribute('aria-label', month); 84 | }); 85 | }; 86 | 87 | export const renderYearPreview = (calendarNodes, instance, year) => { 88 | const { previewCells, currentYearSelect } = calendarNodes; 89 | const { store, prevLimitDate, nextLimitDate, options } = instance; 90 | const minYear = prevLimitDate && prevLimitDate.getFullYear(); 91 | const maxYear = nextLimitDate && nextLimitDate.getFullYear(); 92 | const currentYear = store.preview.year; 93 | currentYearSelect.setAttribute('aria-expanded', true); 94 | previewCells.forEach((cell, index) => { 95 | let yearClasslist = ['mc-month-year__cell']; 96 | let customYear = year + index; 97 | const lessThanMinYear = prevLimitDate && customYear < minYear; 98 | const moreThanMaxYear = nextLimitDate && customYear > maxYear; 99 | if (customYear === currentYear) yearClasslist.push('mc-month-year__cell--picked'); 100 | if (lessThanMinYear || moreThanMaxYear || !isActiveYear(options, customYear)) { 101 | yearClasslist.push('mc-month-year__cell--inactive'); 102 | previewCells[index].setAttribute('tabindex', -1); 103 | } else { 104 | previewCells[index].setAttribute('tabindex', 0); 105 | } 106 | cell.classList = yearClasslist.join(' '); 107 | cell.innerHTML = `${customYear}`; 108 | }); 109 | }; 110 | 111 | export const renderCalendar = (instance, date) => { 112 | const { options, pickedDate } = instance; 113 | // get the first day of the month 114 | const firstMonthDate = createNewDate(date.getFullYear(), date.getMonth(), 1); 115 | // get the month 116 | const activeMonth = firstMonthDate.getMonth(); 117 | // create date custom object 118 | const renderDay = (dayObject) => { 119 | let classArray = ['mc-date']; 120 | dayObject.ariaLabel = dayObject.date.toDateString(); 121 | // check the cases when the date is not active 122 | if ( 123 | !isInActiveMonth(activeMonth, dayObject) || 124 | !isActiveMonth(options, dayObject.month) || 125 | !isActiveYear(options, dayObject.year) || 126 | isSelectable(instance, dayObject) || 127 | isExcludedWeekend(options, dayObject) || 128 | isDisabledWeekDay(options, dayObject) || 129 | isDisabledDate(options, dayObject) 130 | ) { 131 | classArray.push('mc-date--inactive'); 132 | if (options.hideInactiveDays) classArray.push('mc-date--invisible'); 133 | if (options.selectInactiveDays) classArray.push('mc-date--selectable'); 134 | dayObject.tabindex = -1; 135 | } else { 136 | classArray.push('mc-date--active'); 137 | classArray.push('mc-date--selectable'); 138 | dayObject.tabindex = 0; 139 | } 140 | if (isPicked(pickedDate, dayObject)) { 141 | classArray.push('mc-date--picked'); 142 | dayObject.ariaLabel = `Picked: ${dayObject.ariaLabel}`; 143 | } 144 | 145 | if (isMarked(instance, dayObject)) { 146 | classArray.push('mc-date--marked'); 147 | dayObject.ariaLabel = `Marked: ${dayObject.ariaLabel}`; 148 | } 149 | 150 | if (isToday(dayObject)) { 151 | classArray.push('mc-date--today'); 152 | dayObject.ariaLabel = `Today: ${dayObject.ariaLabel}`; 153 | } 154 | 155 | dayObject.classList = classArray.join(' '); 156 | 157 | return dayObject; 158 | }; 159 | 160 | const calendarArray = getCalendarArray(options, firstMonthDate); 161 | return calendarArray.map((day) => renderDay(day)); 162 | }; 163 | 164 | export function writeTemplate(instanceOptions) { 165 | // create a new div tag 166 | const calendarDiv = document.createElement('div'); 167 | // set the classList of the created div 168 | calendarDiv.className = 'mc-calendar'; 169 | // make the calendar focusable 170 | calendarDiv.setAttribute('tabindex', 0); 171 | // write the template to the div content 172 | calendarDiv.innerHTML = template; 173 | // add the new div to the document 174 | document.body.appendChild(calendarDiv); 175 | // get calendar Nodes 176 | const calendarNodes = getDOMNodes(calendarDiv); 177 | // apply listeners to calendar 178 | applyListeners(calendarNodes); 179 | 180 | return calendarNodes; 181 | } 182 | -------------------------------------------------------------------------------- /src/js/template.js: -------------------------------------------------------------------------------- 1 | export const spanTemplate = (direction, content) => { 2 | const units = direction === 'next' ? '-100' : '100'; 3 | return `${content}`; 4 | }; 5 | 6 | export default `
7 |
8 |

Thursday

9 |
10 |
11 |
12 |

1

13 |
14 |
15 |

January

16 |

1970

17 |
18 |
19 |
20 |
21 |
22 |
23 | 28 | 31 | 36 |
37 |
38 | 43 | 46 | 51 |
52 |
January 1970
53 |
54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 |
SMTWTFS
28293031123
45678910
11121314151617
18192021222324
25262728293031
1234567
124 | 138 |
139 | 148 |
`; 149 | -------------------------------------------------------------------------------- /src/js/utils.js: -------------------------------------------------------------------------------- 1 | import { 2 | dispatchCalendarShow, 3 | dispatchCalendarHide, 4 | dispatchCalendarUpdate, 5 | dispatchDisplayUpdate, 6 | dispatchHeaderUpdate, 7 | dispatchPreviewUpdate 8 | } from './emiters'; 9 | import { dateFormatValidator } from './validators'; 10 | import { Is } from './validators'; 11 | 12 | export function noop() {} 13 | 14 | export const getNewIndex = (array, currentIndex, direction) => { 15 | // get the next array index, if the current index is equal to array.length set the next index to 0 16 | const forward = (currentIndex + 1) % array.length; 17 | // get the prev array index, if the current index is equal to 0 set the next index to the last array index 18 | const backward = (((currentIndex - 1) % array.length) + array.length) % array.length; 19 | // get the overlap if the next index is greather that array.length 20 | const forwardOverlap = (currentIndex + 1) / array.length; 21 | // get the overlap if the index is less that 0 22 | const backwardOverlap = (currentIndex - array.length) / array.length; 23 | // get the new Index based on direction 24 | const newIndex = direction === 'next' ? forward : backward; 25 | // get overlap based on direction 26 | const overlap = direction === 'next' ? ~~forwardOverlap : ~~backwardOverlap; 27 | return { newIndex, overlap }; 28 | }; 29 | 30 | export const getAnimations = (element) => { 31 | return Promise.all( 32 | element.getAnimations({ subtree: true }).map((animation) => animation.finished) 33 | ); 34 | }; 35 | 36 | export const waitFor = (time) => { 37 | return new Promise((resolve, reject) => { 38 | setTimeout(resolve, time); 39 | }); 40 | }; 41 | 42 | export const Animation = () => { 43 | let promises = null; 44 | const slide = (activeElem, newElem, dir) => { 45 | // const activeElementClass = dir === 'prev' ? 'slide-left--out' : 'slide-right--out'; 46 | const activeElementClass = dir === 'prev' ? 'slide-right--out' : 'slide-left--out'; 47 | // const newElementClass = dir === 'prev' ? 'slide-left--in' : 'slide-right--in'; 48 | const newElementClass = dir === 'prev' ? 'slide-right--in' : 'slide-left--in'; 49 | activeElem.classList.add(activeElementClass); 50 | newElem.classList.add(newElementClass); 51 | 52 | promises = waitFor(150).then(() => { 53 | // remove the style attribute from the new element 54 | activeElem.remove(); 55 | newElem.removeAttribute('style'); 56 | newElem.classList.remove(newElementClass); 57 | // remove the old span tag 58 | }); 59 | }; 60 | const onFinish = (callback) => { 61 | !promises && callback(); 62 | promises && promises.then(() => callback()); 63 | promises = null; 64 | }; 65 | return { slide, onFinish }; 66 | }; 67 | 68 | export const createNewDate = (year, month, day) => { 69 | const date = new Date(); 70 | date.setHours(0, 0, 0, 0); 71 | Is(year).number() && date.setFullYear(year); 72 | Is(month).number() && date.setMonth(month); 73 | Is(day).number() && date.setDate(day); 74 | return date; 75 | }; 76 | 77 | export const dateFormatParser = ( 78 | date = new Date(), 79 | { customWeekDays, customMonths }, 80 | format = 'dd-mmm-yyyy' 81 | ) => { 82 | if (Is(date).date() && dateFormatValidator(format.toLocaleLowerCase()).isValid()) { 83 | const wDay = date.getDay(); 84 | const mDate = date.getDate(); 85 | const month = date.getMonth(); 86 | const year = String(date.getFullYear()).padStart(4, '0'); 87 | const flags = { 88 | d: String(mDate), 89 | dd: String(mDate).padStart(2, '0'), 90 | ddd: customWeekDays[wDay].substr(0, 3), 91 | dddd: customWeekDays[wDay], 92 | m: String(month + 1), 93 | mm: String(month + 1).padStart(2, '0'), 94 | mmm: customMonths[month].substr(0, 3), 95 | mmmm: customMonths[month], 96 | yy: year, 97 | yyyy: year 98 | }; 99 | return dateFormatValidator(format.toLocaleLowerCase()).replaceMatch(flags); 100 | } 101 | throw new Error(date + ' Is not a Date object.'); 102 | }; 103 | 104 | export const valueOfDate = (date) => { 105 | return date.setHours(0, 0, 0, 0).valueOf(); 106 | }; 107 | 108 | export const getRectProps = (element) => { 109 | var rec = element.getBoundingClientRect(); 110 | return { 111 | t: Math.ceil(rec.top), 112 | l: Math.ceil(rec.left), 113 | b: Math.ceil(rec.bottom), 114 | r: Math.ceil(rec.right), 115 | w: Math.ceil(rec.width), 116 | h: Math.ceil(rec.height) 117 | }; 118 | }; 119 | const getDimensions = (calendarDIV, linkedElement) => { 120 | const vw = window.innerWidth; 121 | const vh = window.innerHeight; 122 | const dh = document.body.offsetHeight; 123 | const elementDimensions = getRectProps(linkedElement); 124 | const calendarDimensions = getRectProps(calendarDIV); 125 | const elementOffsetTop = elementDimensions.t + +window.scrollY; 126 | const elementOffsetleft = elementDimensions.l + window.scrollX; 127 | return { 128 | vw, 129 | vh, 130 | dh, 131 | elementOffsetTop, 132 | elementOffsetleft, 133 | elem: elementDimensions, 134 | cal: calendarDimensions 135 | }; 136 | }; 137 | const getOffsetOnView = ({ elem, cal }) => { 138 | const t = elem.t - cal.h - 10; 139 | const b = elem.b + cal.h + 10; 140 | const l = elem.w > cal.w ? elem.l : elem.l - cal.w; 141 | const r = elem.w > cal.w ? elem.r : elem.r + cal.w; 142 | return { t, b, l, r }; 143 | }; 144 | 145 | const getOffsetOnDoc = ({ elementOffsetTop, elem, cal }) => { 146 | const t = elementOffsetTop - cal.h - 10; 147 | const b = elementOffsetTop + elem.h + cal.h + 10; 148 | return { t, b }; 149 | }; 150 | 151 | export const calculateCalendarPosition = (calendarDIV, linkedElement) => { 152 | const allDimensions = getDimensions(calendarDIV, linkedElement); 153 | const { cal, elem, vw, vh, dh, elementOffsetTop, elementOffsetleft } = allDimensions; 154 | const offsetOnView = getOffsetOnView(allDimensions); 155 | const offsetOnDoc = getOffsetOnDoc(allDimensions); 156 | const moreThanViewMinW = offsetOnView.l > 0 ? true : false; 157 | const lessThanViewMaxW = vw > offsetOnView.r ? true : false; 158 | const moreThanViewMinH = offsetOnView.t > 0 ? true : false; 159 | const lessThanViewMaxH = vh > offsetOnView.b ? true : false; 160 | 161 | const moreThanDocMinH = offsetOnDoc.t > 0 ? true : false; 162 | const lessThanDocMaxH = dh > offsetOnDoc.b ? true : false; 163 | 164 | let top = null; 165 | let left = null; 166 | // calculate left position 167 | if (lessThanViewMaxW) left = elementOffsetleft; 168 | if (!lessThanViewMaxW && moreThanViewMinW) left = elementOffsetleft + elem.w - cal.w; 169 | if (!lessThanViewMaxW && !moreThanViewMinW) left = (vw - cal.w) / 2; 170 | 171 | // calculate top position 172 | 173 | if (lessThanViewMaxH) top = elementOffsetTop + elem.h + 5; 174 | if (!lessThanViewMaxH && moreThanViewMinH) top = elementOffsetTop - cal.h - 5; 175 | if (!lessThanViewMaxH && !moreThanViewMinH) { 176 | if (lessThanDocMaxH) top = elementOffsetTop + elem.h + 5; 177 | if (!lessThanDocMaxH && moreThanDocMinH) top = elementOffsetTop - cal.h - 5; 178 | if (!lessThanDocMaxH && !moreThanDocMinH) top = (vh - cal.h) / 2; 179 | } 180 | return { top, left }; 181 | }; 182 | 183 | export const HandleArrowClass = (arrow) => { 184 | const active = () => { 185 | arrow.classList.remove('mc-select__nav--inactive'); 186 | }; 187 | const inactive = () => { 188 | arrow.classList.add('mc-select__nav--inactive'); 189 | }; 190 | 191 | return { active, inactive }; 192 | }; 193 | 194 | export const Store = (calendarNodes, dataTarget) => { 195 | const { calendar, calendarDisplay, calendarHeader, monthYearPreview } = calendarNodes; 196 | return { 197 | display: { 198 | target: dataTarget, 199 | date: null, 200 | set setDate(date) { 201 | this.date = date; 202 | dispatchDisplayUpdate(calendarDisplay); 203 | } 204 | }, 205 | header: { 206 | target: dataTarget, 207 | month: null, 208 | year: null, 209 | set setTarget(target) { 210 | this.target = target; 211 | dispatchHeaderUpdate(calendarHeader); 212 | }, 213 | set setMonth(month) { 214 | this.month = month; 215 | dispatchHeaderUpdate(calendarHeader); 216 | }, 217 | set setYear(year) { 218 | this.year = year; 219 | dispatchHeaderUpdate(calendarHeader); 220 | } 221 | }, 222 | preview: { 223 | target: null, 224 | month: null, 225 | year: null, 226 | set setTarget(target) { 227 | this.target = target; 228 | dispatchPreviewUpdate(monthYearPreview); 229 | }, 230 | set setMonth(month) { 231 | this.month = month; 232 | dispatchPreviewUpdate(monthYearPreview); 233 | }, 234 | set setYear(year) { 235 | this.year = year; 236 | dispatchPreviewUpdate(monthYearPreview); 237 | } 238 | }, 239 | calendar: { 240 | date: null, 241 | set setDate(date) { 242 | this.date = date; 243 | dispatchCalendarUpdate(calendar); 244 | } 245 | } 246 | }; 247 | }; 248 | 249 | export const CalendarStateManager = (calendar) => { 250 | let openTimer = null; 251 | let closeTimer = null; 252 | let prevInstance = null; 253 | let sameInstance = false; 254 | 255 | return { 256 | opened: false, 257 | closed: true, 258 | blured: false, 259 | isOpening: false, 260 | isClosing: false, 261 | isBluring: false, 262 | open(instance) { 263 | if (this.isClosing) return; 264 | sameInstance = prevInstance && prevInstance._id === instance._id; 265 | this.isOpening = true; 266 | clearTimeout(openTimer); 267 | dispatchCalendarShow(calendar, instance); 268 | openTimer = setTimeout(() => { 269 | this.isOpening = false; 270 | this.opened = true; 271 | this.closed = false; 272 | prevInstance = instance; 273 | }, 200); 274 | }, 275 | close() { 276 | if (this.closed || this.isOpening || this.isClosing) return; 277 | sameInstance = false; 278 | this.isClosing = true; 279 | clearTimeout(closeTimer); 280 | dispatchCalendarHide(calendar); 281 | closeTimer = setTimeout(() => { 282 | this.isClosing = false; 283 | this.opened = false; 284 | this.closed = true; 285 | }, 200); 286 | }, 287 | blur() { 288 | this.isBluring = true; 289 | return waitFor(100).then(() => { 290 | if (this.closed || this.isOpening || this.isClosing) return !sameInstance; 291 | if (prevInstance && !prevInstance.options.closeOnBlur) return false; 292 | this.close(); 293 | this.isBluring = false; 294 | this.blured = true; 295 | return true; 296 | }); 297 | } 298 | }; 299 | }; 300 | 301 | export const uniqueId = (length = 16) => { 302 | return parseInt(Math.ceil(Math.random() * Date.now()).toPrecision(length)).toString(16); 303 | }; 304 | 305 | export const themeParser = (defaultTheme, customTheme) => { 306 | return { 307 | theme_color: { 308 | cssVar: '--mc-theme-color', 309 | color: customTheme?.theme_color || defaultTheme.theme_color 310 | }, 311 | main_background: { 312 | cssVar: '--mc-main-bg', 313 | color: customTheme?.main_background || defaultTheme.main_background 314 | }, 315 | active_text_color: { 316 | cssVar: '--mc-active-text-color', 317 | color: customTheme?.active_text_color || defaultTheme.active_text_color 318 | }, 319 | inactive_text_color: { 320 | cssVar: '--mc-inactive-text-color', 321 | color: customTheme?.inactive_text_color || defaultTheme.inactive_text_color 322 | }, 323 | display_foreground: { 324 | cssVar: '--mc-display-foreground', 325 | color: customTheme?.display?.foreground || defaultTheme.display.foreground 326 | }, 327 | display_background: { 328 | cssVar: '--mc-display-background', 329 | color: 330 | customTheme?.display?.background || 331 | customTheme?.theme_color || 332 | defaultTheme.display.background 333 | }, 334 | picker_foreground: { 335 | cssVar: '--mc-picker-foreground', 336 | color: 337 | customTheme?.picker?.foreground || 338 | customTheme?.active_text_color || 339 | defaultTheme.picker.foreground 340 | }, 341 | picker_background: { 342 | cssVar: '--mc-picker-background', 343 | color: 344 | customTheme?.picker?.background || 345 | customTheme?.main_background || 346 | defaultTheme.picker.background 347 | }, 348 | picker_header_active: { 349 | cssVar: '--mc-picker-header-active', 350 | color: customTheme?.picker_header?.active || defaultTheme.picker_header.active 351 | }, 352 | picker_header_inactive: { 353 | cssVar: '--mc-picker-header-inactive', 354 | color: 355 | customTheme?.picker_header?.inactive || 356 | customTheme?.inactive_text_color || 357 | defaultTheme.picker_header.inactive 358 | }, 359 | weekday_foreground: { 360 | cssVar: '--mc-weekday-foreground', 361 | color: 362 | customTheme?.weekday?.foreground || 363 | customTheme?.theme_color || 364 | defaultTheme.weekday.foreground 365 | }, 366 | button_success_foreground: { 367 | cssVar: '--mc-btn-success-foreground', 368 | color: 369 | customTheme?.button?.success?.foreground || 370 | customTheme?.theme_color || 371 | defaultTheme.button.success.foreground 372 | }, 373 | button_danger_foreground: { 374 | cssVar: '--mc-btn-danger-foreground', 375 | color: customTheme?.button?.danger?.foreground || defaultTheme.button.danger.foreground 376 | }, 377 | date_active_default_foreground: { 378 | cssVar: '--mc-date-active-def-foreground', 379 | color: 380 | customTheme?.date?.active?.default?.foreground || 381 | customTheme?.active_text_color || 382 | defaultTheme.date.active.default.foreground 383 | }, 384 | date_active_picked_foreground: { 385 | cssVar: '--mc-date-active-pick-foreground', 386 | color: 387 | customTheme?.date?.active?.picked?.foreground || defaultTheme.date.active.picked.foreground 388 | }, 389 | date_active_picked_background: { 390 | cssVar: '--mc-date-active-pick-background', 391 | color: 392 | customTheme?.date?.active?.picked?.background || 393 | customTheme?.theme_color || 394 | defaultTheme.date.active.picked.background 395 | }, 396 | date_active_today_foreground: { 397 | cssVar: '--mc-date-active-today-foreground', 398 | color: 399 | customTheme?.date?.active?.today?.foreground || 400 | customTheme?.active_text_color || 401 | defaultTheme.date.active.today.foreground 402 | }, 403 | date_active_today_background: { 404 | cssVar: '--mc-date-active-today-background', 405 | color: 406 | customTheme?.date?.active?.today?.background || 407 | customTheme?.inactive_text_color || 408 | defaultTheme.date.active.today.background 409 | }, 410 | date_inactive_default_foreground: { 411 | cssVar: '--mc-date-inactive-def-foreground', 412 | color: 413 | customTheme?.date?.inactive?.default?.foreground || 414 | customTheme?.inactive_text_color || 415 | defaultTheme.date.inactive.default.foreground 416 | }, 417 | date_inactive_picked_foreground: { 418 | cssVar: '--mc-date-inactive-pick-foreground', 419 | color: 420 | customTheme?.date?.inactive?.picked?.foreground || 421 | customTheme?.theme_color || 422 | defaultTheme.date.inactive.picked.foreground 423 | }, 424 | date_inactive_picked_background: { 425 | cssVar: '--mc-date-inactive-pick-background', 426 | color: 427 | customTheme?.date?.inactive?.picked?.background || 428 | customTheme?.theme_color || 429 | defaultTheme.date.inactive.picked.background 430 | }, 431 | date_inactive_today_foreground: { 432 | cssVar: '--mc-date-inactive-today-foreground', 433 | color: 434 | customTheme?.date?.inactive?.today?.foreground || 435 | customTheme?.inactive_text_color || 436 | defaultTheme.date.inactive.today.foreground 437 | }, 438 | date_inactive_today_background: { 439 | cssVar: '--mc-date-inactive-today-background', 440 | color: 441 | customTheme?.date?.inactive?.today?.background || 442 | customTheme?.inactive_text_color || 443 | defaultTheme.date.inactive.today.background 444 | }, 445 | date_marcked_foreground: { 446 | cssVar: '--mc-date-marcked-foreground', 447 | color: 448 | customTheme?.date?.marcked?.foreground || 449 | customTheme?.theme_color || 450 | defaultTheme.date.marcked.foreground 451 | }, 452 | month_year_preview_active_default_foreground: { 453 | cssVar: '--mc-prev-active-def-foreground', 454 | color: 455 | customTheme?.month_year_preview?.active?.default?.foreground || 456 | customTheme?.active_text_color || 457 | defaultTheme.month_year_preview.active.default.foreground 458 | }, 459 | month_year_preview_active_picked_foreground: { 460 | cssVar: '--mc-prev-active-pick-foreground', 461 | color: 462 | customTheme?.month_year_preview?.active?.picked?.foreground || 463 | customTheme?.active_text_color || 464 | defaultTheme.month_year_preview.active.picked.foreground 465 | }, 466 | month_year_preview_active_picked_background: { 467 | cssVar: '--mc-prev-active-pick-background', 468 | color: 469 | customTheme?.month_year_preview?.active?.picked?.background || 470 | defaultTheme.month_year_preview.active.picked.background 471 | }, 472 | month_year_preview_inactive_default_foreground: { 473 | cssVar: '--mc-prev-inactive-def-foreground', 474 | color: 475 | customTheme?.month_year_preview?.inactive?.default?.foreground || 476 | customTheme?.inactive_text_color || 477 | defaultTheme.month_year_preview.inactive.default.foreground 478 | }, 479 | month_year_preview_inactive_picked_foreground: { 480 | cssVar: '--mc-prev-inactive-pick-foreground', 481 | color: 482 | customTheme?.month_year_preview?.inactive?.picked?.foreground || 483 | customTheme?.inactive_text_color || 484 | defaultTheme.month_year_preview.inactive.picked.foreground 485 | }, 486 | month_year_preview_inactive_picked_background: { 487 | cssVar: '--mc-prev-inactive-pick-background', 488 | color: 489 | customTheme?.month_year_preview?.inactive?.picked?.background || 490 | customTheme?.inactive_text_color || 491 | defaultTheme.month_year_preview.inactive.picked.background 492 | } 493 | }; 494 | }; 495 | -------------------------------------------------------------------------------- /src/js/validators.js: -------------------------------------------------------------------------------- 1 | import { valueOfDate } from './utils'; 2 | 3 | export const Is = (variable) => { 4 | const type = Object.prototype.toString 5 | .call(variable) 6 | .match(/\s([a-zA-Z]+)/)[1] 7 | .toLowerCase(); 8 | 9 | const number = () => { 10 | const isNaN = Number.isNaN(variable); 11 | return type === 'number' && !isNaN; 12 | }; 13 | 14 | const object = () => type === 'object'; 15 | const array = () => type === 'array'; 16 | const date = () => type === 'date'; 17 | const string = () => type === 'string'; 18 | const boolean = () => type === 'boolean'; 19 | const func = () => type === 'function'; 20 | const defined = () => variable !== void 0; 21 | return { object, array, date, number, string, boolean, func, defined }; 22 | }; 23 | 24 | const isValidColor = (color) => { 25 | const validator = /(?:#|0x)(?:[a-f0-9]{3}|[a-f0-9]{6})\b|(?:rgb|hsl)a?\([^\)]*\)/gi; 26 | return Is(color).string() && validator.test(color); 27 | }; 28 | 29 | export const dateFormatValidator = (format) => { 30 | const validator = /^(?:(d{1,4}|m{1,4}|y{4}|y{2})?\b(?:(?:,\s)|[.-\s\/]{1})?(d{1,4}|m{1,4}|y{4}|y{2})?\b(?:(?:,\s)|[.-\s\/]{1})?(d{1,4}|m{1,4}|y{4}|y{2})\b(?:(?:,\s)|[.-\s\/]{1})?(d{1,4}|m{1,4}|y{2}|y{4})?\b)$/gi; 31 | 32 | const isValid = () => { 33 | // check if the value of the format property match the RegExp 34 | const test = validator.test(format); 35 | if (!test) return console.error(new Error(`"${format}" format is not supported`)); 36 | return test; 37 | }; 38 | const replaceMatch = (flags) => { 39 | // replace all matched groups with the value of flags 40 | return format.replace(validator, (match, ...groups) => { 41 | groups.forEach((group) => { 42 | if (group) match = match.replace(group, flags[group]); 43 | }); 44 | return match; 45 | }); 46 | }; 47 | return { isValid, replaceMatch }; 48 | }; 49 | 50 | const objectKeysValidator = (keys, object) => { 51 | return ( 52 | Is(object).object() && 53 | Is(keys).array() && 54 | Object.keys(object).every((key) => keys.includes(key)) 55 | ); 56 | // Is(object).object() && Is(keys).array() && keys.every((key) => object.hasOwnProperty(key)) 57 | // Object.keys(object).every((key) => keys.includes(key)) 58 | }; 59 | 60 | const keyColorValidator = (keys, object) => { 61 | const hasKeys = objectKeysValidator(keys, object); 62 | return hasKeys && Object.keys(object).every((key) => isValidColor(object[key])); 63 | }; 64 | 65 | export const validateRequired = (object, schema) => { 66 | // check if all object properied match the schema and return a new Error for the property that doesn't match the schema 67 | const errors = Object.keys(schema) 68 | .filter((key) => !schema[key](object[key])) 69 | .map((key) => new Error(`Data does not match the schema for property: "${key}"`)); 70 | if (errors.length === 0) return true; 71 | errors.forEach((error) => console.error(error)); 72 | return false; 73 | }; 74 | 75 | const validateOptional = (object, schema) => { 76 | const schemaErrors = Object.keys(object) 77 | .filter((key) => schema.hasOwnProperty(key) && !schema[key](object[key])) 78 | .map((key) => new Error(`Data does not match the schema for property: "${key}"`)); 79 | if (schemaErrors.length === 0) return true; 80 | schemaErrors.forEach((error) => console.error(error)); 81 | return false; 82 | }; 83 | 84 | export const eventSchema = { 85 | date: (value) => Is(value).date(), 86 | title: (value) => Is(value).string(), 87 | description: (value) => Is(value).string() 88 | }; 89 | 90 | export const eventColorTypeSchema = { 91 | type: (value) => Is(value).string(), 92 | color: (value) => /^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/.test(value) 93 | }; 94 | 95 | const themeColorSchema = { 96 | theme_color: (value) => isValidColor(value), 97 | main_background: (value) => isValidColor(value), 98 | active_text_color: (value) => isValidColor(value), 99 | inactive_text_color: (value) => isValidColor(value), 100 | display: (obj) => keyColorValidator(['foreground', 'background'], obj), 101 | picker: (obj) => keyColorValidator(['foreground', 'background'], obj), 102 | picker_header: (obj) => keyColorValidator(['active', 'inactive'], obj), 103 | weekday: (obj) => keyColorValidator(['foreground'], obj), 104 | button: (obj) => { 105 | const objKeys = ['success', 'danger']; 106 | const validObjectKeys = objectKeysValidator(objKeys, obj); 107 | return ( 108 | validObjectKeys && 109 | Object.keys(obj).every((key) => keyColorValidator(['foreground'], obj[key])) 110 | ); 111 | }, 112 | date: (obj) => { 113 | const objKeys = ['active', 'inactive', 'marcked']; 114 | const validObjectKeys = objectKeysValidator(objKeys, obj); 115 | 116 | const chainValidate = Object.keys(obj).every((key) => { 117 | const chainObject = obj[key]; 118 | const chainObjKeys = ['default', 'picked', 'today']; 119 | const validChainObjectKeys = objectKeysValidator(chainObjKeys, chainObject); 120 | if (key === 'marcked') return keyColorValidator(['foreground'], chainObject); 121 | const hasValidColors = Object.keys(chainObject).every((key) => { 122 | const colorTypeArray = key === 'default' ? ['foreground'] : ['foreground', 'background']; 123 | return keyColorValidator(colorTypeArray, chainObject[key]); 124 | }); 125 | return validChainObjectKeys && hasValidColors; 126 | }); 127 | return validObjectKeys && chainValidate; 128 | }, 129 | month_year_preview: (obj) => { 130 | const objKeys = ['active', 'inactive']; 131 | const validObjectKeys = objectKeysValidator(objKeys, obj); 132 | 133 | const chainValidate = Object.keys(obj).every((key) => { 134 | const chainObject = obj[key]; 135 | const chainObjKeys = ['default', 'picked']; 136 | const validChainObjectKeys = objectKeysValidator(chainObjKeys, chainObject); 137 | const hasValidColors = Object.keys(chainObject).every((key) => { 138 | const colorTypeArray = key === 'default' ? ['foreground'] : ['foreground', 'background']; 139 | return keyColorValidator(colorTypeArray, chainObject[key]); 140 | }); 141 | return validChainObjectKeys && hasValidColors; 142 | }); 143 | return validObjectKeys && chainValidate; 144 | } 145 | }; 146 | 147 | const optionsSchema = { 148 | el: (value) => /^[#][-\w]+$/.test(value), 149 | context: (value) => 150 | value.nodeType == Node.ELEMENT_NODE || value.nodeType == Node.DOCUMENT_FRAGMENT_NODE, 151 | dateFormat: (value) => dateFormatValidator(value).isValid(), 152 | bodyType: (value) => { 153 | const types = ['modal', 'inline', 'permanent']; 154 | return types.includes(value); 155 | }, 156 | autoClose: (value) => Is(value).boolean(), 157 | closeOndblclick: (value) => Is(value).boolean(), 158 | closeOnBlur: (value) => Is(value).boolean(), 159 | hideInactiveDays: (value) => Is(value).boolean(), 160 | selectInactiveDays: (value) => Is(value).boolean(), 161 | showCalendarDisplay: (value) => Is(value).boolean(), 162 | customWeekDays: (value) => { 163 | // ['S', 'M', 'T', 'W', 'T', 'F', 'S'] 164 | return ( 165 | Is(value).array() && value.length === 7 && value.every((elem) => /^[^\d\s]{2,}$/.test(elem)) 166 | ); 167 | }, 168 | customMonths: (value) => { 169 | return ( 170 | Is(value).array() && value.length === 12 && value.every((elem) => /^[^\d\s]{2,}$/.test(elem)) 171 | ); 172 | }, 173 | customOkBTN: (value) => Is(value).string(), 174 | customClearBTN: (value) => Is(value).string(), 175 | customCancelBTN: (value) => Is(value).string(), 176 | firstWeekday: (value) => Is(value).number() && /^[0-6]{1}$/.test(value), 177 | selectedDate: (value) => Is(value).date(), 178 | minDate: (value) => Is(value).date(), 179 | maxDate: (value) => Is(value).date(), 180 | jumpToMinMax: (value) => Is(value).boolean(), 181 | jumpOverDisabled: (value) => Is(value).boolean(), 182 | disableWeekends: (value) => Is(value).boolean(), 183 | disableWeekDays: (value) => Is(value).array() && value.every((elem) => /^[0-6]{1}$/.test(elem)), // ex: [0,2,5] accept numbers 0-6; 184 | disableDates: (value) => Is(value).array() && value.every((elem) => Is(elem).date()), // ex: [new Date(2019,11, 25), new Date(2019, 11, 26)] 185 | allowedMonths: (value) => 186 | Is(value).array() && value.length < 12 && value.every((elem) => Is(elem).number() && elem < 12), 187 | allowedYears: (value) => Is(value).array() && value.every((elem) => Is(elem).number()), 188 | disableMonths: (value) => 189 | Is(value).array() && value.length < 12 && value.every((elem) => Is(elem).number() && elem < 12), 190 | disableYears: (value) => Is(value).array() && value.every((elem) => Is(elem).number()), 191 | markDates: (value) => Is(value).array() && value.every((elem) => Is(elem).date()), // ex: [new Date(2019,11, 25), new Date(2019, 11, 26)] 192 | markDatesCustom: (value) => Is(value).func(), // ex: (day) => (date.getDay() === 10) 193 | daterange: (value) => Is(value).boolean(), 194 | theme: (themeObject) => 195 | Is(themeObject).object() && validateOptional(themeObject, themeColorSchema), 196 | events: (value) => { 197 | return ( 198 | Is(value).array() && 199 | value.every((elem) => Is(elem).object() && validateRequired(elem, eventSchema)) 200 | ); 201 | }, 202 | eventColorScheme: (value) => { 203 | return ( 204 | Is(value).array() && 205 | value.every((elem) => Is(elem).object() && validateRequired(elem, eventColorTypeSchema)) 206 | ); 207 | } 208 | }; 209 | 210 | export const validateOptions = (customOptions, defaultOptions) => { 211 | // filter the object kays and return a new Error for the kays that not match the schema 212 | const errors = Object.keys(customOptions) 213 | .filter((key) => !defaultOptions.hasOwnProperty(key)) 214 | .map((key) => new Error(`Property "${key}" is not recognized`)); 215 | // check if the customOption object has the property "el", that is required 216 | if ( 217 | customOptions.hasOwnProperty('allowedMonths') && 218 | customOptions.hasOwnProperty('disableMonths') 219 | ) 220 | errors.unshift( 221 | new Error(`"disableMonths" option cannot be used along with "allowedMonths" option`) 222 | ); 223 | if (customOptions.hasOwnProperty('allowedYears') && customOptions.hasOwnProperty('disableYears')) 224 | errors.unshift( 225 | new Error(`"disableYears" option cannot be used along with "allowedYears" option`) 226 | ); 227 | // check if all object properied match the schema 228 | const schemaErrors = Object.keys(customOptions) 229 | .filter((key) => defaultOptions.hasOwnProperty(key) && !optionsSchema[key](customOptions[key])) 230 | .map((key) => new Error(`Data does not match the schema for property: "${key}"`)); 231 | 232 | if (customOptions.hasOwnProperty('minDate') && customOptions.hasOwnProperty('maxDate')) { 233 | valueOfDate(customOptions.minDate) >= valueOfDate(customOptions.maxDate) && 234 | errors.push(new Error('maxDate should be greater than minDate')); 235 | } 236 | // merge all errors in one array 237 | if (schemaErrors.length > 0) errors.push(...schemaErrors); 238 | // log all errors if the array is not empty 239 | if (errors.length > 0) return errors.forEach((error) => console.error(error)); 240 | // replace the default properties with the custom ones 241 | defaultOptions.context = document.body; 242 | return { ...defaultOptions, ...customOptions }; 243 | }; 244 | -------------------------------------------------------------------------------- /src/scss/_components.buttons.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Button Components */ 3 | /*============================================*/ 4 | 5 | .mc-btn { 6 | display: inline-block; 7 | font-weight: 500; 8 | padding: 0 0.5em; 9 | 10 | &--success { 11 | color: $btn-success-color; 12 | margin-left: 0.5em; 13 | } 14 | 15 | &--danger { 16 | color: $btn-danger-color; 17 | margin-right: 0.5em; 18 | } 19 | 20 | &:active { 21 | transform: scale3d(0.95, 0.95, 0.95); 22 | } 23 | 24 | &:focus { 25 | -webkit-tap-highlight-color: transparent; 26 | -ms-touch-action: manipulation; 27 | touch-action: manipulation; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/scss/_components.calendar.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Calendar Components */ 3 | /*============================================*/ 4 | .mc-calendar { 5 | @include includeColors(); 6 | display: flex; 7 | position: absolute; 8 | top: -100vh; 9 | left: 50vw; 10 | flex-direction: column; 11 | font-family: $calendar-font; 12 | font-weight: 500; 13 | overflow: hidden; 14 | user-select: none; 15 | visibility: hidden; 16 | // background-color: $main-bg-color; 17 | // background-color: $picker-bg-color; 18 | 19 | &:focus { 20 | outline: none; 21 | } 22 | 23 | &--opened { 24 | visibility: visible; 25 | } 26 | 27 | &--inline { 28 | width: 100%; 29 | max-width: 300px; 30 | height: 100%; 31 | max-height: 325px; 32 | background: none; 33 | font-size: 1rem; 34 | box-shadow: 0 0 1px $date-inactive-text-color; 35 | // border: 1px solid $date-inactive-text-color; 36 | border-radius: 10px; 37 | z-index: 1002; 38 | 39 | @include for-desktop { 40 | transform: unset; 41 | } 42 | } 43 | 44 | &--modal { 45 | position: fixed; 46 | width: 100%; 47 | max-width: 425px; 48 | border-radius: 0 0 30px 30px; 49 | box-shadow: 0 10px 10px 5px rgba(0, 0, 0, 0.25); 50 | // background-color: $main-theme-color; 51 | z-index: 1002; 52 | transform: translate(-50%, -65vh); 53 | transform: translate(-50%, -65vh); 54 | transition: transform 0.4s linear 0.2s, visibility 0s linear 0.5s, top 0s linear 0.5s; 55 | 56 | @include for-desktop { 57 | flex-direction: row; 58 | width: auto; 59 | max-width: unset; 60 | height: 400px; 61 | border-radius: 10px; 62 | // background-color: unset; 63 | box-shadow: 0 0 30px rgba(0, 0, 0, 0.3); 64 | opacity: 0; 65 | transform: translate(-50%, -50%) scale(0.9); 66 | transition: transform 0.3s, opacity 0.3s, visibility 0s linear 0.3s, top 0s linear 0.3s; 67 | } 68 | 69 | &#{$opened} { 70 | top: 0; 71 | transform: translate(-50%, 0); 72 | transition: unset; 73 | animation: slide-down 0.3s linear; 74 | 75 | @include for-desktop { 76 | top: 50vh; 77 | opacity: 1; 78 | visibility: visible; 79 | height: 95vh; 80 | max-height: 400px; 81 | transform: translate(-50%, -50%) scale(1); 82 | @include popup(); 83 | } 84 | } 85 | } 86 | &--permanent { 87 | position: relative; 88 | top: 0; 89 | left: 0; 90 | width: 100%; 91 | height: 100%; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/scss/_components.date.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Date Components */ 3 | /*============================================*/ 4 | 5 | .mc-date { 6 | $root: &; 7 | text-align: center; 8 | border-radius: 5px; 9 | font-weight: 300; 10 | width: calc(100% / 7); 11 | cursor: default; 12 | 13 | &--active { 14 | // cursor: pointer; 15 | color: $date-active-text-color; 16 | 17 | &#{$root}--today { 18 | color: $date-active-today-text-color; 19 | background-color: $date-active-today-bg-color; 20 | } 21 | &#{$root}--picked { 22 | color: $date-active-pick-text-color; 23 | background-color: $date-active-pick-bg-color; 24 | } 25 | } 26 | 27 | &--invisible { 28 | visibility: hidden; 29 | } 30 | 31 | &--selectable { 32 | cursor: pointer; 33 | } 34 | 35 | &--inactive { 36 | color: $date-inactive-text-color; 37 | // cursor: default; 38 | 39 | &#{$root}--today { 40 | box-shadow: 0 0 0 1px $date-inactive-today-bg-color; 41 | color: $date-inactive-today-text-color; 42 | box-shadow: none; 43 | } 44 | 45 | &#{$root}--picked { 46 | box-shadow: 0 0 0 1px $date-inactive-pick-bg-color; 47 | color: $date-inactive-pick-text-color; 48 | // box-shadow: none; 49 | } 50 | } 51 | 52 | &--marked { 53 | color: $date-marked-text-color; 54 | font-weight: 500; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/scss/_components.display.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Display Components */ 3 | /*============================================*/ 4 | 5 | .mc-display { 6 | $root: &; 7 | display: none; 8 | color: $display-text-color; 9 | background-color: $display-bg-color; 10 | 11 | #{$modal} & { 12 | display: flex; 13 | flex-direction: column; 14 | max-height: 0; 15 | transition: max-height 0.2s linear; 16 | padding: 0 0 2em; 17 | margin: 0 0 -2em; 18 | @include for-desktop { 19 | width: 200px; 20 | height: 100%; 21 | max-height: unset; 22 | background-color: $display-bg-color; 23 | padding: 0 1em 0 0; 24 | margin: 0 -1em 0 0; 25 | } 26 | } 27 | 28 | #{$modal}#{$opened} & { 29 | max-height: 210px; 30 | animation: stretch 0.4s; 31 | 32 | @include for-desktop { 33 | transition: unset; 34 | max-height: unset; 35 | animation: unset; 36 | } 37 | } 38 | 39 | &__body { 40 | display: flex; 41 | justify-content: space-between; 42 | align-items: center; 43 | padding: 0.5em 0; 44 | 45 | @include for-desktop { 46 | flex-direction: column; 47 | height: 100%; 48 | padding: 0; 49 | } 50 | } 51 | 52 | &__header { 53 | background-color: $display-bg-color-darker; 54 | #{$root}[data-target='month'] &, 55 | #{$root}[data-target='year'] & { 56 | @include for-desktop { 57 | display: none; 58 | } 59 | } 60 | } 61 | 62 | &__day { 63 | text-align: center; 64 | @include font-size-clamp-polyfill(1rem, 8, 1.25rem); 65 | line-height: 1; 66 | padding: 0.5em 0; 67 | #{$root}[data-target='month'] &, 68 | #{$root}[data-target='year'] & { 69 | visibility: hidden; 70 | } 71 | @include for-desktop { 72 | padding: 1em 0; 73 | } 74 | } 75 | 76 | &__data { 77 | display: flex; 78 | width: 50%; 79 | 80 | @include for-desktop { 81 | width: 100%; 82 | height: 50%; 83 | text-align: center; 84 | } 85 | 86 | &--primary { 87 | justify-content: flex-end; 88 | #{$root}[data-target='month'] &, 89 | #{$root}[data-target='year'] & { 90 | display: none; 91 | } 92 | 93 | @include for-desktop { 94 | justify-content: center; 95 | align-items: flex-end; 96 | } 97 | } 98 | &--secondary { 99 | flex-direction: column; 100 | #{$root}[data-target='month'] &, 101 | #{$root}[data-target='year'] & { 102 | width: 100%; 103 | text-align: center; 104 | @include for-desktop { 105 | justify-content: center; 106 | height: 100%; 107 | } 108 | } 109 | @include for-desktop { 110 | justify-content: space-between; 111 | } 112 | } 113 | } 114 | 115 | &__date { 116 | @include font-size-clamp-polyfill(5rem, 40, 7rem); 117 | line-height: 1; 118 | } 119 | 120 | &__month { 121 | @include font-size-clamp-polyfill(1.2rem, 9, 1.5rem); 122 | #{$root}[data-target='year'] & { 123 | display: none; 124 | } 125 | @include for-desktop { 126 | height: auto; 127 | } 128 | } 129 | 130 | &__year { 131 | @include font-size-clamp-polyfill(2.4rem, 18, 3rem); 132 | line-height: 0.8; 133 | #{$root}[data-target='year'] & { 134 | padding: 0.3em 0; 135 | } 136 | @include for-desktop { 137 | height: auto; 138 | padding: 0.5em 0; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/scss/_components.month_year_preview.scss: -------------------------------------------------------------------------------- 1 | .mc-month-year { 2 | &__preview { 3 | position: absolute; 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-evenly; 7 | align-items: center; 8 | top: 0; 9 | left: 0; 10 | height: 100%; 11 | width: 90%; 12 | margin: 0 5%; 13 | overflow: hidden; 14 | visibility: hidden; 15 | opacity: 0; 16 | background-color: $picker-bg-color; 17 | 18 | &--opened { 19 | visibility: visible; 20 | opacity: 1; 21 | } 22 | } 23 | &__cell { 24 | $root: &; 25 | position: relative; 26 | display: flex; 27 | justify-content: center; 28 | align-items: center; 29 | width: 30%; 30 | height: 20%; 31 | text-align: center; 32 | border-radius: 10px; 33 | cursor: pointer; 34 | color: $prev-cell-active-text-color; 35 | 36 | &::after { 37 | content: ''; 38 | position: absolute; 39 | top: 0; 40 | left: 0; 41 | right: 0; 42 | bottom: 0; 43 | opacity: 0; 44 | border-radius: 10px; 45 | } 46 | 47 | &--picked { 48 | color: $prev-cell-active-pick-text-color; 49 | background-color: $prev-cell-active-pick-bg-color; 50 | } 51 | &--inactive { 52 | color: $prev-cell-inactive-text-color; 53 | cursor: default; 54 | &#{$root}--picked { 55 | color: $prev-cell-inactive-pick-text-color; 56 | box-shadow: 0 0 0 1px $prev-cell-inactive-pick-bg-color; 57 | background-color: transparent; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/scss/_components.picker.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Picker Components */ 3 | /*============================================*/ 4 | 5 | .mc-picker { 6 | display: flex; 7 | flex-direction: column; 8 | width: 100%; 9 | height: 100%; 10 | background-color: $picker-bg-color; 11 | color: $picker-text-color; 12 | 13 | #{$modal} & { 14 | height: 65vh; 15 | max-height: 400px; 16 | border-radius: 30px 30px 0 0; 17 | @include for-desktop { 18 | justify-content: center; 19 | align-items: flex-end; 20 | height: 100%; 21 | width: 425px; 22 | border-radius: unset; 23 | } 24 | } 25 | 26 | #{$inline} & { 27 | border-radius: unset; 28 | } 29 | 30 | &__header { 31 | display: flex; 32 | @include font-size-clamp-polyfill(1rem, 8, 1.25rem); 33 | justify-content: center; 34 | padding: 1em 0 0.5em; 35 | color: $picker-header-active-color; 36 | #{$modal} & { 37 | @include for-desktop { 38 | padding: 0.7em 0; 39 | justify-content: space-between; 40 | } 41 | } 42 | 43 | #{$inline} & { 44 | font-size: 1rem; 45 | padding: 0.7em 0 0; 46 | } 47 | 48 | .icon-angle { 49 | height: calc(8vw + 0.25rem); 50 | min-height: 1.75rem; 51 | max-height: 2rem; 52 | } 53 | } 54 | 55 | &__body { 56 | position: relative; 57 | flex: 1 0; 58 | width: 100%; 59 | } 60 | 61 | &__footer { 62 | display: flex; 63 | justify-content: space-between; 64 | padding: 1em 0 2em; 65 | 66 | #{$inline} & { 67 | padding: 0.5em 0 1em; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/scss/_components.selector.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Selector Components */ 3 | /*============================================*/ 4 | 5 | .mc-select { 6 | $root: &; 7 | 8 | &[data-target='year'] { 9 | #{$root}__month { 10 | display: none; 11 | } 12 | #{$root}__year { 13 | width: 100%; 14 | #{$root}__data--year { 15 | width: 75%; 16 | max-width: unset; 17 | min-width: unset; 18 | justify-content: center; 19 | } 20 | #{$root}__nav { 21 | display: flex; 22 | position: relative; 23 | } 24 | } 25 | } 26 | 27 | &__month, 28 | &__year { 29 | display: flex; 30 | align-items: center; 31 | } 32 | &__nav { 33 | display: flex; 34 | outline: revert; 35 | align-items: center; 36 | position: absolute; 37 | text-decoration: none; 38 | color: $picker-header-active-color; 39 | padding: 0 1em; 40 | @include for-desktop { 41 | #{$modal} & { 42 | position: relative; 43 | } 44 | } 45 | 46 | &:focus { 47 | -webkit-tap-highlight-color: transparent; 48 | -ms-touch-action: manipulation; 49 | touch-action: manipulation; 50 | } 51 | 52 | &--inactive { 53 | color: $picker-header-inactive-color; 54 | cursor: default; 55 | &:active { 56 | transform: none !important; 57 | } 58 | } 59 | 60 | &--prev, 61 | &--next { 62 | transition: transform 0.2s ease-in-out; 63 | } 64 | &--prev { 65 | &:active { 66 | transform: translateX(-0.5em); 67 | } 68 | #{$inline} #{$root}__month &, 69 | & { 70 | left: 0; 71 | } 72 | } 73 | &--next { 74 | &:active { 75 | transform: translateX(0.5em); 76 | } 77 | #{$inline} #{$root}__month &, 78 | & { 79 | right: 0; 80 | } 81 | } 82 | 83 | #{$root}__year & { 84 | #{$inline} &, 85 | #{$permanent} &, 86 | & { 87 | display: none; 88 | } 89 | @include for-desktop { 90 | display: flex; 91 | } 92 | } 93 | } 94 | &__data { 95 | display: flex; 96 | align-items: center; 97 | @include font-size-clamp-polyfill(1rem, 8, 1.25rem); 98 | height: calc(8vw + 0.25rem); 99 | min-height: 1.75rem; 100 | max-height: 2rem; 101 | overflow: hidden; 102 | position: relative; 103 | cursor: pointer; 104 | 105 | &::after { 106 | content: ''; 107 | position: absolute; 108 | top: 0; 109 | left: 0; 110 | right: 0; 111 | bottom: 0; 112 | opacity: 0; 113 | } 114 | 115 | span { 116 | line-height: 1.2; 117 | text-align: center; 118 | position: absolute; 119 | #{$root}[data-target='year'] & { 120 | position: relative; 121 | } 122 | } 123 | 124 | &--month { 125 | &, 126 | span { 127 | width: 40vw; 128 | min-width: 5rem; 129 | max-width: 6.25rem; 130 | } 131 | #{$inline} & { 132 | &, 133 | span { 134 | width: 6.4rem; 135 | } 136 | } 137 | } 138 | 139 | &--year { 140 | &, 141 | span { 142 | width: 22vw; 143 | min-width: 3rem; 144 | max-width: 3.5rem; 145 | } 146 | #{$inline} & { 147 | &, 148 | span { 149 | width: 3.2rem; 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | .slide { 157 | &-right { 158 | &--in { 159 | animation: slide-right-in 200ms ease; 160 | } 161 | &--out { 162 | animation: slide-right-out 200ms ease; 163 | } 164 | } 165 | &-left { 166 | &--in { 167 | animation: slide-left-in 200ms ease; 168 | } 169 | &--out { 170 | animation: slide-left-out 200ms ease; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/scss/_components.table.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Table Components */ 3 | /*============================================*/ 4 | 5 | .mc-table { 6 | height: 100%; 7 | border-collapse: unset; 8 | #{$modal} & { 9 | @include for-desktop { 10 | border-top: none; 11 | } 12 | } 13 | 14 | #{$inline} & { 15 | border-top: none; 16 | } 17 | &__weekday { 18 | text-align: center; 19 | padding: 0.5em 0; 20 | color: $weekday-text-color; 21 | width: calc(100% / 7); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/scss/_elements.all.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* HTML Elements */ 3 | /*============================================*/ 4 | 5 | /* Styling for bare HTML elements (like H1, A, header, footer, …). 6 | These come with default styling from the browser so we must to redefine them here. */ 7 | 8 | .mc-calendar { 9 | h1, 10 | h2, 11 | h3 { 12 | font-weight: 500; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/scss/_generic.resets.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* CSS Resets */ 3 | /*============================================*/ 4 | 5 | .mc-calendar { 6 | &, 7 | * { 8 | line-height: 1.2; 9 | &, 10 | &::before, 11 | &::after { 12 | box-sizing: border-box; 13 | margin: 0; 14 | padding: 0; 15 | } 16 | } 17 | } 18 | .mc-btn, 19 | .mc-select__nav { 20 | background: none; 21 | border: 0; 22 | cursor: pointer; 23 | font: inherit; 24 | line-height: normal; 25 | overflow: visible; 26 | padding: 0; 27 | &::-moz-focus-inner { 28 | border: 0; 29 | padding: 0; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/scss/_objects.layout.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Layout */ 3 | /*============================================*/ 4 | 5 | .mc-container { 6 | position: relative; 7 | margin: 0 auto; 8 | width: 90%; 9 | } 10 | -------------------------------------------------------------------------------- /src/scss/_settings.animations.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Transition Variables */ 3 | /*============================================*/ 4 | $transition-duration-m: 0.7s; 5 | $transition-duration-s: 0.3s; 6 | $transition-duration-n: 0s; 7 | 8 | $transition-delay-m: 0.5s; 9 | $transition-delay-s: 0.3s; 10 | $transition-delay-n: 0s; 11 | 12 | $easing: linear; 13 | -------------------------------------------------------------------------------- /src/scss/_settings.breakpoints.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Breakpoints Variables */ 3 | /*============================================*/ 4 | 5 | $bkp-mobile: 625px; 6 | -------------------------------------------------------------------------------- /src/scss/_settings.colors.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Color Variables */ 3 | /*============================================*/ 4 | 5 | $main-bg-color: #{var(--mc-main-bg)}; 6 | $main-theme-color: #{var(--mc-theme-color)}; 7 | $active-text-color: #{var(--mc-active-text-color)}; 8 | $inactive-text-color: #{var(--mc-inactive-text-color)}; 9 | 10 | $display-text-color: #{var(--mc-display-foreground)}; 11 | $display-bg-color: #{var(--mc-display-background)}; 12 | $display-bg-color-darker: rgba(0, 0, 0, 0.2); 13 | 14 | $picker-text-color: #{var(--mc-picker-foreground)}; 15 | $picker-bg-color: #{var(--mc-picker-background)}; 16 | 17 | $picker-header-active-color: #{var(--mc-picker-header-active)}; 18 | $picker-header-inactive-color: #{var(--mc-picker-header-inactive)}; 19 | 20 | $weekday-text-color: #{var(--mc-weekday-foreground)}; 21 | 22 | $date-active-text-color: #{var(--mc-date-active-def-foreground)}; 23 | $date-active-pick-text-color: #{var(--mc-date-active-pick-foreground)}; 24 | $date-active-pick-bg-color: #{var(--mc-date-active-pick-background)}; 25 | $date-active-today-text-color: #{var(--mc-date-active-today-foreground)}; 26 | $date-active-today-bg-color: #{var(--mc-date-active-today-background)}; 27 | // 28 | $date-inactive-text-color: #{var(--mc-date-inactive-def-foreground)}; 29 | $date-inactive-pick-text-color: #{var(--mc-date-inactive-pick-foreground)}; 30 | $date-inactive-pick-bg-color: #{var(--mc-date-inactive-pick-background)}; 31 | $date-inactive-today-text-color: #{var(--mc-date-inactive-today-foreground)}; 32 | $date-inactive-today-bg-color: #{var(--mc-date-inactive-today-background)}; 33 | $date-marked-text-color: #{var(--mc-date-marcked-foreground)}; 34 | 35 | $prev-cell-active-text-color: #{var(--mc-prev-active-def-foreground)}; 36 | $prev-cell-active-pick-text-color: #{var(--mc-prev-active-pick-foreground)}; 37 | $prev-cell-active-pick-bg-color: #{var(--mc-prev-active-pick-background)}; 38 | // 39 | $prev-cell-inactive-text-color: #{var(--mc-prev-inactive-def-foreground)}; 40 | $prev-cell-inactive-pick-text-color: #{var(--mc-prev-inactive-pick-foreground)}; 41 | $prev-cell-inactive-pick-bg-color: #{var(--mc-prev-inactive-pick-background)}; 42 | 43 | $btn-success-color: #{var(--mc-btn-success-foreground)}; 44 | $btn-danger-color: #{var(--mc-btn-danger-foreground)}; 45 | -------------------------------------------------------------------------------- /src/scss/_settings.layout.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | $modal: '.mc-calendar--modal'; 4 | $inline: '.mc-calendar--inline'; 5 | $permanent: '.mc-calendar--permanent'; 6 | $opened: '.mc-calendar--opened'; -------------------------------------------------------------------------------- /src/scss/_settings.typography.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Typography Variables */ 3 | /*============================================*/ 4 | 5 | @import url('https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;500;600;700;800;900&display=swap'); 6 | 7 | $calendar-font: 'Maven Pro', sans-serif; 8 | 9 | $font-weight-light: 300; 10 | $font-weight-regular: 300; 11 | $font-weight-medium: 500; 12 | -------------------------------------------------------------------------------- /src/scss/_tools.mixins.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Mixins */ 3 | /*============================================*/ 4 | 5 | @mixin includeColors { 6 | --mc-theme-color: #38ada9; 7 | --mc-main-bg: #f5f5f6; 8 | --mc-active-text-color: rgb(0, 0, 0); 9 | --mc-inactive-text-color: rgba(0, 0, 0, 0.8); 10 | // 11 | --mc-display-foreground: rgba(255, 255, 255, 0.8); 12 | --mc-display-background: #38ada9; 13 | // 14 | --mc-picker-foreground: rgb(0, 0, 0); 15 | --mc-picker-background: #f5f5f6; 16 | 17 | --mc-picker-header-active: #818181; 18 | --mc-picker-header-inactive: rgba(0, 0, 0, 0.2); 19 | // 20 | --mc-weekday-foreground: #38ada9; 21 | // 22 | --mc-btn-success-foreground: #38ada9; 23 | --mc-btn-danger-foreground: #e65151; 24 | // 25 | --mc-date-active-def-foreground: rgb(0, 0, 0); 26 | --mc-date-active-pick-foreground: #ffffff; 27 | --mc-date-active-pick-background: #38ada9; 28 | --mc-date-active-today-foreground: rgb(0, 0, 0); 29 | --mc-date-active-today-background: rgba(0, 0, 0, 0.2); 30 | // 31 | --mc-date-inactive-def-foreground: rgba(0, 0, 0, 0.2); 32 | --mc-date-inactive-pick-foreground: #38ada9; 33 | --mc-date-inactive-pick-background: #38ada9; 34 | --mc-date-inactive-today-foreground: rgba(0, 0, 0, 0.2); 35 | --mc-date-inactive-today-background: rgba(0, 0, 0, 0.2); 36 | --mc-date-marcked-foreground: #38ada9; 37 | // 38 | --mc-prev-active-def-foreground: rgb(0, 0, 0); 39 | --mc-prev-active-pick-foreground: rgb(0, 0, 0); 40 | --mc-prev-active-pick-background: rgba(0, 0, 0, 0.2); 41 | // 42 | --mc-prev-inactive-def-foreground: rgba(0, 0, 0, 0.2); 43 | --mc-prev-inactive-pick-foreground: rgba(0, 0, 0, 0.2); 44 | --mc-prev-inactive-pick-background: rgba(0, 0, 0, 0.2); 45 | } 46 | 47 | @mixin for-desktop { 48 | @media (min-width: $bkp-mobile) { 49 | @content; 50 | } 51 | } 52 | 53 | @mixin font-size-clamp-polyfill($min, $middle, $max) { 54 | $display-min: $max * 100 / $middle; 55 | 56 | $display-max: $min * 100 / $middle; 57 | 58 | @supports not (font-size: clamp(#{$min}, #{$middle}vw, #{$max})) { 59 | font-size: #{$middle}vw; 60 | 61 | @media (max-width: $display-max) { 62 | font-size: $min; 63 | } 64 | 65 | @media (min-width: $display-min) { 66 | font-size: $max; 67 | } 68 | } 69 | 70 | @supports (font-size: clamp(#{$min}, #{$middle}vw, #{$max})) { 71 | font-size: clamp(#{$min}, #{$middle}vw, #{$max}); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/scss/_tools.transitions.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Transitions */ 3 | /*============================================*/ 4 | 5 | @keyframes slide-left { 6 | 0% { 7 | transform: translateX(0); 8 | } 9 | 10 | 100% { 11 | transform: translateX(-0.5em); 12 | } 13 | } 14 | @keyframes slide-right { 15 | 0% { 16 | transform: translateX(0); 17 | } 18 | 19 | 100% { 20 | transform: translateX(0.5em); 21 | } 22 | } 23 | @keyframes slide-down { 24 | 0% { 25 | transform: translate(-50%, -400px); 26 | } 27 | 28 | 100% { 29 | transform: translate(-50%, 0); 30 | } 31 | } 32 | 33 | @keyframes zoom-in { 34 | 0% { 35 | transform: translate(-50%, -50%) scale(0.9); 36 | } 37 | 100% { 38 | transform: translate(-50%, -50%) scale(1); 39 | } 40 | } 41 | 42 | @keyframes stretch { 43 | 0% { 44 | max-height: 0; 45 | } 46 | 50% { 47 | max-height: 0; 48 | } 49 | 50 | 100% { 51 | max-height: 175px; 52 | } 53 | } 54 | 55 | @mixin slide($left: true) { 56 | @if $left { 57 | animation: slide-left 0.2s linear; 58 | } @else { 59 | animation: slide-right 0.2s linear; 60 | } 61 | } 62 | 63 | @mixin popup($in: true) { 64 | @if $in { 65 | animation: zoom-in 0.1s linear; 66 | } @else { 67 | animation: zoom-in 0.1s linear reverse; 68 | } 69 | } 70 | 71 | @keyframes slide-left-in { 72 | from { 73 | transform: translateX(100px); 74 | } 75 | to { 76 | transform: translateX(0px); 77 | } 78 | } 79 | 80 | @keyframes slide-left-out { 81 | from { 82 | transform: translateX(0px); 83 | } 84 | to { 85 | transform: translateX(-100px); 86 | } 87 | } 88 | 89 | @keyframes slide-right-in { 90 | from { 91 | transform: translateX(-100px); 92 | } 93 | to { 94 | transform: translateX(0px); 95 | } 96 | } 97 | 98 | @keyframes slide-right-out { 99 | from { 100 | transform: translateX(0px); 101 | } 102 | to { 103 | transform: translateX(100px); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/scss/_utilities.display.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Display Utility */ 3 | /*============================================*/ 4 | 5 | .u-display-none { 6 | @include for-desktop { 7 | display: none !important; 8 | } 9 | } 10 | 11 | .mc-picker-vhidden { 12 | border: 0; 13 | clip: rect(1px, 1px, 1px, 1px); 14 | height: 1px; 15 | overflow: hidden; 16 | padding: 0; 17 | position: absolute; 18 | top: 0; 19 | width: 1px; 20 | } 21 | -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | /*============================================*/ 2 | /* Main */ 3 | /*============================================*/ 4 | 5 | // SETTINGS 6 | @import 'settings.typography'; 7 | @import 'settings.layout'; 8 | @import 'settings.colors'; 9 | @import 'settings.breakpoints'; 10 | @import 'settings.animations'; 11 | // TOOLS 12 | @import 'tools.mixins'; 13 | @import 'tools.transitions'; 14 | 15 | // GENERIC 16 | @import 'generic.resets'; 17 | 18 | // ELEMENTS 19 | @import 'elements.all'; 20 | 21 | // OBJECTS 22 | @import 'objects.layout'; 23 | 24 | // COMPONENTS 25 | @import 'components.calendar'; 26 | @import 'components.display'; 27 | @import 'components.picker'; 28 | @import 'components.selector'; 29 | @import 'components.date'; 30 | @import 'components.table'; 31 | @import 'components.month_year_preview'; 32 | @import 'components.buttons'; 33 | 34 | // UTILITIES 35 | @import 'utilities.display'; 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 4 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const TerserPlugin = require('terser-webpack-plugin'); 7 | 8 | module.exports = (env, argv) => { 9 | console.log(env); 10 | let entry = env.prod ? './src/js/mc-calendar.js' : './sandbox/index.js'; 11 | // env.prod ? entry : (entry = ['./demo/index.js', './src/css/mc-calendar.css', './demo/style.css']); 12 | const outputFilename = env.prod ? 'mc-calendar.min' : 'bundle'; 13 | return { 14 | mode: env.prod ? 'production' : 'development', 15 | target: 'web', 16 | entry: entry, 17 | output: env.prod 18 | ? { 19 | path: path.resolve(__dirname, 'dist'), 20 | library: 'MCDatepicker', 21 | libraryExport: 'default', 22 | libraryTarget: 'umd', 23 | filename: `${outputFilename}.js`, 24 | publicPath: '' 25 | } 26 | : {}, 27 | devServer: { 28 | open: true, 29 | disableHostCheck: true, 30 | contentBase: path.resolve(__dirname, './sandbox'), 31 | port: 9000, 32 | public: 'http://localhost:9000' 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.s?css$/, 38 | use: [ 39 | MiniCssExtractPlugin.loader, 40 | 'css-loader', 41 | { 42 | loader: 'postcss-loader', 43 | options: { 44 | postcssOptions: { 45 | plugins: [ 46 | [ 47 | 'autoprefixer', 48 | { 49 | // Options 50 | } 51 | ] 52 | ] 53 | } 54 | } 55 | }, 56 | 'sass-loader' 57 | ] 58 | }, 59 | // { 60 | // test: /\.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|)$/i, 61 | // use: 'file-loader' 62 | // }, 63 | { 64 | test: /\.js$/, 65 | exclude: /node_modules/, 66 | use: ['babel-loader'] 67 | } 68 | ] 69 | }, 70 | optimization: { 71 | minimize: !!env.prod, 72 | minimizer: [ 73 | new CssMinimizerPlugin(), 74 | new TerserPlugin({ 75 | extractComments: true, 76 | // cache: true, // https://goo.gl/QVWRtq 77 | parallel: true, //https://goo.gl/hUkvnK 78 | terserOptions: { 79 | // https://goo.gl/y3psR1 80 | ecma: 5, 81 | parse: { 82 | html5_comments: false 83 | }, 84 | format: { 85 | comments: false 86 | } 87 | } 88 | }) 89 | ] 90 | }, 91 | plugins: [ 92 | // new HtmlWebpackPlugin({ 93 | // template: './src/template.html' 94 | // }), 95 | new MiniCssExtractPlugin({ 96 | filename: `${outputFilename}.css` 97 | }), 98 | !env.prod && 99 | new HtmlWebpackPlugin({ 100 | template: path.resolve(__dirname, 'sandbox/template.html'), 101 | title: 'Datepicker Sandbox' 102 | }), 103 | new CleanWebpackPlugin() 104 | ].filter(Boolean) 105 | }; 106 | }; 107 | --------------------------------------------------------------------------------