├── .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
\n
\n
1
\n\n
\n
January
\n1970
\n\n
\n
\n\n\n
\n
\n\n\n\n28 | \n29 | \n30 | \n31 | \n1 | \n2 | \n3 | \n
\n\n4 | \n5 | \n6 | \n7 | \n8 | \n9 | \n10 | \n
\n\n11 | \n12 | \n13 | \n14 | \n15 | \n16 | \n17 | \n
\n\n18 | \n19 | \n20 | \n21 | \n22 | \n23 | \n24 | \n
\n\n25 | \n26 | \n27 | \n28 | \n29 | \n30 | \n31 | \n
\n\n1 | \n2 | \n3 | \n4 | \n5 | \n6 | \n7 | \n
\n\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\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 |
48 |
55 |
62 |
69 |
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 |
10 |
11 |
12 |
1
13 |
14 |
15 |
January
16 | 1970
17 |
18 |
19 |
20 |
21 |
54 |
55 |
56 |
67 |
68 |
69 | 28 |
70 | 29 |
71 | 30 |
72 | 31 |
73 | 1 |
74 | 2 |
75 | 3 |
76 |
77 |
78 | 4 |
79 | 5 |
80 | 6 |
81 | 7 |
82 | 8 |
83 | 9 |
84 | 10 |
85 |
86 |
87 | 11 |
88 | 12 |
89 | 13 |
90 | 14 |
91 | 15 |
92 | 16 |
93 | 17 |
94 |
95 |
96 | 18 |
97 | 19 |
98 | 20 |
99 | 21 |
100 | 22 |
101 | 23 |
102 | 24 |
103 |
104 |
105 | 25 |
106 | 26 |
107 | 27 |
108 | 28 |
109 | 29 |
110 | 30 |
111 | 31 |
112 |
113 |
114 | 1 |
115 | 2 |
116 | 3 |
117 | 4 |
118 | 5 |
119 | 6 |
120 | 7 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
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 |
--------------------------------------------------------------------------------