├── .commitlintrc.js ├── .eslintrc.json ├── .flowconfig ├── .gitignore ├── .snyk ├── .tool-versions ├── .travis.yml ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── plugins │ ├── index.js │ └── main.js ├── rollup.config.js └── support │ ├── commands.js │ ├── commands │ ├── mount.js │ └── standalone.html │ └── index.js ├── deploy.sh ├── docs ├── .vuepress │ ├── components │ │ ├── attributes-event-listeners.vue │ │ ├── base.js │ │ ├── basic-example.vue │ │ ├── manual-trigger.vue │ │ └── pikaday-options.vue │ ├── config.js │ ├── public │ │ └── vue-pikaday.png │ └── styles │ │ └── index.styl ├── README.md ├── config │ └── README.md └── guide │ ├── README.md │ ├── installation.md │ ├── usage.md │ └── usage │ ├── attributes-event-listeners.md │ ├── basic-example.md │ ├── manual-trigger.md │ ├── pikaday-options.md │ └── registration.md ├── package.json ├── rollup.config.js ├── src ├── component.js ├── directives.js └── index.js ├── test └── specs │ ├── attributes-event-listeners.js │ ├── basic.js │ ├── custom-formating.js │ ├── external-date-change.js │ ├── manual-trigger.js │ └── pikaday-options.js └── yarn.lock /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'] 3 | }; 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true, 6 | "mocha": true 7 | }, 8 | "extends": [ 9 | "plugin:flowtype/recommended", 10 | "eslint:recommended" 11 | ], 12 | "plugins": [ 13 | "flowtype" 14 | ], 15 | "parserOptions": { 16 | "sourceType": "module" 17 | }, 18 | "rules": { 19 | "indent": [ 20 | "error", 21 | 2 22 | ], 23 | "linebreak-style": [ 24 | "error", 25 | "unix" 26 | ], 27 | "quotes": [ 28 | "error", 29 | "single" 30 | ], 31 | "semi": [ 32 | "error", 33 | "always" 34 | ] 35 | }, 36 | "globals": { 37 | "expect": true, 38 | "Cypress": true, 39 | "cy": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | .coverage 4 | .envrc 5 | *.log 6 | cypress/screenshots 7 | cypress/videos 8 | dist 9 | docs/.vuepress/dist 10 | .DS_Store 11 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.14.1 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | SNYK-JS-LODASH-450202: 7 | - lodash: 8 | patched: '2019-07-03T23:00:54.035Z' 9 | SNYK-JS-LODASH-567746: 10 | - lodash: 11 | patched: '2020-04-30T22:28:49.705Z' 12 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | nodejs 13.3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: 4 | yarn: true 5 | directories: 6 | - ~/.cache 7 | - node_modules 8 | 9 | addons: 10 | apt: 11 | packages: 12 | # Ubuntu 16+ does not install this dependency by default, so we need to install it ourselves 13 | - libgconf-2-4 14 | 15 | notifications: 16 | email: false 17 | 18 | sudo: false 19 | 20 | jobs: 21 | include: 22 | - stage: lint 23 | node_js: '13' 24 | script: commitlint-travis 25 | 26 | - &test 27 | node_js: '13' 28 | stage: test 29 | install: yarn 30 | script: yarn test 31 | - <<: *test 32 | node_js: '12' 33 | - <<: *test 34 | node_js: '11' 35 | - <<: *test 36 | node_js: '10' 37 | 38 | - stage: npm release 39 | node_js: '13' 40 | before_install: yarn global add greenkeeper-lockfile@1 41 | before_script: greenkeeper-lockfile-update 42 | script: 43 | - yarn build 44 | - yarn semantic-release 45 | after_script: greenkeeper-lockfile-upload 46 | 47 | branches: 48 | except: 49 | - /^v\d+\.\d+\.\d+$/ 50 | 51 | stages: 52 | - lint 53 | - test 54 | - name: npm release 55 | if: branch = master 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 - present Enrian Partners a.s. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue Pikaday 2 | 3 | [![npm](https://img.shields.io/npm/v/npm.svg)](https://www.npmjs.com/package/@netcz/vue-pikaday) 4 | [![npm](https://img.shields.io/npm/l/express.svg)](https://www.npmjs.com/package/@netcz/vue-pikaday) 5 | [![Build Status](https://travis-ci.com/NetCZ/vue-pikaday.svg?branch=master)](https://travis-ci.com/NetCZ/vue-pikaday) 6 | [![Known Vulnerabilities](https://snyk.io/test/github/netcz/vue-pikaday/badge.svg)](https://snyk.io/test/github/netcz/vue-pikaday) 7 | 8 | VueJS wrapper component for Pikaday datepicker 9 | 10 | ## Install 11 | ```bash 12 | npm i -D @netcz/vue-pikaday 13 | ``` 14 | or 15 | ```bash 16 | yarn add -D @netcz/vue-pikaday 17 | ``` 18 | 19 | ## Usage 20 | 21 | Please consult [documentation](https://netcz.github.io/vue-pikaday). 22 | 23 | ## Contribution 24 | 25 | Feel free to create issue for bugs or pull request with fixes / enhancements. 26 | 27 | ## License 28 | 29 | [MIT](https://opensource.org/licenses/MIT) 30 | 31 | Copyright (c) 2018 - present Michal Szajter 32 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixturesFolder": false, 3 | "integrationFolder": "test/specs", 4 | "projectId": "o8hy27" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | require('@babel/register')({ 15 | presets: ['@babel/preset-env'] 16 | }); 17 | module.exports = require('./main').default; 18 | -------------------------------------------------------------------------------- /cypress/plugins/main.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import rollup from '@cypress/rollup-preprocessor'; 3 | 4 | export default (on) => { 5 | on('file:preprocessor', rollup(path.resolve('cypress/rollup.config'))); 6 | }; 7 | -------------------------------------------------------------------------------- /cypress/rollup.config.js: -------------------------------------------------------------------------------- 1 | import camelCase from 'lodash/camelCase'; 2 | 3 | import babel from 'rollup-plugin-babel'; 4 | import resolve from 'rollup-plugin-node-resolve'; 5 | import commonjs from 'rollup-plugin-commonjs'; 6 | import { terser } from 'rollup-plugin-terser'; 7 | import html from 'rollup-plugin-html'; 8 | 9 | import { scope, name as nameWithScope } from '../package.json'; 10 | 11 | const name = nameWithScope.replace(`@${scope}/`, ''); 12 | 13 | const debug = process.env.DEBUG === 'true'; 14 | 15 | export default { 16 | plugins: [ 17 | resolve({ external: ['vue'] }), 18 | html({ 19 | include: '**/*.html' 20 | }), 21 | babel({ 22 | presets: [ 23 | ['@babel/preset-env', { 'modules': false }], 24 | '@babel/preset-flow' 25 | ], 26 | exclude: 'node_modules/**' 27 | }), 28 | commonjs({ 29 | include: 'node_modules/**' 30 | }), 31 | terser({ 32 | compress: { 33 | drop_console: !debug, 34 | drop_debugger: !debug 35 | } 36 | }) 37 | ], 38 | output: { 39 | format: 'umd', 40 | name: camelCase(name), 41 | sourcemap: 'inline' 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | import './commands/mount'; 12 | -------------------------------------------------------------------------------- /cypress/support/commands/mount.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | import isFunction from 'lodash/isFunction'; 3 | 4 | import mountVue from 'cypress-vue-unit-test'; 5 | 6 | import VuePikaday from '../../../dist/vue-pikaday.esm'; 7 | 8 | import standaloneHTML from './standalone.html'; 9 | 10 | const mode = `${ Cypress.env('mode') || 'esm' }`; 11 | 12 | const options = { 13 | extensions: { 14 | plugins: [VuePikaday] 15 | }, 16 | html: '
' 17 | }; 18 | 19 | Cypress.Commands.add('mount', (component) => { 20 | const appIframe = window.parent.document.querySelector('.aut-iframe'); 21 | const iframeDocument = appIframe.contentDocument || appIframe.contentWindow.document; 22 | const componentData = (isFunction(component.data) ? component.data() : component.data) || {}; 23 | 24 | component.data = function () { 25 | return merge({ 26 | date: null, 27 | }, componentData, { 28 | options: { 29 | // we have to specify container to put picker to, otherwise it would be rendered in wrong frame 30 | container: iframeDocument.querySelector('body') 31 | } 32 | }); 33 | }; 34 | 35 | if (mode === 'standalone') { 36 | component.el = '#app'; 37 | 38 | // inject component specification into appIframe so it can be used to instantiate Vue component 39 | appIframe.contentWindow._component = component; 40 | // inject Cypress object to appIframe for tests consistency 41 | appIframe.contentWindow.Cypress = Cypress; 42 | 43 | const componentHTML = ''; 44 | 45 | iframeDocument.write(standaloneHTML); 46 | iframeDocument.write(componentHTML); 47 | iframeDocument.close(); 48 | } else { 49 | mountVue(component, options)(); 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /cypress/support/commands/standalone.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | Cypress.config('mode', `${Cypress.env('mode') || 'esm'}`); 20 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # abort on errors 4 | set -e 5 | 6 | yarn docs:build 7 | 8 | cd docs/.vuepress/dist 9 | 10 | git init 11 | git add -A 12 | git commit -m 'deploy' 13 | 14 | git push -f git@github.com:NetCZ/vue-pikaday.git master:gh-pages 15 | -------------------------------------------------------------------------------- /docs/.vuepress/components/attributes-event-listeners.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 46 | -------------------------------------------------------------------------------- /docs/.vuepress/components/base.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import moment from 'moment'; 3 | import _ from 'lodash'; 4 | 5 | import 'pikaday/css/pikaday.css'; 6 | 7 | import VuePikaday from '../../../src'; 8 | 9 | Vue.use(VuePikaday); 10 | 11 | Vue.prototype.$moment = moment; 12 | Vue.prototype.$_ = _; 13 | -------------------------------------------------------------------------------- /docs/.vuepress/components/basic-example.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 41 | -------------------------------------------------------------------------------- /docs/.vuepress/components/manual-trigger.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 48 | 49 | -------------------------------------------------------------------------------- /docs/.vuepress/components/pikaday-options.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 48 | 49 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | base: '/vue-pikaday/', 3 | title: 'VuePikaday', 4 | head: [ 5 | ['link', { 6 | rel: 'stylesheet', 7 | href: 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600|Roboto Mono' 8 | }] 9 | ], 10 | extendMarkdown: md => { 11 | md.use(require('markdown-it-include'), './docs/guide'); 12 | md.use(require('markdown-it-attrs')); 13 | }, 14 | themeConfig: { 15 | repo: 'netcz/vue-pikaday', 16 | logo: '/vue-pikaday.png', 17 | nav: [ 18 | { 19 | text: 'Guide', 20 | link: '/guide/' 21 | }, 22 | { 23 | text: 'Config Reference', 24 | link: '/config/#props' 25 | } 26 | ], 27 | sidebar: { 28 | '/guide/': [ 29 | { 30 | title: 'Guide', 31 | collapsable: false, 32 | children: [ 33 | '', 34 | 'installation', 35 | ['usage', 'Usage'] 36 | ] 37 | } 38 | ], 39 | '/config/': [ 40 | { 41 | title: 'Config Reference', 42 | collapsable: false, 43 | sidebarDepth: 0, 44 | children: [ 45 | ['#props', 'Props'], 46 | ['#directives', 'Directives'] 47 | ] 48 | } 49 | ] 50 | }, 51 | smoothScroll: true, 52 | lastUpdated: 'Last Updated', 53 | displayAllHeaders: true 54 | }, 55 | chainWebpack: config => { 56 | config.module 57 | .rule('js') 58 | .use('babel-loader') 59 | .tap(options => { 60 | return Object.assign(options, { 61 | presets: [].concat(options.presets, [ 62 | ['@babel/preset-env', { 'modules': false }], 63 | '@babel/preset-flow' 64 | ]) 65 | }) 66 | }) 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /docs/.vuepress/public/vue-pikaday.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NetCZ/vue-pikaday/c79576355c59add84988052b95a6e42c021b8cff/docs/.vuepress/public/vue-pikaday.png -------------------------------------------------------------------------------- /docs/.vuepress/styles/index.styl: -------------------------------------------------------------------------------- 1 | body 2 | font-family "Source Sans Pro", "Helvetica Neue", Arial, sans-serif !important 3 | 4 | .hero 5 | img 6 | max-width 100% 7 | 8 | .clearfix 9 | &:after 10 | content "" 11 | display table 12 | clear both 13 | 14 | .pika-single 15 | table, thead, tbody, tr, td, th, abbr 16 | margin 0 !important 17 | padding 0 !important 18 | border 0 !important 19 | font-size 100% !important 20 | vertical-align baseline !important 21 | 22 | table 23 | border-collapse collapse !important 24 | border-spacing 0 !important 25 | display table !important 26 | 27 | abbr 28 | cursor pointer !important 29 | font-weight 400 30 | text-decoration none 31 | 32 | .theme-default-content 33 | input 34 | cursor text 35 | width 10rem 36 | color #4e6e8e 37 | display inline-block 38 | border 1px solid #cfd4db 39 | border-radius 2rem 40 | font-size 0.9rem 41 | line-height 2rem 42 | padding 0 1rem 43 | outline none 44 | transition all 0.2s ease 45 | 46 | &:focus 47 | cursor auto 48 | border-color #3eaf7c 49 | 50 | button 51 | cursor pointer 52 | width 5rem 53 | display inline-block 54 | border 1px solid #cfd4db 55 | border-radius 2rem 56 | font-size 0.9rem 57 | line-height 2rem 58 | padding 0 1rem 59 | margin 0 1rem 60 | outline none 61 | transition all 0.2s ease 62 | 63 | &:hover 64 | border-color #3eaf7c 65 | 66 | .example 67 | padding 2rem 0 68 | 69 | 70 | pre 71 | margin 2rem 0 0 72 | padding 1rem 73 | background-color rgba(27,31,35,0.05) 74 | border-radius 6px 75 | 76 | code 77 | color #476582 78 | padding 0.25rem 0.5rem 79 | margin 0 80 | font-size 0.85em 81 | 82 | .mw-100 83 | min-width 100px 84 | 85 | .sidebar-heading 86 | display none 87 | 88 | .sidebar-group a.sidebar-link 89 | padding-left 1rem 90 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | heroImage: /vue-pikaday.png 4 | description: Pikaday datepicker wrapper for Vue 5 | actionText: Get Started → 6 | actionLink: /guide/ 7 | footer: 'MIT Licensed | Copyright © 2018 - present Michal Szajter' 8 | --- 9 | -------------------------------------------------------------------------------- /docs/config/README.md: -------------------------------------------------------------------------------- 1 | # Config Reference 2 | 3 | [[toc]] 4 | 5 | ## Props 6 | 7 | | Name{.mw-100} | Required | Default | Datatype | Description | 8 | | --- | --- | --- | --- | --- | 9 | | v-model | yes | | Date, null, undefined | component model binding, for more info see [VueJS docs](https://vuejs.org/v2/guide/forms.html#Value-Bindings) | 10 | | options | no | {} | Object | **reactive** options passed to underlaying Pikaday instance, for all available options see Pikaday [documentation](https://github.com/dbushell/Pikaday#configuration) 11 | | autoDefault | no | false | Boolean | if `true`, component will set actual date as its default value | 12 | 13 | ::: warning NOTE 14 | `field` option is internally overridden and not available to use.\ 15 | `trigger` is not usable either as DOM element do not exist yet when component is registered 16 | ::: tip 17 | use `v-p-visible` directive instead 18 | ::: 19 | 20 | ## Directives 21 | 22 | | Name{.mw-100} | Required | Default | Datatype | Description | 23 | | --- | --- | --- | --- | --- | 24 | | v-p-visible | no | false | Boolean | two-way binding directive to control datepicker visibility | 25 | 26 | -------------------------------------------------------------------------------- /docs/guide/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | VuePikaday is a Vue component (wrapper) around [Pikaday](https://github.com/dbushell/Pikaday) datepicker with 4 | simple purpose - to make it easy to integrate with Vue applications. 5 | -------------------------------------------------------------------------------- /docs/guide/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | [[toc]] 4 | 5 | You can either add VuePikaday to your `package.json` dependencies or HTML as script tag. 6 | ::: warning NOTE 7 | VuePikaday has also [Pikaday](https://github.com/dbushell/Pikaday) and [moment.js](http://momentjs.com) 8 | as `peerDependencies` and requires them to be installed / included in order to work. 9 | ::: 10 | 11 | ## NPM 12 | 13 | ```bash 14 | npm install --save @netcz/vue-pikaday 15 | ``` 16 | 17 | or 18 | 19 | ```bash 20 | yarn add @netcz/vue-pikaday 21 | ``` 22 | 23 | ## Standalone 24 | 25 | 1. Download latest [release](https://github.com/netcz/vue-pikaday/releases) of VuePikaday or use latest [jsDelivr](https://www.jsdelivr.com/package/npm/@netcz/vue-pikaday) links 26 | 2. Add to your HTML head section (after Vue) using script tag 27 | 28 | ```html 29 | 30 | 31 | 32 | 33 | 34 | or 35 | 36 | ``` 37 | 38 | ::: warning 39 | **DO NOT** use jsDelivr's "combine" method (even its tempting) as it uglifies code and [Pikaday](https://github.com/dbushell/Pikaday) relies on "moment" variable in scope in order to use it. 40 | ::: 41 | -------------------------------------------------------------------------------- /docs/guide/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | [[toc]] 4 | 5 | !!!include(usage/registration.md)!!! 6 | !!!include(usage/basic-example.md)!!! 7 | !!!include(usage/attributes-event-listeners.md)!!! 8 | !!!include(usage/pikaday-options.md)!!! 9 | !!!include(usage/manual-trigger.md)!!! 10 | -------------------------------------------------------------------------------- /docs/guide/usage/attributes-event-listeners.md: -------------------------------------------------------------------------------- 1 | ## Attributes and event listeners 2 | 3 | Component passes attributes and binds event listeners down to underlying `input`. 4 | All attributes and/or event listeners you are used to use will work. 5 | 6 | ::: tip 7 | Open browser console to see event listeners working. 8 | ::: 9 | 10 | 11 | 12 | 13 | 14 | ```vue 15 | 23 | 24 | 43 | ``` 44 | -------------------------------------------------------------------------------- /docs/guide/usage/basic-example.md: -------------------------------------------------------------------------------- 1 | ## Basic example 2 | 3 | Simply include `vue-pikaday` component with `v-model` defined. 4 | 5 | 6 | 7 | 8 | 9 | ```vue 10 | 16 | 17 | 33 | ``` 34 | -------------------------------------------------------------------------------- /docs/guide/usage/manual-trigger.md: -------------------------------------------------------------------------------- 1 | ## Manually toggle datepicker 2 | 3 | To delegate datepicker opening to other element than underlying input, use two-way `v-p-visible` directive. 4 | 5 | 6 | 7 | 8 | 9 | ```vue 10 | 17 | 18 | 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/guide/usage/pikaday-options.md: -------------------------------------------------------------------------------- 1 | ## Passing options to underlying Pikaday 2 | 3 | Options can be passed using `options` prop on component. 4 | To see all possible options, consult official Pikaday [documentation](https://github.com/dbushell/Pikaday#configuration). 5 | 6 | ::: tip 7 | see [Config Reference](/config/#props) for more info 8 | ::: 9 | 10 | 11 | 12 | 13 | 14 | ```vue 15 | 21 | 22 | 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/guide/usage/registration.md: -------------------------------------------------------------------------------- 1 | ## Component registration 2 | 3 | ::: warning NOTE 4 | Skip this step when using standalone version. 5 | ::: 6 | 7 | In your main project file add 8 | 9 | ```javascript 10 | import VuePikaday from '@netcz/vue-pikaday'; 11 | Vue.use(VuePikaday); 12 | ``` 13 | 14 | and from now on you'll be able to use `vue-pikaday` component in your application. 15 | 16 | ::: tip 17 | If you wish to use Pikaday default styles, include them through VuePikaday 18 | 19 | ```javascript 20 | import '@netcz/vue-pikaday/dist/vue-pikaday.min.css'; 21 | ``` 22 | ::: 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@netcz/vue-pikaday", 3 | "scope": "netcz", 4 | "private": false, 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "version": "0.0.0-development", 9 | "description": "VueJS wrapper component for Pikaday datepicker", 10 | "main": "dist/vue-pikaday.common.js", 11 | "browser": "dist/vue-pikaday.js", 12 | "module": "dist/vue-pikaday.esm.js", 13 | "style": "dist/vue-pikaday.css", 14 | "author": "Michal Szajter ", 15 | "license": "MIT", 16 | "homepage": "https://netcz.github.io/vue-pikaday", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://git@github.com/NetCZ/vue-pikaday.git" 20 | }, 21 | "bugs": { 22 | "url": "https://github.com/NetCZ/vue-pikaday/issues" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "commit-msg": "commitlint -e" 27 | } 28 | }, 29 | "scripts": { 30 | "dev": "rollup -c -w", 31 | "build": "rollup -c", 32 | "pretest": "yarn lint && yarn build", 33 | "test:esm": "yarn cy:run --env mode=esm", 34 | "test:standalone": "yarn cy:run --env mode=standalone", 35 | "test": "yarn test:esm && yarn test:standalone", 36 | "cy:run": "cypress run $RECORD", 37 | "cy:open": "cypress open", 38 | "lint": "eslint src", 39 | "semantic-release": "semantic-release", 40 | "snyk-protect": "snyk protect", 41 | "prepublishOnly": "yarn snyk-protect", 42 | "docs:dev": "vuepress dev docs", 43 | "docs:build": "vuepress build docs" 44 | }, 45 | "keywords": [ 46 | "vue", 47 | "vuejs", 48 | "pikaday", 49 | "datepicker" 50 | ], 51 | "dependencies": { 52 | "lodash": "^4.17.21" 53 | }, 54 | "peerDependencies": { 55 | "moment": "^2.24.0", 56 | "pikaday": "^1.8.0", 57 | "vue": "^2.6.10" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "^7.9.0", 61 | "@babel/preset-env": "^7.9.0", 62 | "@babel/preset-flow": "^7.9.0", 63 | "@babel/register": "^7.5.5", 64 | "@commitlint/cli": "^8.1.0", 65 | "@commitlint/config-conventional": "^8.1.0", 66 | "@commitlint/travis-cli": "^8.1.0", 67 | "@cypress/rollup-preprocessor": "https://github.com/dschulten/cypress-rollup-preprocessor", 68 | "babel-eslint": "^10.0.2", 69 | "chai": "^4.2.0", 70 | "cypress": "^4.2.0", 71 | "cypress-vue-unit-test": "^1.11.3", 72 | "eslint": "^6.1.0", 73 | "eslint-plugin-flowtype": "^4.3.1", 74 | "flow-bin": "^0.121.0", 75 | "husky": "^4.2.1", 76 | "moment": "^2.24.0", 77 | "pikaday": "^1.8.0", 78 | "rollup": "^2.3.0", 79 | "rollup-plugin-babel": "^4.4.0", 80 | "rollup-plugin-commonjs": "^10.0.2", 81 | "rollup-plugin-copy": "^3.1.0", 82 | "rollup-plugin-html": "^0.2.1", 83 | "rollup-plugin-node-resolve": "^5.2.0", 84 | "rollup-plugin-postcss": "^2.0.3", 85 | "rollup-plugin-terser": "^5.3.0", 86 | "semantic-release": "^17.0.0", 87 | "snyk": "^1.316.1", 88 | "vue": "^2.6.11", 89 | "vue-template-compiler": "^2.6.11", 90 | "lodash": "^4.17.21", 91 | "markdown-it": "^10.0.0", 92 | "markdown-it-attrs": "^3.0.2", 93 | "markdown-it-include": "^1.1.0", 94 | "vue-server-renderer": "^2.6.11", 95 | "vuepress": "^1.4.1" 96 | }, 97 | "resolutions": { 98 | "tar": "^4.4.10", 99 | "marked": "^0.6.2" 100 | }, 101 | "snyk": true 102 | } 103 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import camelCase from 'lodash/camelCase'; 3 | 4 | import babel from 'rollup-plugin-babel'; 5 | import resolve from 'rollup-plugin-node-resolve'; 6 | import commonjs from 'rollup-plugin-commonjs'; 7 | import { terser } from 'rollup-plugin-terser'; 8 | import postcss from 'rollup-plugin-postcss'; 9 | import copy from 'rollup-plugin-copy'; 10 | 11 | import { peerDependencies, scope, name as nameWithScope } from './package.json'; 12 | 13 | const name = nameWithScope.replace(`@${ scope }/`, ''); 14 | const base = path.resolve(__dirname, './'); 15 | const src = path.resolve(base, 'src'); 16 | const dist = path.resolve(base, 'dist'); 17 | const debug = process.env.DEBUG === 'true'; 18 | 19 | const baseConfig = { 20 | input: path.resolve(src, 'index.js'), 21 | external: Object.keys(peerDependencies), 22 | plugins: [ 23 | resolve({ external: ['vue'] }), 24 | babel({ 25 | babelrc: false, 26 | presets: [ 27 | ['@babel/preset-env', { 'modules': false }], 28 | '@babel/preset-flow' 29 | ], 30 | exclude: 'node_modules/**' 31 | }), 32 | commonjs(), 33 | postcss({ 34 | extract: dist + '/vue-pikaday.min.css', 35 | minimize: true 36 | }), 37 | copy({ 38 | targets: { 39 | 'node_modules/pikaday/css/pikaday.css': dist + '/vue-pikaday.css' 40 | } 41 | }), 42 | terser({ 43 | mangle: { 44 | reserved: ['Pikaday', 'moment'] 45 | }, 46 | compress: { 47 | drop_console: !debug, 48 | drop_debugger: !debug 49 | } 50 | }) 51 | ] 52 | }; 53 | 54 | export default [ 55 | { 56 | ...baseConfig, 57 | output: { 58 | format: 'cjs', 59 | name: camelCase(name), 60 | file: path.resolve(dist, name + '.common.js'), 61 | sourcemap: true, 62 | globals: { 63 | moment: 'moment', 64 | pikaday: 'Pikaday' 65 | }, 66 | }, 67 | }, 68 | { 69 | ...baseConfig, 70 | output: { 71 | format: 'umd', 72 | name: camelCase(name), 73 | file: path.resolve(dist, name + '.js'), 74 | sourcemap: true, 75 | globals: { 76 | moment: 'moment', 77 | pikaday: 'Pikaday' 78 | }, 79 | } 80 | }, 81 | { 82 | ...baseConfig, 83 | output: { 84 | format: 'es', 85 | file: path.resolve(dist, name + '.esm.js'), 86 | sourcemap: true 87 | } 88 | } 89 | ]; 90 | -------------------------------------------------------------------------------- /src/component.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import moment from 'moment'; 4 | import Pikaday from 'pikaday'; 5 | import isDate from 'lodash/isDate'; 6 | import isString from 'lodash/isString'; 7 | 8 | import 'pikaday/css/pikaday.css'; 9 | 10 | function isEvent(value) { 11 | return value instanceof Event || (value && value.constructor && value.constructor.name === 'Event'); 12 | } 13 | 14 | export default { 15 | name: 'vue-pikaday', 16 | inheritAttrs: false, 17 | props: { 18 | value: { 19 | validator(value: typeof undefined | null | Date | Event | string): boolean { 20 | const allowedTypes: Array = [undefined, null]; 21 | 22 | if (isEvent(value)) { 23 | return true; 24 | } 25 | 26 | if (isDate(value)) { 27 | return true; 28 | } 29 | 30 | if(isString(value) && moment(value).isValid()) { 31 | return true; 32 | } 33 | 34 | for (const type of allowedTypes) { 35 | const allowed: boolean = value === type || typeof value === type; 36 | 37 | if (allowed) { 38 | return true; 39 | } 40 | } 41 | 42 | return false; 43 | }, 44 | required: true 45 | }, 46 | options: { 47 | required: false, 48 | default() { 49 | return {}; 50 | } 51 | }, 52 | autoDefault: { 53 | type: Boolean, 54 | required: false, 55 | default: false 56 | } 57 | }, 58 | data() { 59 | return { 60 | visible: false, 61 | elAttrs: { 62 | type: 'text' 63 | }, 64 | defaultOptions: { 65 | format: 'D MMM YYYY' 66 | } 67 | }; 68 | }, 69 | computed: { 70 | elementAttributes(): Object { 71 | return Object.assign({}, this.$attrs, this.elAttrs); 72 | }, 73 | mergedOptions() { 74 | return Object.assign({}, this.defaultOptions, this.options); 75 | } 76 | }, 77 | render(h: Function): Object { 78 | return h('input', { 79 | attrs: this.elementAttributes, 80 | on: this.$listeners, 81 | value: this.inputValue(this.value) 82 | }, this.$slots.default); 83 | }, 84 | mounted() { 85 | this.create(); 86 | 87 | this.$watch('value', (value: typeof undefined | null | Date) => { 88 | if (!isDate(value)) { 89 | value = null; 90 | } 91 | if (!this.visible) { 92 | this.pikaday.setDate(value, true); 93 | } 94 | this.change(value); 95 | }); 96 | }, 97 | beforeDestroy() { 98 | this.destroy(); 99 | }, 100 | watch: { 101 | options: { 102 | handler() { 103 | this.reload(); 104 | }, 105 | deep: true 106 | } 107 | }, 108 | methods: { 109 | create() { 110 | this.mergedOptions.field = this.$el; 111 | 112 | this.bindListener('onSelect', () => this.onSelect()); 113 | this.bindListener('onOpen', () => this.onOpen()); 114 | this.bindListener('onClose', () => this.onClose()); 115 | 116 | this.pikaday = new Pikaday(this.mergedOptions); 117 | 118 | let defaultValue: typeof undefined | null | Date = this.value; 119 | 120 | if (!this.value && this.autoDefault) { 121 | defaultValue = moment().toDate(); 122 | this.change(defaultValue); 123 | } 124 | 125 | this.pikaday.setDate(defaultValue, true); 126 | 127 | if (this.mergedOptions.bound === false) { 128 | this.hide(); 129 | } else { 130 | this.visible ? this.show() : this.hide(); 131 | } 132 | }, 133 | destroy() { 134 | this.pikaday.destroy(); 135 | }, 136 | reload() { 137 | this.destroy(); 138 | this.create(); 139 | }, 140 | change(value: typeof undefined | null | Date) { 141 | this.$emit('input', value); 142 | this.$emit('input-value', this.inputValue(value)); 143 | }, 144 | inputValue(value: typeof undefined | null | Date): string | null { 145 | if (!isDate(value)) { 146 | return null; 147 | } 148 | const inputValue: moment = moment(value); 149 | return inputValue.isValid() ? inputValue.format(this.mergedOptions.format) : null; 150 | }, 151 | onSelect() { 152 | this.change(this.pikaday.getDate()); 153 | }, 154 | onOpen() { 155 | this.visible = true; 156 | }, 157 | onClose() { 158 | if (!isDate(this.value)) { 159 | this.pikaday.setDate(null, true); 160 | this.change(null); 161 | } 162 | this.visible = false; 163 | }, 164 | show() { 165 | this.pikaday.show(); 166 | }, 167 | hide() { 168 | this.pikaday.hide(); 169 | }, 170 | bindListener(listener: Function, cb: Function) { 171 | if (this.mergedOptions[listener]) { 172 | const old: Function = this.mergedOptions[listener]; 173 | this.mergedOptions[listener] = (...args) => { 174 | old(args); 175 | cb.apply(this); 176 | }; 177 | } else { 178 | this.mergedOptions[listener] = cb; 179 | } 180 | } 181 | } 182 | }; 183 | -------------------------------------------------------------------------------- /src/directives.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import _debounce from 'lodash/debounce'; 4 | 5 | function changeState(state: boolean, target: Object, expression: string, instance: Object | null = null) { 6 | target[expression] = state; 7 | instance = instance || target; 8 | state ? instance.show() : instance.hide(); 9 | } 10 | 11 | export const VuePikadayVisible = { 12 | inserted(el: Object, binding: Object, VNode: Object) { 13 | const instance: Object = VNode.componentInstance; 14 | 15 | instance.onOpen = () => _debounce(() => changeState(true, VNode.context, binding.expression, instance), 100); 16 | 17 | instance.onClose = _debounce(() => changeState(false, VNode.context, binding.expression, instance), 100); 18 | }, 19 | update(el: Object, binding: Object, VNode: Object) { 20 | _debounce(() => changeState(binding.value, VNode.componentInstance, binding.expression), 100)(); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import VuePikaday from './component'; 2 | import { VuePikadayVisible } from './directives'; 3 | 4 | const VuePikadayPlugin = { 5 | install(Vue) { 6 | Vue.component(VuePikaday.name, VuePikaday); 7 | Vue.directive('p-visible', VuePikadayVisible); 8 | } 9 | }; 10 | 11 | if (typeof window !== 'undefined' && window.Vue) { 12 | window.Vue.use(VuePikadayPlugin); 13 | } 14 | 15 | export default VuePikadayPlugin; 16 | -------------------------------------------------------------------------------- /test/specs/attributes-event-listeners.js: -------------------------------------------------------------------------------- 1 | const component = { 2 | template: ``, 10 | methods: { 11 | focus() { 12 | this.$emit('focused'); 13 | }, 14 | blur() { 15 | this.$emit('blurred'); 16 | } 17 | } 18 | }; 19 | 20 | describe('Attributes & events', () => { 21 | before(() => cy.mount(component)); 22 | 23 | it('renders custom attributes', () => { 24 | cy.get('[data-vue-pikaday]').should('have.attr', 'placeholder', 'Pick a date'); 25 | }); 26 | 27 | it('dispatches event listeners', () => { 28 | const focusSpy = cy.spy(); 29 | const blurSpy = cy.spy(); 30 | 31 | Cypress.vue.$on('focused', focusSpy); 32 | Cypress.vue.$on('blurred', blurSpy); 33 | 34 | cy.get('[data-vue-pikaday]').as('picker').click().then(() => { 35 | expect(focusSpy).to.be.calledOnce; 36 | }); 37 | 38 | cy.get('@picker').blur().then(() => { 39 | expect(blurSpy).to.be.calledOnce; 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/specs/basic.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const component = { 4 | template: '' 5 | }; 6 | 7 | describe('Basic', () => { 8 | before(() => cy.mount(component)); 9 | 10 | it('renders', () => { 11 | cy.get('[data-vue-pikaday]').should('be.visible'); 12 | cy.get('.pika-single').should('not.be.visible'); 13 | }); 14 | 15 | it('focuses / blurs picker', () => { 16 | cy.get('[data-vue-pikaday]').as('picker').focus(); 17 | cy.get('.pika-single').should('be.visible'); 18 | cy.get('@picker').blur(); 19 | cy.get('.pika-single').should('not.be.visible'); 20 | }); 21 | 22 | it('selects current date via picker', () => { 23 | cy.get('[data-vue-pikaday]').focus(); 24 | cy.get(`[data-day="${ moment().date() }"] button`).click(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/specs/custom-formating.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | function customFormat(date, format) { 4 | const start = moment(date, format).day(1); 5 | const end = moment(date, format).day(5); 6 | return `${ start.format(format) } - ${ end.format(format) }`; 7 | } 8 | 9 | const component = { 10 | template: ``, 15 | data() { 16 | return { 17 | options: { 18 | pickWholeWeek: true, 19 | toString: customFormat, 20 | parse: () => moment(this.date) 21 | } 22 | }; 23 | } 24 | }; 25 | 26 | describe('Custom formatting', () => { 27 | before(() => cy.mount(component)); 28 | 29 | it('renders input value using custom formatter', () => { 30 | cy.get('[data-vue-pikaday]').as('picker').focus(); 31 | cy.get(`[data-day="${ moment().date() }"] button`).click(); 32 | 33 | cy.get('@picker').should('have.value', customFormat(moment().date(), 'D MMM YYYY')); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/specs/external-date-change.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const updatedDateCurrent = moment(); 4 | const updatedDateWeekForward = moment().add(7, 'days'); 5 | 6 | const component = { 7 | template: `
8 | 12 | Internal date: {{ date ? date : 'null' }} 13 | 14 | 15 | 16 |
`, 17 | methods: { 18 | updateDateCurrent() { 19 | this.date = updatedDateCurrent.toDate(); 20 | }, 21 | updateDateWeekForward() { 22 | this.date = updatedDateWeekForward.toDate(); 23 | }, 24 | clear() { 25 | this.date = null; 26 | } 27 | } 28 | }; 29 | 30 | describe('External changes', () => { 31 | before(() => cy.mount(component)); 32 | 33 | it('updates Pikaday value from outside', () => { 34 | cy.get('[data-test="update-button-current"]').click(); 35 | cy.get('[data-vue-pikaday]').as('picker').should('have.value', updatedDateCurrent.format('D MMM YYYY')); 36 | 37 | cy.get('[data-test="update-button-week"]').click(); 38 | cy.get('@picker').should('have.value', updatedDateWeekForward.format('D MMM YYYY')); 39 | }); 40 | 41 | it('handles removing characters', () => { 42 | cy.get('[data-test="update-button-current"]').click(); 43 | 44 | cy.get('[data-vue-pikaday]').as('picker').type('{backspace}'); 45 | 46 | cy.get('[data-internal-value]').should('contain', 'null'); 47 | }); 48 | 49 | it('handles invalid value typed in', () => { 50 | cy.get('[data-vue-pikaday]').as('picker').clear().type('invalid value').blur().should('have.value', ''); 51 | 52 | cy.get('@picker').clear().type('13 Se').blur().should('have.value', ''); 53 | }); 54 | 55 | it('handles clearing value from outside', () => { 56 | cy.get('[data-test="update-button-current"]').click(); 57 | cy.get('[data-test="clear-button"]').click(); 58 | cy.get('[data-vue-pikaday]').as('picker').should('have.value', ''); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /test/specs/manual-trigger.js: -------------------------------------------------------------------------------- 1 | const component = { 2 | template: `
3 | 9 | 10 |
`, 11 | data() { 12 | return { 13 | visible: false 14 | }; 15 | } 16 | }; 17 | 18 | describe('Manual trigger', () => { 19 | before(() => cy.mount(component)); 20 | 21 | it('shows / hide via external trigger', () => { 22 | cy.get('[data-test="manual"]').click(); 23 | cy.get('.pika-single').should('be.visible'); 24 | 25 | cy.get('[data-test="manual"]').click(); 26 | cy.get('.pika-single').should('not.be.visible'); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/specs/pikaday-options.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | const allowedDateStart = moment().date(5); 4 | const allowedDateEnd = moment(allowedDateStart).add(6, 'days'); 5 | 6 | const component = { 7 | template: `
8 | 14 | 15 |
`, 16 | data() { 17 | return { 18 | options: { 19 | format: 'YYYY/MM/DD', 20 | minDate: allowedDateStart.toDate(), 21 | maxDate: allowedDateEnd.toDate() 22 | } 23 | }; 24 | }, 25 | methods: { 26 | updateOptions() { 27 | this.options.minDate = moment(allowedDateStart).subtract(2, 'day').toDate(); 28 | this.options.maxDate = moment(allowedDateEnd).subtract(2, 'day').toDate(); 29 | } 30 | } 31 | }; 32 | 33 | describe('Pikaday options', () => { 34 | before(() => cy.mount(component)); 35 | 36 | it('have current date filled by default', () => { 37 | let testDate = moment(); 38 | 39 | if (testDate >= allowedDateEnd) { 40 | testDate = allowedDateEnd; 41 | } 42 | 43 | if (testDate <= allowedDateStart) { 44 | testDate = allowedDateStart; 45 | } 46 | 47 | cy.get('[data-vue-pikaday]').should('have.value', testDate.format('YYYY/MM/DD')); 48 | }); 49 | 50 | it('have custom display format', () => { 51 | cy.get('[data-vue-pikaday]').as('picker').click(); 52 | cy.get(`[data-day="${ allowedDateStart.date() }"] button`).click(); 53 | cy.get('@picker').should('have.value', allowedDateStart.format('YYYY/MM/DD')); 54 | }); 55 | 56 | it('restricts dates selection to specific week', () => { 57 | cy.get('[data-vue-pikaday]').as('picker').click(); 58 | cy.get(`[data-day="${ moment(allowedDateStart).subtract(1, 'day').date() }"]`).should('have.class', 'is-disabled'); 59 | 60 | cy.get('@picker').click(); 61 | cy.get(`[data-day="${ moment(allowedDateStart).add(8, 'days').date() }"]`).should('have.class', 'is-disabled'); 62 | 63 | const plus5Days = moment(allowedDateStart).add(5, 'days'); 64 | 65 | cy.get('@picker').click(); 66 | cy.get(`[data-day="${ plus5Days.date() }"]`).should('not.have.class', 'is-disabled'); 67 | cy.get(`[data-day="${ plus5Days.date() }"] button`).click(); 68 | cy.get('@picker').should('have.value', plus5Days.format('YYYY/MM/DD')); 69 | }); 70 | 71 | it('updates dates restriction selection', () => { 72 | cy.get('[data-test="update-options"]').click(); 73 | 74 | cy.get('[data-vue-pikaday]').as('picker').click(); 75 | cy.get(`[data-day="${ moment(allowedDateStart).subtract(3, 'days').date() }"]`).should('have.class', 'is-disabled'); 76 | 77 | cy.get('@picker').click(); 78 | cy.get(`[data-day="${ moment(allowedDateEnd).subtract(1, 'day').date() }"]`).should('have.class', 'is-disabled'); 79 | 80 | const plus2Days = moment(allowedDateStart).add(2, 'days'); 81 | 82 | cy.get('@picker').click(); 83 | cy.get(`[data-day="${ plus2Days.date() }"]`).should('not.have.class', 'is-disabled'); 84 | cy.get(`[data-day="${ plus2Days.date() }"] button`).click(); 85 | cy.get('@picker').should('have.value', plus2Days.format('YYYY/MM/DD')); 86 | }); 87 | }); 88 | --------------------------------------------------------------------------------