Full Code of icehaunter/vue3-datepicker for AI

master a086dd68d245 cached
28 files
75.6 KB
19.8k tokens
1 requests
Download .txt
Repository: icehaunter/vue3-datepicker
Branch: master
Commit: a086dd68d245
Files: 28
Total size: 75.6 KB

Directory structure:
gitextract_cw1ibvv4/

├── .github/
│   └── workflows/
│       ├── docs-autobuild.yml
│       ├── formatting-check.yml
│       └── npm-publish.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── docs/
│   ├── .vitepress/
│   │   ├── config.js
│   │   └── theme/
│   │       ├── custom.css
│   │       └── index.js
│   ├── config.md
│   ├── examples.md
│   └── index.md
├── index.html
├── package.json
├── src/
│   ├── App.vue
│   ├── datepicker/
│   │   ├── Datepicker.vue
│   │   ├── DayPicker.vue
│   │   ├── MonthPicker.vue
│   │   ├── PickerPopup.vue
│   │   ├── Timepicker.vue
│   │   └── YearPicker.vue
│   ├── index.css
│   ├── main.js
│   └── vue-shim.d.ts
├── tsconfig.json
└── vite.config.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/docs-autobuild.yml
================================================
name: Documentation release

on:
  push:
    branches: [master]
    paths:
      - 'docs/**'
      - 'package.json'
  workflow_dispatch:

jobs:
  build_documentation:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Build documentation
        run: |
          npm ci
          npm run build:docs

      - name: Initialize repo with docs
        run: git init ./docs/.vitepress/dist

      - name: Commit updated docs
        run: |
          git config user.name "${{ github.actor }}"
          git config user.email "${{ github.actor }}@users.noreply.github.com"
          git add -A
          git commit -m "Updated docs from ${{ github.sha }}"
          git push --verbose -f "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" master:gh-pages
        working-directory: ./docs/.vitepress/dist


================================================
FILE: .github/workflows/formatting-check.yml
================================================
name: Verify the code
on:
  pull_request:

jobs:
  verify-formatting:
    name: Check formatting
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          mode-version: 18
          cache: npm
      - run: npm ci
      - run: npx prettier --check .


================================================
FILE: .github/workflows/npm-publish.yml
================================================
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages

name: Node.js Package

on:
  release:
    types: [created]
  workflow_dispatch:

jobs:
  publish-npm:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: 18
          registry-url: https://registry.npmjs.org/
      - run: npm ci
      - run: rm -rf dist/
      - run: npm run build:component
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}


================================================
FILE: .gitignore
================================================
node_modules
.DS_Store
.idea
dist
*.local
**/.vitepress/cache


================================================
FILE: .prettierignore
================================================
**/dist
**/.vitepress/cache

================================================
FILE: .prettierrc
================================================
{
  "semi": false,
  "singleQuote": true,
  "vueIndentScriptAndStyle": false,
  "tabWidth": 2
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2020 Ilia Borovitinov

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Vue 3 Datepicker

Documentation: https://icehaunter.github.io/vue3-datepicker/

This is a basic (at least for now) reimplementation of https://github.com/icehaunter/vuejs-datepicker in Vue 3 and with greatly cleaned up code.

All date manipulation and formatting are done via the amazing [`date-fns`](https://date-fns.org/) library, so it's a direct dependency of this picker.

## Installation

Package is available on NPM: https://www.npmjs.com/package/vue3-datepicker

```sh
npm i vue3-datepicker
```

The component is packaged mainly for use with bundlers, if you require a browser build - post an issue.

## Usage

For more examples see https://icehaunter.github.io/vue3-datepicker/examples.html

```vue
<template>
  <datepicker
    v-model="selected"
    :locale="locale"
    :upperLimit="to"
    :lowerLimit="from"
    :clearable="true"
  />
</template>
```

## Props and attributes

Attribute fallthrough is enabled, so any attribute you apply to the component will be passed down to the input.

All props which accept formatting strings for dates use [`date-fns` formatting function](https://date-fns.org/docs/format) under the hood, so see that function's documentation for patterns.

Main interaction to date selection is done via `v-model` with `Date` as expected type of the value passed.

Full props documentation is available at https://icehaunter.github.io/vue3-datepicker/config.html#props

| ID                       | Type                                                     | Default              | Description                                                                                                                                |
| ------------------------ | -------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `upperLimit`             | `Date`                                                   |                      | Upper limit for available dates for picking                                                                                                |
| `lowerLimit`             | `Date`                                                   |                      | Lower limit for available dates for picking                                                                                                |
| `startingViewDate`       | `Date`                                                   | `() => new Date()`   | Date on which to focus when empty datepicker is opened. Default is "right now"                                                             |
| `disabledDates`          | `{ dates: Date[] }`                                      |                      | Dates not available for picking                                                                                                            |
| `disabledTime`           | `{ dates: Date[] }`                                      |                      | Dates not available for time picking                                                                                                       |
| `startingView`           | `'time' \| 'day' \| 'month' \| 'year'`                   | `'day'`              | View on which the date picker should open. Can be either `year`, `month`, or `day`                                                         |
| `minimumView`            | `'time' \| 'day' \| 'month' \| 'year'`                   | `'day'`              | If set, lower-level views won't show                                                                                                       |
| `dayPickerHeadingFormat` | `String`                                                 | `LLLL yyyy`          | `date-fns`-type formatting for a day view heading                                                                                          |
| `dayFormat`              | `String`                                                 | `dd`                 | `date-fns`-type formatting for each day on the day view                                                                                    |
| `weekdayFormat`          | `String`                                                 | `EE`                 | `date-fns`-type formatting for a line of weekdays on day view                                                                              |
| `inputFormat`            | `String`                                                 | `yyyy-MM-dd`         | `date-fns`-type format in which the string in the input should be both parsed and displayed                                                |
| `locale`                 | [`Locale`](https://date-fns.org/v2.16.1/docs/I18n#usage) | `date-fns/locale/en` | [`date-fns` locale object](https://date-fns.org/v2.16.1/docs/I18n#usage). Used in string formatting (see default `dayPickerHeadingFormat`) |
| `disabled`               | `Boolean`                                                | `false`              | Disables datepicker and prevents it's opening                                                                                              |
| `typeable`               | `Boolean`                                                | `false`              | Allows user to input date manually                                                                                                         |
| `weekStartsOn`           | `Number`                                                 | 1                    | Day on which the week should start. Number from 0 to 6, where 0 is Sunday and 6 is Saturday. Week starts with a Monday (1) by default      |
| `clearable`              | `Boolean`                                                | `false`              | Allows clearing the selected date and setting the value to `null`                                                                          |
| `allowOutsideInterval`   | `Boolean`                                                | `false`              | Allows user to click dates outside of current interval                                                                                     |

### Events

- `opened`: Emitted every time the popup opens, including on field focus
- `closed`: Emitted every time the popup closes, including on field blur
- `decadePageChanged`: Emitted when a page is changed on the year picker view, displaying a different decade. Has a date that is included in the shown decade as an argument.
- `yearPageChanged`: Emitted when a page is changed on the month picker view, displaying a different year. Has a date that is included in the shown year as an argument.
- `monthPageChanged`: Emitted when a page is changed on the day picker view, displaying a different month. Has a date that is included in the shown month as an argument.

## Compatibility

Package is transpiled and should be usable for everyone with ES6 and above, but the styling of the datepicker itself uses CSS Grid and CSS variables.

## Example

```vue
<template>
  <datepicker v-model="picked" />
</template>

<script>
import Datepicker from '../src/datepicker/Datepicker.vue'
components: {
  Datepicker
},
data(): {
  return {
    picked: new Date();
  }
}
</script>
```


================================================
FILE: docs/.vitepress/config.js
================================================
module.exports = {
  title: 'Vue 3 Datepicker',
  description: 'Datepicker component suitable for Vue 3',
  base: '/vue3-datepicker/',
  themeConfig: {
    nav: [
      { text: 'Getting Started', link: '/' },
      { text: 'Configuration', link: '/config' },
      { text: 'Examples', link: '/examples' },
    ],
    editLink: {
      pattern:
        'https://github.com/icehaunter/vue3-datepicker/edit/master/docs/:path',
      text: 'Edit this page on Github',
    },
    socialLinks: [
      { icon: 'github', link: 'https://github.com/icehaunter/vue3-datepicker' },
    ],
  },
}


================================================
FILE: docs/.vitepress/theme/custom.css
================================================
.vp-doc input[type='text'] {
  border: 1px solid lightgray;
  padding: 4px 8px;
}


================================================
FILE: docs/.vitepress/theme/index.js
================================================
import DefaultTheme from 'vitepress/theme'
import './custom.css'

export default DefaultTheme


================================================
FILE: docs/config.md
================================================
# Configuration

## Props

### `v-model`

- Type: `Date`
- Required: yes

The actual date that will be selected. The component behaves as close to Vue 3 documentation on custom component inputs as possible, using `modelValue` prop and `update:modelValue` event pair. Use those if you want to have more control over your model binding.

### `upperLimit`

- Type: `Date`
- Required: no

Upper limit (not inclusive) for available dates for picking. All dates above that limit will not be selectable.

### `lowerLimit`

- Type: `Date`
- Required: no

Lower limit (not inclusive) for available dates for picking. All dates below that limit will not be selectable.

## `startingViewDate`

- Type: `Date`
- Default: `new Date()` (right now)

When opening the empty datepicker, this date will be "focused": the month of this date will be shown on the day picker view, the year of this date will be shown in the month picker view, and the decade of this date will be shown in the year picker view.

### `disabledDates`

- Type: `{ dates: Date[], predicate: (target: Date) => boolean }`
- Required: no

All dates listed in the `dates` array will not be selectable. Can also take in a function via the `predicate` key, which
tests each date in the current view of the calendar, returning `true` if date should be disabled.

### `disabledTime`

- Type: `{ dates: Date[], predicate: (target: Date) => boolean }`
- Required: no

All dates listed in the `dates` array will not be selectable in the timepicker view. Can also take in a function via the `predicate` key, which
tests each date in the timepicker view, returning `true` if date should be disabled.

### `startingView`

- Type: `'time' | 'day' | 'month' | 'year'`
- Default: `'day'`

View on which the date picker should open. Can be either `year`, `month`, `day` or `time`. If `startingView` is `time` and `minimumView` is `time` then only view of the calendar `time` will be available.

### `dayPickerHeadingFormat`

- Type: `String` (date-fns [format string](https://date-fns.org/docs/format))
- Default: `LLLL yyyy`

`date-fns`-type formatting for a day view heading. By default prints full month as text and selected year (e.g. January 2021).

### `weekdayFormat`

- Type: `String` (date-fns [format string](https://date-fns.org/docs/format))
- Default: `EE`

`date-fns`-type formatting for a line of weekdays on day view. By default uses three-letter representation (e.g. Fri).

### `dayFormat`

- Type: `String` (date-fns [format string](https://date-fns.org/docs/format))
- Default: `dd`

`date-fns`-type formatting for the day picker view.

### `inputFormat`

- Type: `String` (date-fns [format string](https://date-fns.org/docs/format))
- Default: `yyyy-MM-dd`

`date-fns`-type format in which the string in the input should be both parsed and displayed. By default uses ISO format (e.g. 2021-01-01).

### `locale`

- Type: `Locale` [`date-fns` locale object](https://date-fns.org/v2.16.1/docs/I18n#usage)
- Default: `date-fns/locale/en`

Used in all date string formatting (e.g. see default `dayPickerHeadingFormat`)

### `weekStartsOn`

- Type: `0 | 1 | 2 | 3 | 4 | 5 | 6`
- Default: `1`

Day on which the week should start. Number from 0 to 6, where 0 is Sunday and 6 is Saturday. Week starts with a Monday (1) by default.

### `clearable`

- Type: `Boolean`
- Default: `false`

Allows clearing the selected date and setting the value to `null`

### `disabled`

- Type: `Boolean`
- Default: `false`

Disables datepicker and prevents it's opening

### `typeable`

- Type: `Boolean`
- Default: `false`

Allows user to input date manually

### `allowOutsideInterval`

- Type: `Boolean`
- Default: `false`

Allows user to click dates outside of current interval.

## Styling

The input itself can be styled via passing classes to it. [Attribute fallthrough](https://v3.vuejs.org/guide/component-attrs.html#disabling-attribute-inheritance) is enabled. Keep in mind that input itself is not a top-level element, as it is contained within the top-level `div`.

:::warning
Heavy restyling via variables has not been tested, as I am mostly using this component as-is. If you find any issues while adjusting the colors (e.g. some colors don't change or a setting is missing) please [file an issue on GitHub](https://github.com/icehaunter/vue3-datepicker/issues).
:::

### Variables

Style can be altered significantly without editing CSS files of the components. This is done via CSS variables. Following variables are available:

| Variable name                      | Default value  | Type   |
| ---------------------------------- | -------------- | ------ |
| `--vdp-bg-color`                   | `#fff`         | color  |
| `--vdp-text-color`                 | `#000`         | color  |
| `--vdp-box-shadow`                 | See source     | shadow |
| `--vdp-border-radius`              | `3px`          | size   |
| `--vdp-heading-size`               | `2.5em`        | size   |
| `--vdp-heading-weight`             | `bold`         | weight |
| `--vdp-heading-hover-color`        | `#eeeeee`      | color  |
| `--vdp-arrow-color`                | `currentColor` | color  |
| `--vdp-elem-color`                 | `currentColor` | color  |
| `--vdp-disabled-color`             | `#d5d9e0`      | color  |
| `--vdp-hover-color`                | `#fff`         | color  |
| `--vdp-hover-bg-color`             | `#0baf74`      | color  |
| `--vdp-selected-color`             | `#fff`         | color  |
| `--vdp-selected-bg-color`          | `#0baf74`      | color  |
| `--vdp-current-date-outline-color` | `#888888`      | color  |
| `--vdp-current-date-font-weight`   | `bold`         | weight |
| `--vdp-elem-font-size`             | `0.8em`        | size   |
| `--vdp-elem-border-radius`         | `3px`          | size   |
| `--vdp-divider-color`              | `#d5d9e0`      | color  |

### Styling example and playground

:::tip
Note that variables affect only the datepicker popup. If you want to change styles for the input, just pass it in the `:style` prop or use classes.
:::

<script setup>
import Datepicker from '../src/datepicker/Datepicker.vue'
import { ref, reactive, computed } from 'vue'
const picked = ref(new Date())

const variables = reactive({
  '--vdp-bg-color': { value: '#ffffff', type: 'color' },
  '--vdp-text-color': { value: '#000000', type: 'color' },
  '--vdp-box-shadow': { value: '0 4px 10px 0 rgba(128, 144, 160, 0.1), 0 0 1px 0 rgba(128, 144, 160, 0.81)', type: 'shadow' },
  '--vdp-border-radius': { value: '3px', type: 'size' },
  '--vdp-heading-size': { value: '2.5em', type: 'size' },
  '--vdp-heading-weight': { value: 'bold', type: 'weight' },
  '--vdp-heading-hover-color': { value: '#eeeeee', type: 'color' },
  '--vdp-arrow-color': { value: 'currentColor', type: 'color' },
  '--vdp-elem-color': { value: 'currentColor', type: 'color' },
  '--vdp-disabled-color': { value: '#d5d9e0', type: 'color' },
  '--vdp-hover-color': { value: '#ffffff', type: 'color' },
  '--vdp-hover-bg-color': { value: '#0baf74', type: 'color' },
  '--vdp-selected-color': { value: '#ffffff', type: 'color' },
  '--vdp-selected-bg-color': { value: '#0baf74', type: 'color' },
  '--vdp-current-date-outline-color': { value: '#888888', type: 'color'},
  '--vdp-current-date-font-weight': { value: 'bold', type: 'weight'},
  '--vdp-elem-font-size': { value: '0.8em', type: 'size' },
  '--vdp-elem-border-radius': { value: '3px', type: 'size' },
  '--vdp-divider-color': { value: '#d5d9e0', type: 'color' },
})

const styleObj = computed(() =>
  Object.entries(variables)
    .map(([k, { value }]) => [k, value])
    .reduce((acc, [k, v]) => {
      acc[k] = v
      return acc
    }, {})
)
</script>

<br />
<Datepicker :style="{...styleObj, padding: '4px'}" v-model="picked" />

<table>
<tr v-for="(value, variable) in variables" :key="variable">
<td>
<code>{{ variable }}</code>
</td>
<td v-if="value.type === 'color'">
  <label><code>currentColor</code> <input type="checkbox" v-model="value.value" true-value="currentColor" false-value="#000000"></label>
  &nbsp;

  <input v-if="value.value !== 'currentColor'" type="color" v-model="value.value" />
</td>
<td v-else>
  <input type="text" v-model="value.value">
</td>
</tr>
</table>

<details>
  <summary>Copy serialized object with these settings</summary>
  
  <div class="language-json">
    <pre><code>{{ styleObj }}</code></pre>
  </div>
</details>


================================================
FILE: docs/examples.md
================================================
<script setup>
import Datepicker from '../src/datepicker/Datepicker.vue'
import { ref } from 'vue'
import { add } from 'date-fns'

// Ex 1
const picked = ref(new Date())

const typeable = ref(null)

// Ex 2
const example2 = ref(new Date())
const example2_from = ref(add(new Date(), { days: -7 }))
const example2_to = ref(add(new Date(), { days: 7 }))

// Disabled dates example
const pickedDate = ref(new Date())
const disabledDate = ref(add(new Date(), { days: 1 }))

// Ex 3
const example3 = ref(new Date())
const startingView = ref('day')

// Ex 4
const example4 = ref(new Date())
const minimumView = ref('day')

</script>

# Examples

## Styling

For styling examples, see [Configuration section](/config#styling-example-and-playground).

## Basic usage

<Datepicker v-model="picked" />

<details>
  <summary>Code for this example</summary>
  
  ```vue
  <script setup>
  import Datepicker from '../src/datepicker/Datepicker.vue'
  import { ref } from 'vue'
  const picked = ref(new Date())

  </script>

  <template>
    <Datepicker v-model="picked" />
  </template>
  ```
</details>

## Typeable input

<Datepicker v-model="typeable" typeable  inputFormat="yyyy-MM-d" />

<details>
  <summary>Code for this example</summary>
  
  ```vue
  <script setup>
  import Datepicker from '../src/datepicker/Datepicker.vue'
  import { ref } from 'vue'
  const picked = ref(new Date())

  </script>

  <template>
    <Datepicker v-model="typeable" typeable />
  </template>
  ```
</details>

## Upper and lower limits

:::tip
If limit is within the current "view" (e.g. a month), then view changing will be prohibited.
:::

Settings:

- Lower limit: <datepicker v-model="example2_from" />
- Upper limit: <datepicker v-model="example2_to" />

Result:

<Datepicker v-model="example2" :upper-limit="example2_to" :lower-limit="example2_from" />

<details>
  <summary>Code for this example</summary>
  
  ```vue
  <script setup>
  import Datepicker from '../src/datepicker/Datepicker.vue'
  import { ref } from 'vue'
  const example2 = ref(new Date())
  const example2_from = ref(new Date())
  const example2_to = ref(new Date())

  </script>

  <template>
    <Datepicker
      v-model="example2"
      :upper-limit="example2_to"
      :lower-limit="example2_from"
    />
  </template>
  ```
</details>

## Disabled dates

Settings:

- Disabled date: <Datepicker v-model="disabledDate" />

Result:

<Datepicker v-model="pickedDate" :disabledDates="{ dates: [disabledDate] }" />

<details>
  <summary>Code for this example</summary>
  
  ```vue
  <script setup>
  import Datepicker from '../src/datepicker/Datepicker.vue'
  import { ref } from 'vue'
  import { add } from 'date-fns'
  const pickedDate = ref(new Date())
  const disabledDate = ref(add(new Date(), { days: 1 }))
  </script>

  <template>
    <Datepicker
      v-model="pickedDate"
      :disabledDates="{ dates: [disabledDate] }"
    />
  </template>
  ```
</details>

## Starting view

Settings:

- <label>Starting view: <code>time</code> <input type="radio" v-model="startingView" value="time"></label>
- <label>Starting view: <code>day</code> <input type="radio" v-model="startingView" value="day"></label>
- <label>Starting view: <code>month</code> <input type="radio" v-model="startingView" value="month"></label>
- <label>Starting view: <code>year</code> <input type="radio" v-model="startingView" value="year"></label>

Result:
<Datepicker v-model="example3" :starting-view="startingView" :minimum-view="startingView === 'time' ? 'time' : 'day'" :inputFormat="startingView === 'time' ? 'yyyy-MM-dd HH:mm' : 'yyyy-MM-dd'" />

## Mininimal view

Settings:

- <label>Minimum view: <code>time</code> <input type="radio" v-model="minimumView" value="time"></label>
- <label>Minimum view: <code>day</code> <input type="radio" v-model="minimumView" value="day"></label>
- <label>Minimum view: <code>month</code> <input type="radio" v-model="minimumView" value="month"></label>
- <label>Minimum view: <code>year</code> <input type="radio" v-model="minimumView" value="year"></label>

Result:
<Datepicker v-model="example4" :minimum-view="minimumView" :inputFormat="minimumView === 'time' ? 'yyyy-MM-dd HH:mm' : 'yyyy-MM-dd'" />

## Clearable

<Datepicker v-model="pickedDate" :clearable="true" />

<details>
  <summary>Code for this example</summary>

```vue
<script setup>
import Datepicker from '../src/datepicker/Datepicker.vue'
import { ref } from 'vue'
const pickedDate = ref(new Date())
</script>

<template>
  <Datepicker v-model="pickedDate" :clearable="true" />
</template>
```

</details>

:::tip
We can customize clearable view with `slot` for example:
:::

<Datepicker v-model="pickedDate" :clearable="true" placeholder="placeholder">
  <template v-slot:clear="{ onClear }">
    <button @click="onClear" style="color: red">x</button>
  </template>
</Datepicker>

<details>
  <summary>Code for this example</summary>

```vue
<script setup>
import Datepicker from '../src/datepicker/Datepicker.vue'
import { ref } from 'vue'
const pickedDate = ref(new Date())
</script>

<template>
  <Datepicker v-model="pickedDate" :clearable="true" placeholder="placeholder">
    <template v-slot:clear="{ onClear }">
      <button @click="onClear" style="color: red">x</button>
    </template>
  </Datepicker>
</template>
```

</details>


================================================
FILE: docs/index.md
================================================
<script setup>
import Datepicker from '../src/datepicker/Datepicker.vue'
import { ref } from 'vue'
const picked = ref(new Date())

</script>

# Introduction

This is a reimplementation of [vuejs-datepicker](https://github.com/icehaunter/vuejs-datepicker) in Vue 3 and with greatly cleaned up code.

All date manipulation and formatting are done via the amazing [`date-fns`](https://date-fns.org/) library, so it's a direct dependency of this picker.

## Example

Datepicker comes with styling, but input itself does not. Attributes fall through to the `input` element, so you can use classes and styles as you would on any input.

<Datepicker v-model="picked" />

## Installation

Package is available on NPM: [![npm](https://img.shields.io/npm/v/vue3-datepicker)](https://www.npmjs.com/package/vue3-datepicker)

```sh
npm i vue3-datepicker
```

Then import it in your code and use as a usual component:

```vue
<script setup>
import Datepicker from 'vue3-datepicker'
import { ref } from 'vue'
const picked = ref(new Date())
</script>

<template>
  <Datepicker v-model="picked" />
</template>
```

## Compatibility

Package is transpiled and should be usable for everyone with ES6 and above, but the styling of the datepicker itself uses CSS Grid and CSS variables.

Package uses typescript and ships with TS declarations for its components.

## Props and attributes

Attribute fallthrough is enabled, so any attribute you apply to the component will be passed down to the input.

All props which accept formatting strings for dates use [`date-fns` formatting function](https://date-fns.org/docs/format) under the hood, so see that function's documentation for patterns.

Main interaction to date selection is done via `v-model` with `Date` as expected type of the value passed.

More in-depth documentation of the props, as well as examples, can be found in [Configuration](/config)

| ID                       | Type                                                     | Default              | Description                                                                                                                                |
| ------------------------ | -------------------------------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `upperLimit`             | `Date`                                                   |                      | Upper limit for available dates for picking                                                                                                |
| `lowerLimit`             | `Date`                                                   |                      | Lower limit for available dates for picking                                                                                                |
| `startingViewDate`       | `Date`                                                   | `() => new Date()`   | Date on which to focus when empty datepicker is opened. Default is "right now"                                                             |
| `disabledDates`          | `{ dates: Date[] }`                                      |                      | Dates not available for picking                                                                                                            |
| `disabledTime`           | `{ dates: Date[] }`                                      |                      | Dates not available for time picking                                                                                                       |
| `startingView`           | `'time' \| `'day' \| 'month' \| 'year'`                  | `'day'`              | View on which the date picker should open. Can be either `year`, `month`, or `day`                                                         |
| `minimumView`            | `'time' \| `'day' \| 'month' \| 'year'`                  | `'day'`              | If set, lower-level views won't show                                                                                                       |
| `dayPickerHeadingFormat` | `String`                                                 | `LLLL yyyy`          | `date-fns`-type formatting for a day view heading                                                                                          |
| `dayFormat`              | `String`                                                 | `dd`                 | `date-fns`-type formatting for each day on the day view                                                                                    |
| `weekdayFormat`          | `String`                                                 | `EE`                 | `date-fns`-type formatting for a line of weekdays on day view                                                                              |
| `inputFormat`            | `String`                                                 | `yyyy-MM-dd`         | `date-fns`-type format in which the string in the input should be both parsed and displayed                                                |
| `locale`                 | [`Locale`](https://date-fns.org/v2.16.1/docs/I18n#usage) | `date-fns/locale/en` | [`date-fns` locale object](https://date-fns.org/v2.16.1/docs/I18n#usage). Used in string formatting (see default `dayPickerHeadingFormat`) |
| `disabled`               | `Boolean`                                                | `false`              | Disables datepicker and prevents it's opening                                                                                              |
| `typeable`               | `Boolean`                                                | `false`              | Allows user to input date manually                                                                                                         |
| `weekStartsOn`           | `Number`                                                 | 1                    | Day on which the week should start. Number from 0 to 6, where 0 is Sunday and 6 is Saturday. Week starts with a Monday (1) by default      |
| `clearable`              | `Boolean`                                                | `false`              | Allows clearing the selected date and setting the value to `null`                                                                          |
| `allowOutsideInterval`   | `Boolean`                                                | `false`              | Allows user to click dates outside of current interval                                                                                     |

### Events

- `opened`: Emitted every time the popup opens, including on field focus
- `closed`: Emitted every time the popup closes, including on field blur
- `decadePageChanged`: Emitted when a page is changed on the year picker view, displaying a different decade. Has a date that is included in the shown decade as an argument.
- `yearPageChanged`: Emitted when a page is changed on the month picker view, displaying a different year. Has a date that is included in the shown year as an argument.
- `monthPageChanged`: Emitted when a page is changed on the day picker view, displaying a different month. Has a date that is included in the shown month as an argument.

## Styling

Styling is done via CSS variables, which control colors used in the popup. All variables, as well as styling example and playground can be found in [Configuration section](/config.html#styling-example-and-playground)


================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>


================================================
FILE: package.json
================================================
{
  "author": {
    "email": "icehaunter@gmail.com",
    "name": "Ilya Borovitinov"
  },
  "license": "MIT",
  "engines": {
    "node": ">=10.16.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/icehaunter/vue3-datepicker.git"
  },
  "bugs": {
    "url": "https://github.com/icehaunter/vue3-datepicker/issues"
  },
  "private": false,
  "name": "vue3-datepicker",
  "version": "0.4.0",
  "publishConfig": {
    "access": "public"
  },
  "description": "A simple Vue 3 datepicker component. Supports disabling of dates, translations. Dependent on date-fns.",
  "keywords": [
    "vue",
    "vue 3",
    "component",
    "datepicker",
    "date-picker",
    "calendar",
    "date-fns"
  ],
  "main": "dist/vue3-datepicker.umd.js",
  "module": "dist/vue3-datepicker.mjs",
  "types": "./dist/types/Datepicker.vue.d.ts",
  "files": [
    "src/datepicker",
    "dist"
  ],
  "scripts": {
    "dev": "vite",
    "dev:docs": "vitepress dev docs",
    "build:component": "vite build",
    "build:docs": "vitepress build docs",
    "preview:docs": "vitepress preview docs",
    "format": "prettier -w ."
  },
  "devDependencies": {
    "@babel/types": "^7.22.4",
    "@types/node": "^20.2.5",
    "@vitejs/plugin-vue": "^4.2.3",
    "@vue/compiler-sfc": "^3.3.4",
    "prettier": "^2.8.8",
    "typescript": "^4.8.4",
    "vite": "^4.3.9",
    "vite-plugin-css-injected-by-js": "^3.1.1",
    "vite-plugin-dts": "^2.3.0",
    "vitepress": "^1.0.0-beta.1",
    "vue": "^3.3.4"
  },
  "peerDependencies": {
    "vue": "^3.0.0"
  },
  "dependencies": {
    "date-fns": "^2.22.1"
  }
}


================================================
FILE: src/App.vue
================================================
<template>
  <img alt="Vue logo" src="./assets/logo.png" />
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      @pageChanged="
        (view, date) =>
          log(`Page changed on view ${view} to include date ${date}`)
      "
      class="picker"
      v-model="selected"
      :locale="locale"
      :upperLimit="to"
      :lowerLimit="from"
      :clearable="true"
      dayFormat="d"
      :disabledDates="{ predicate: isToday }"
    >
      <template v-slot:clear="{ onClear }">
        <button @click="onClear">x</button>
      </template>
    </datepicker>
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="time"
      :locale="locale"
      starting-view="time"
      minimum-view="time"
      inputFormat="HH:mm"
      placeholder="selectTime"
      :disabledTime="{ predicate: isMorning }"
    />
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="full"
      :locale="locale"
      minimum-view="time"
      inputFormat="HH:mm dd.MM.yyyy"
      placeholder="date & time"
      :disabledTime="{ dates: disabledTime }"
    />
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="selected"
      :locale="locale"
      :upperLimit="to"
      :lowerLimit="from"
      :clearable="true"
      :disabledDates="{ predicate: isToday }"
      :allow-outside-interval="true"
      placeholder="allow outside interval"
    />
  </div>
  <div>
    <datepicker
      class="picker"
      weekday-format="iiiiii"
      month-list-format="LLLL"
      v-model="from"
      :locale="locale"
      placeholder="from"
    />
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="to"
      placeholder="to"
    />
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="to"
      :locale="locale"
      disabled
      placeholder="disabled"
    />
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="yearSelected"
      :locale="locale"
      minimum-view="year"
      placeholder="selectYear"
    />
  </div>
  <div>
    <datepicker
      @opened="log('opened')"
      @closed="log('closed')"
      class="picker"
      v-model="monthSelected"
      :locale="locale"
      minimum-view="month"
      starting-view="month"
      placeholder="selectMonth"
      :starting-view-date="new Date(0)"
      input-format="yyyy-MM"
    />
  </div>
</template>

<script>
import Datepicker from './datepicker/Datepicker.vue'
import { defineComponent } from 'vue'
import { enUS } from 'date-fns/locale'
import { isSameDay, set } from 'date-fns'

export default defineComponent({
  name: 'App',
  components: {
    Datepicker,
  },
  data() {
    return {
      time: null,
      full: null,
      selected: null,
      from: null,
      to: null,
      yearSelected: null,
      monthSelected: null,
      disabledTime: [
        set(new Date(), { hours: 11, minutes: 12 }),
        set(new Date(), { hours: 12, minutes: 30 }),
      ],
    }
  },
  computed: {
    locale: () => enUS,
  },
  watch: {
    selected: (value) => console.log(value),
  },
  methods: {
    isToday(date) {
      return isSameDay(date, new Date())
    },
    isMorning(date) {
      const newDate = set(new Date(date.getTime()), { hours: 11, minutes: 0 })
      return date < newDate
    },
    log(data) {
      console.log(data)
    },
  },
})
</script>

<style>
.picker {
  color: #3c4a5a;
}
</style>


================================================
FILE: src/datepicker/Datepicker.vue
================================================
<template>
  <div
    class="v3dp__datepicker"
    :style="variables($attrs.style as Record<string, string> | undefined)"
  >
    <div class="v3dp__input_wrapper">
      <input
        type="text"
        ref="inputRef"
        :readonly="!typeable"
        v-model="input"
        v-bind="$attrs"
        :placeholder="placeholder"
        :disabled="disabled"
        :tabindex="disabled ? -1 : 0"
        @keyup="keyUp"
        @blur="blur"
        @focus="focus"
        @click="click"
      />
      <div class="v3dp__clearable" v-show="clearable && modelValue">
        <slot name="clear" :onClear="clearModelValue">
          <i @click="clearModelValue()">x</i>
        </slot>
      </div>
    </div>
    <year-picker
      v-show="viewShown === 'year'"
      :pageDate="pageDate"
      @update:pageDate="(v) => updatePageDate('year', v)"
      :selected="modelValue"
      :lowerLimit="lowerLimit"
      :upperLimit="upperLimit"
      @select="selectYear"
    />
    <month-picker
      v-show="viewShown === 'month'"
      :pageDate="pageDate"
      @update:pageDate="(v) => updatePageDate('month', v)"
      :selected="modelValue"
      @select="selectMonth"
      :lowerLimit="lowerLimit"
      :upperLimit="upperLimit"
      :format="monthListFormat"
      :locale="locale"
      @back="viewShown = 'year'"
    />
    <day-picker
      v-show="viewShown === 'day'"
      :pageDate="pageDate"
      @update:pageDate="(v) => updatePageDate('day', v)"
      :selected="modelValue"
      :weekStartsOn="weekStartsOn"
      :lowerLimit="lowerLimit"
      :upperLimit="upperLimit"
      :headingFormat="dayPickerHeadingFormat"
      :disabledDates="disabledDates"
      :locale="locale"
      :weekdayFormat="weekdayFormat"
      :allow-outside-interval="allowOutsideInterval"
      :format="dayFormat"
      @select="selectDay"
      @back="viewShown = 'month'"
    />
    <time-picker
      v-show="viewShown === 'time'"
      :pageDate="pageDate"
      :visible="viewShown === 'time'"
      :selected="modelValue"
      :disabledTime="disabledTime"
      @select="selectTime"
      @back="goBackFromTimepicker"
    />
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, computed, watchEffect, PropType } from 'vue'
import { parse, isValid, format, max, min } from 'date-fns'
import YearPicker from './YearPicker.vue'
import MonthPicker from './MonthPicker.vue'
import DayPicker from './DayPicker.vue'
import TimePicker from './Timepicker.vue'

const TIME_RESOLUTIONS = ['time', 'day', 'month', 'year']

const boundedDate = (
  lower: Date | undefined,
  upper: Date | undefined,
  target: Date | undefined = undefined
) => {
  let date = target || new Date()

  if (lower) date = max([lower, date])
  if (upper) date = min([upper, date])

  return date
}

export default defineComponent({
  components: {
    YearPicker,
    MonthPicker,
    DayPicker,
    TimePicker,
  },
  inheritAttrs: false,
  props: {
    placeholder: {
      type: String,
      default: '',
    },
    /**
     * `v-model` for selected date
     */
    modelValue: {
      type: Date as PropType<Date>,
      required: false,
    },
    /**
     * Dates not available for picking
     */
    disabledDates: {
      type: Object as PropType<{
        dates?: Date[]
        predicate?: (currentDate: Date) => boolean
      }>,
      required: false,
    },
    allowOutsideInterval: {
      type: Boolean,
      required: false,
      default: false,
    },
    /**
     * Time not available for picking
     */
    disabledTime: {
      type: Object as PropType<{
        dates?: Date[]
        predicate?: (currentDate: Date) => boolean
      }>,
      required: false,
    },
    /**
     * Upper limit for available dates for picking
     */
    upperLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
    /**
     * Lower limit for available dates for picking
     */
    lowerLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
    /**
     * View on which the date picker should open. Can be either `year`, `month`, `day` or `time`
     */
    startingView: {
      type: String as PropType<'year' | 'month' | 'day' | 'time'>,
      required: false,
      default: 'day',
      validate: (v: unknown) =>
        typeof v === 'string' && TIME_RESOLUTIONS.includes(v),
    },
    /**
     * Date which should be the "center" of the initial view.
     * When an empty datepicker opens, it focuses on the month/year
     * that contains this date
     */
    startingViewDate: {
      type: Date as PropType<Date>,
      required: false,
      default: () => new Date(),
    },
    /**
     * `date-fns`-type formatting for a month view heading
     */
    dayPickerHeadingFormat: {
      type: String,
      required: false,
      default: 'LLLL yyyy',
    },
    /**
     * `date-fns`-type formatting for the month picker view
     */
    monthListFormat: {
      type: String,
      required: false,
      default: 'LLL',
    },
    /**
     * `date-fns`-type formatting for a line of weekdays on day view
     */
    weekdayFormat: {
      type: String,
      required: false,
      default: 'EE',
    },
    /**
     * `date-fns`-type formatting for the day picker view
     */
    dayFormat: {
      type: String,
      required: false,
      default: 'dd',
    },
    /**
     * `date-fns`-type format in which the string in the input should be both
     * parsed and displayed
     */
    inputFormat: {
      type: String,
      required: false,
      default: 'yyyy-MM-dd',
    },
    /**
     * [`date-fns` locale object](https://date-fns.org/v2.16.1/docs/I18n#usage).
     * Used in string formatting (see default `dayPickerHeadingFormat`)
     */
    locale: {
      type: Object as PropType<Locale>,
      required: false,
    },
    /**
     * Day on which the week should start.
     *
     * Number from 0 to 6, where 0 is Sunday and 6 is Saturday.
     * Week starts with a Monday (1) by default
     */
    weekStartsOn: {
      type: Number as PropType<0 | 1 | 2 | 3 | 4 | 5 | 6>,
      required: false,
      default: 1,
      validator: (value: any) => [0, 1, 2, 3, 4, 5, 6].includes(value),
    },
    /**
     * Disables datepicker and prevents it's opening
     */
    disabled: {
      type: Boolean,
      required: false,
      default: false,
    },
    /**
     * Clears selected date
     */
    clearable: {
      type: Boolean,
      required: false,
      default: false,
    },
    /*
     * Allows user to input date manually
     */
    typeable: {
      type: Boolean,
      required: false,
      default: false,
    },
    /**
     * If set, lower-level views won't show
     */
    minimumView: {
      type: String as PropType<'year' | 'month' | 'day' | 'time'>,
      required: false,
      default: 'day',
      validate: (v: unknown) =>
        typeof v === 'string' && TIME_RESOLUTIONS.includes(v),
    },
  },
  emits: {
    'update:modelValue': (value: Date | null | undefined) =>
      value === null || value === undefined || isValid(value),
    decadePageChanged: (pageDate: Date) => true,
    yearPageChanged: (pageDate: Date) => true,
    monthPageChanged: (pageDate: Date) => true,
    opened: () => true,
    closed: () => true,
  },
  setup(props, { emit, attrs }) {
    const viewShown = ref('none' as 'year' | 'month' | 'day' | 'time' | 'none')
    const pageDate = ref<Date>(props.startingViewDate)
    const inputRef = ref(null as HTMLInputElement | null)
    const isFocused = ref(false)

    const input = ref('')
    watchEffect(() => {
      const parsed = parse(input.value, props.inputFormat, new Date(), {
        locale: props.locale,
      })
      if (isValid(parsed)) {
        pageDate.value = parsed
      }
    })

    watchEffect(
      () =>
        (input.value =
          props.modelValue && isValid(props.modelValue)
            ? format(props.modelValue, props.inputFormat, {
                locale: props.locale,
              })
            : '')
    )

    const renderView = (view: typeof viewShown.value = 'none') => {
      if (!props.disabled) {
        if (view !== 'none' && viewShown.value === 'none')
          pageDate.value =
            props.modelValue ||
            boundedDate(props.lowerLimit, props.upperLimit, pageDate.value)
        viewShown.value = view

        if (view !== 'none') {
          emit('opened')
        } else {
          emit('closed')
        }
      }
    }

    watchEffect(() => {
      if (props.disabled) viewShown.value = 'none'
    })

    const updatePageDate = (
      view: 'year' | 'month' | 'day',
      newPageDate: Date
    ) => {
      // We need to emit "page changed" event and set the page date
      pageDate.value = newPageDate

      if (view === 'year') emit('decadePageChanged', newPageDate)
      else if (view === 'month') emit('yearPageChanged', newPageDate)
      else if (view === 'day') emit('monthPageChanged', newPageDate)
    }

    const selectYear = (date: Date) => {
      pageDate.value = date

      if (props.minimumView === 'year') {
        renderView('none')
        emit('update:modelValue', date)
      } else {
        viewShown.value = 'month'
      }
    }
    const selectMonth = (date: Date) => {
      pageDate.value = date

      if (props.minimumView === 'month') {
        renderView('none')
        emit('update:modelValue', date)
      } else {
        viewShown.value = 'day'
      }
    }
    const selectDay = (date: Date) => {
      pageDate.value = date

      if (props.minimumView === 'day') {
        renderView('none')
        emit('update:modelValue', date)
      } else {
        viewShown.value = 'time'
      }
    }

    const selectTime = (date: Date) => {
      renderView('none')
      emit('update:modelValue', date)
    }

    const clearModelValue = () => {
      if (props.clearable) {
        renderView('none')
        emit('update:modelValue', null)
        pageDate.value = props.startingViewDate
      }
    }

    const click = () => (isFocused.value = true)

    const focus = () => renderView(initialView.value)

    const blur = () => {
      isFocused.value = false
      renderView()
    }

    const keyUp = (event: KeyboardEvent) => {
      const code = event.keyCode ? event.keyCode : event.which
      // close calendar if escape or enter are pressed
      const closeButton = [
        27, // escape
        13, // enter
      ].includes(code)

      if (closeButton) {
        inputRef.value!.blur()
      }
      if (props.typeable) {
        const parsedDate = parse(
          inputRef.value!.value,
          props.inputFormat,
          new Date(),
          { locale: props.locale }
        )

        // If the date is formatted back same way as it was inputted, then we're not disturbing user input
        if (
          isValid(parsedDate) &&
          input.value ===
            format(parsedDate, props.inputFormat, { locale: props.locale })
        ) {
          input.value = inputRef.value!.value
          emit('update:modelValue', parsedDate)
        }
      }
    }

    const initialView = computed(() => {
      const startingViewOrder = TIME_RESOLUTIONS.indexOf(props.startingView)
      const minimumViewOrder = TIME_RESOLUTIONS.indexOf(props.minimumView)

      return startingViewOrder < minimumViewOrder
        ? props.minimumView
        : props.startingView
    })

    const variables = (object: Record<string, string> | undefined) =>
      Object.fromEntries(
        Object.entries(object ?? {}).filter(([key, _]) => key.startsWith('--'))
      )

    const goBackFromTimepicker = () =>
      props.startingView === 'time' && props.minimumView === 'time'
        ? null
        : (viewShown.value = 'day')

    return {
      blur,
      focus,
      click,
      input,
      inputRef,
      pageDate,
      renderView,
      updatePageDate,
      selectYear,
      selectMonth,
      selectDay,
      selectTime,
      keyUp,
      viewShown,
      goBackFromTimepicker,
      clearModelValue,
      initialView,
      log: (e: any) => console.log(e),
      variables,
    }
  },
})
</script>

<style>
.v3dp__datepicker {
  --popout-bg-color: var(--vdp-bg-color, #fff);
  --box-shadow: var(
    --vdp-box-shadow,
    0 4px 10px 0 rgba(128, 144, 160, 0.1),
    0 0 1px 0 rgba(128, 144, 160, 0.81)
  );
  --text-color: var(--vdp-text-color, #000000);
  --border-radius: var(--vdp-border-radius, 3px);
  --heading-size: var(--vdp-heading-size, 2.5em); /* 40px for 16px font */
  --heading-weight: var(--vdp-heading-weight, bold);
  --heading-hover-color: var(--vdp-heading-hover-color, #eeeeee);
  --arrow-color: var(--vdp-arrow-color, currentColor);

  --elem-color: var(--vdp-elem-color, currentColor);
  --elem-disabled-color: var(--vdp-disabled-color, #d5d9e0);
  --elem-hover-color: var(--vdp-hover-color, #fff);
  --elem-hover-bg-color: var(--vdp-hover-bg-color, #0baf74);
  --elem-selected-color: var(--vdp-selected-color, #fff);
  --elem-selected-bg-color: var(--vdp-selected-bg-color, #0baf74);

  --elem-current-outline-color: var(--vdp-current-date-outline-color, #888);
  --elem-current-font-weight: var(--vdp-current-date-font-weight, bold);

  --elem-font-size: var(--vdp-elem-font-size, 0.8em);
  --elem-border-radius: var(--vdp-elem-border-radius, 3px);

  --divider-color: var(--vdp-divider-color, var(--elem-disabled-color));

  position: relative;
}

.v3dp__clearable {
  display: inline;
  position: relative;
  left: -15px;
  cursor: pointer;
}
</style>


================================================
FILE: src/datepicker/DayPicker.vue
================================================
<template>
  <picker-popup
    headingClickable
    :leftDisabled="leftDisabled"
    :rightDisabled="rightDisabled"
    :items="days"
    viewMode="day"
    @left="previousPage"
    @right="nextPage"
    @heading="$emit('back')"
    @elementClick="$emit('select', $event)"
  >
    <template #heading>{{ heading }}</template>
    <template #subheading>
      <span
        v-for="(day, index) in weekDays"
        :key="day"
        :class="`v3dp__subheading__weekday__${index}`"
      >
        {{ day }}
      </span>
    </template>
  </picker-popup>
</template>

<script lang="ts">
import { defineComponent, computed, ref, watchEffect, PropType } from 'vue'
import {
  startOfMonth,
  endOfMonth,
  eachDayOfInterval,
  subMonths,
  addMonths,
  startOfWeek,
  endOfWeek,
  isSameDay,
  setDay,
  isWithinInterval,
  isBefore,
  isAfter,
  isSameMonth,
  endOfDay,
  startOfDay,
  isValid,
  format as formatDate,
} from 'date-fns'
import PickerPopup, { Item } from './PickerPopup.vue'

export default defineComponent({
  components: {
    PickerPopup,
  },
  emits: {
    'update:pageDate': (date: Date) => isValid(date),
    select: (date: Date) => isValid(date),
    back: () => true,
  },
  props: {
    selected: {
      type: Date as PropType<Date>,
      required: false,
    },
    pageDate: {
      type: Date as PropType<Date>,
      required: true,
    },
    format: {
      type: String,
      required: false,
      default: 'dd',
    },
    headingFormat: {
      type: String,
      required: false,
      default: 'LLLL yyyy',
    },
    weekdayFormat: {
      type: String,
      required: false,
      default: 'EE',
    },
    locale: {
      type: Object as PropType<Locale>,
      required: false,
    },
    weekStartsOn: {
      type: Number as PropType<1 | 2 | 3 | 4 | 5 | 6 | 0>,
      required: false,
      default: 1,
      validator: (i: unknown): boolean =>
        typeof i === 'number' && Number.isInteger(i) && i >= 0 && i <= 6,
    },
    lowerLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
    upperLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
    disabledDates: {
      type: Object as PropType<{
        dates?: Date[]
        predicate?: (target: Date) => boolean
      }>,
      required: false,
    },
    allowOutsideInterval: {
      type: Boolean,
      required: false,
      default: false,
    },
  },
  setup(props, { emit }) {
    const format = computed(
      () => (format: string) => (value: Date | number) =>
        formatDate(value, format, {
          locale: props.locale,
          weekStartsOn: props.weekStartsOn,
        })
    )

    const monthStart = computed(() => startOfMonth(props.pageDate))
    const monthEnd = computed(() => endOfMonth(props.pageDate))
    const currentMonth = computed(() => ({
      start: monthStart.value,
      end: monthEnd.value,
    }))

    const displayedInterval = computed(() => ({
      start: startOfWeek(monthStart.value, {
        weekStartsOn: props.weekStartsOn,
      }),
      end: endOfWeek(monthEnd.value, {
        weekStartsOn: props.weekStartsOn,
      }),
    }))

    const weekDays = computed(() => {
      const initial = props.weekStartsOn
      const dayFormat = format.value(props.weekdayFormat)
      return Array.from(Array(7))
        .map((_, i) => (initial + i) % 7)
        .map((v) =>
          setDay(new Date(), v, {
            weekStartsOn: props.weekStartsOn,
          })
        )
        .map(dayFormat)
    })

    const isEnabled = (
      target: Date,
      lower?: Date,
      upper?: Date,
      disabledDates?: { dates?: Date[]; predicate?: (target: Date) => boolean }
    ): boolean => {
      if (disabledDates?.dates?.some((date) => isSameDay(target, date)))
        return false
      if (disabledDates?.predicate?.(target)) return false
      if (!lower && !upper) return true
      if (lower && isBefore(target, startOfDay(lower))) return false
      if (upper && isAfter(target, endOfDay(upper))) return false
      return true
    }

    const days = computed(() => {
      const today = new Date()
      const dayFormat = format.value(props.format)
      return eachDayOfInterval(displayedInterval.value).map(
        (value): Item => ({
          value,
          display: dayFormat(value),
          selected: !!props.selected && isSameDay(props.selected, value),
          current: isSameDay(today, value),
          disabled:
            (!props.allowOutsideInterval &&
              !isWithinInterval(value, currentMonth.value)) ||
            !isEnabled(
              value,
              props.lowerLimit,
              props.upperLimit,
              props.disabledDates
            ),
          key: format.value('yyyy-MM-dd')(value),
        })
      )
    })

    const heading = computed(() =>
      format.value(props.headingFormat)(props.pageDate)
    )
    const leftDisabled = computed(
      () =>
        props.lowerLimit &&
        (isSameMonth(props.lowerLimit, props.pageDate) ||
          isBefore(props.pageDate, props.lowerLimit))
    )
    const rightDisabled = computed(
      () =>
        props.upperLimit &&
        (isSameMonth(props.upperLimit, props.pageDate) ||
          isAfter(props.pageDate, props.upperLimit))
    )

    const previousPage = () =>
      emit('update:pageDate', subMonths(props.pageDate, 1))
    const nextPage = () => emit('update:pageDate', addMonths(props.pageDate, 1))

    return {
      weekDays,
      days,
      heading,
      leftDisabled,
      rightDisabled,
      previousPage,
      nextPage,
    }
  },
})
</script>

<style></style>


================================================
FILE: src/datepicker/MonthPicker.vue
================================================
<template>
  <picker-popup
    headingClickable
    :columnCount="3"
    :items="months"
    :leftDisabled="leftDisabled"
    :rightDisabled="rightDisabled"
    viewMode="month"
    @left="previousPage"
    @right="nextPage"
    @heading="$emit('back')"
    @elementClick="$emit('select', $event)"
  >
    <template #heading>{{ heading }}</template>
  </picker-popup>
</template>

<script lang="ts">
import { defineComponent, computed, ref, watchEffect, PropType } from 'vue'
import {
  startOfYear,
  endOfYear,
  eachMonthOfInterval,
  getMonth,
  getYear,
  subYears,
  addYears,
  format,
  isSameMonth,
  isBefore,
  isAfter,
  isSameYear,
  startOfMonth,
  endOfMonth,
  isValid,
  format as formatDate,
} from 'date-fns'
import PickerPopup, { Item } from './PickerPopup.vue'

export default defineComponent({
  components: {
    PickerPopup,
  },
  emits: {
    'update:pageDate': (date: Date) => isValid(date),
    select: (date: Date) => isValid(date),
    back: () => true,
  },
  props: {
    /**
     * Currently selected date, needed for highlighting
     */
    selected: {
      type: Date as PropType<Date>,
      required: false,
    },
    pageDate: {
      type: Date as PropType<Date>,
      required: true,
    },
    format: {
      type: String,
      required: false,
      default: 'LLL',
    },
    locale: {
      type: Object as PropType<Locale>,
      required: false,
    },
    lowerLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
    upperLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
  },
  setup(props, { emit }) {
    const from = computed(() => startOfYear(props.pageDate))
    const to = computed(() => endOfYear(props.pageDate))

    const format = computed(
      () => (value: Date | number) =>
        formatDate(value, props.format, {
          locale: props.locale,
        })
    )

    const isEnabled = (
      target: Date,
      lower: Date | undefined,
      upper: Date | undefined
    ): boolean => {
      if (!lower && !upper) return true
      if (lower && isBefore(target, startOfMonth(lower))) return false
      if (upper && isAfter(target, endOfMonth(upper))) return false
      return true
    }

    const months = computed(() =>
      eachMonthOfInterval({
        start: from.value,
        end: to.value,
      }).map(
        (value): Item => ({
          value,
          display: format.value(value),
          key: format.value(value),
          selected: !!props.selected && isSameMonth(props.selected, value),
          disabled: !isEnabled(value, props.lowerLimit, props.upperLimit),
        })
      )
    )

    const heading = computed(() => getYear(from.value))

    const leftDisabled = computed(
      () =>
        props.lowerLimit &&
        (isSameYear(props.lowerLimit, props.pageDate) ||
          isBefore(props.pageDate, props.lowerLimit))
    )
    const rightDisabled = computed(
      () =>
        props.upperLimit &&
        (isSameYear(props.upperLimit, props.pageDate) ||
          isAfter(props.pageDate, props.upperLimit))
    )

    const previousPage = () =>
      emit('update:pageDate', subYears(props.pageDate, 1))
    const nextPage = () => emit('update:pageDate', addYears(props.pageDate, 1))

    return {
      months,
      heading,
      leftDisabled,
      rightDisabled,
      previousPage,
      nextPage,
    }
  },
})
</script>


================================================
FILE: src/datepicker/PickerPopup.vue
================================================
<template>
  <div
    class="v3dp__popout"
    :class="`v3dp__popout-${viewMode}`"
    :style="{ ['--popout-column-definition' as any]: `repeat(${columnCount}, 1fr)` }"
    @mousedown.prevent
  >
    <div class="v3dp__heading">
      <button
        class="v3dp__heading__button v3dp__heading__button__left"
        :disabled="leftDisabled"
        @click.stop.prevent="$emit('left')"
      >
        <slot name="arrow-left">
          <svg
            class="v3dp__heading__icon"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 6 8"
          >
            <g fill="none" fill-rule="evenodd">
              <path stroke="none" d="M-9 16V-8h24v24z" />
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M5 0L1 4l4 4"
              />
            </g>
          </svg>
        </slot>
      </button>
      <component
        :is="headingClickable ? 'button' : 'span'"
        class="v3dp__heading__center"
        @click.stop.prevent="$emit('heading')"
      >
        <slot name="heading" />
      </component>
      <button
        class="v3dp__heading__button v3dp__heading__button__right"
        :disabled="rightDisabled"
        @click.stop.prevent="$emit('right')"
      >
        <slot name="arrow-right">
          <svg
            class="v3dp__heading__icon"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 6 8"
          >
            <g fill="none" fill-rule="evenodd">
              <path stroke="none" d="M15-8v24H-9V-8z" />
              <path
                stroke-linecap="round"
                stroke-linejoin="round"
                d="M1 8l4-4-4-4"
              />
            </g>
          </svg>
        </slot>
      </button>
    </div>
    <div class="v3dp__body">
      <template v-if="'subheading' in $slots">
        <div class="v3dp__subheading">
          <slot name="subheading" />
        </div>
        <hr class="v3dp__divider" />
      </template>
      <div class="v3dp__elements">
        <slot name="body">
          <button
            v-for="item in items"
            :key="item.key"
            :disabled="item.disabled"
            :class="[
              {
                selected: item.selected,
                current: item.current,
              },
              `v3dp__element__button__${viewMode}`,
            ]"
            @click.stop.prevent="$emit('elementClick', item.value)"
          >
            <span>{{ item.display }}</span>
          </button>
        </slot>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { isValid } from 'date-fns'
import { defineComponent, PropType, computed } from 'vue'

export interface Item {
  key: string
  value: Date
  display: number | string
  disabled: boolean
  selected: boolean
  current?: boolean
}

export type ViewMode = 'year' | 'month' | 'day' | 'time' | 'custom'
const VIEW_MODES = ['year', 'month', 'day', 'time', 'custom']

export default defineComponent({
  emits: {
    elementClick: (value: Date) => isValid(value),
    left: () => true,
    right: () => true,
    heading: () => true,
  },
  props: {
    headingClickable: {
      type: Boolean,
      default: false,
    },
    leftDisabled: {
      type: Boolean,
      default: false,
    },
    rightDisabled: {
      type: Boolean,
      default: false,
    },
    columnCount: {
      type: Number,
      default: 7,
    },
    items: {
      type: Array as PropType<Item[]>,
      default: (): Item[] => [],
    },
    viewMode: {
      type: String as PropType<ViewMode>,
      required: true,
      validate: (x: unknown) => typeof x === 'string' && VIEW_MODES.includes(x),
    },
  },
})
</script>

<style scoped>
.v3dp__popout {
  z-index: 10;
  position: absolute;
  /* bottom: 0; */
  text-align: center;
  width: 17.5em;
  background-color: var(--popout-bg-color);
  box-shadow: var(--box-shadow);
  border-radius: var(--border-radius);
  padding: 8px 0 1em;
  color: var(--text-color);
}

.v3dp__popout * {
  color: inherit;
  font-size: inherit;
  font-weight: inherit;
}

.v3dp__popout :deep(button) {
  background: none;
  border: none;
  outline: none;
}

.v3dp__popout :deep(button:not(:disabled)) {
  cursor: pointer;
}

.v3dp__heading {
  width: 100%;
  display: flex;
  height: var(--heading-size);
  line-height: var(--heading-size);
  font-weight: var(--heading-weight);
}

.v3dp__heading__button {
  background: none;
  border: none;
  padding: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  width: var(--heading-size);
}

button.v3dp__heading__center:hover,
.v3dp__heading__button:not(:disabled):hover {
  background-color: var(--heading-hover-color);
}

.v3dp__heading__center {
  flex: 1;
}

.v3dp__heading__icon {
  height: 12px;
  stroke: var(--arrow-color);
}

.v3dp__heading__button:disabled .v3dp__heading__icon {
  stroke: var(--elem-disabled-color);
}

.v3dp__subheading,
.v3dp__elements {
  display: grid;
  grid-template-columns: var(--popout-column-definition);
  font-size: var(--elem-font-size);
}

.v3dp__subheading {
  margin-top: 1em;
}

.v3dp__divider {
  border: 1px solid var(--divider-color);
  border-radius: 3px;
}

.v3dp__elements :deep(button:disabled) {
  color: var(--elem-disabled-color);
}

.v3dp__elements :deep(button) {
  padding: 0.3em 0.6em;
}

.v3dp__elements :deep(button span) {
  display: block;
  line-height: 1.9em;
  height: 1.8em;
  border-radius: var(--elem-border-radius);
}

.v3dp__elements :deep(button:not(:disabled):hover span) {
  background-color: var(--elem-hover-bg-color);
  color: var(--elem-hover-color);
}

.v3dp__elements :deep(button.selected span) {
  background-color: var(--elem-selected-bg-color);
  color: var(--elem-selected-color);
}

.v3dp__elements :deep(button.current span) {
  font-weight: var(--elem-current-font-weight);
  outline: 1px solid var(--elem-current-outline-color);
}
</style>


================================================
FILE: src/datepicker/Timepicker.vue
================================================
<template>
  <picker-popup
    headingClickable
    :columnCount="2"
    :leftDisabled="true"
    :rightDisabled="true"
    viewMode="time"
    @heading="$emit('back')"
  >
    <template #heading
      >{{ padStartZero(hours) }}:{{ padStartZero(minutes) }}</template
    >
    <template #body>
      <div ref="hoursListRef" class="v3dp__column">
        <button
          v-for="item in hoursList"
          :key="item.value"
          :ref="item.ref"
          :class="[{ selected: item.selected }, 'v3dp__element_button__hour']"
          :disabled="!isEnabled(item.date)"
          @click.stop.prevent="hours = item.value"
        >
          <span>{{ padStartZero(item.value) }}</span>
        </button>
      </div>
      <div ref="minutesListRef" class="v3dp__column">
        <button
          v-for="item in minutesList"
          :key="item.value"
          :ref="item.ref"
          :class="[{ selected: item.selected }, 'v3dp__element_button__minute']"
          :disabled="!isEnabled(item.date)"
          @click.stop.prevent="selectMinutes(item)"
        >
          <span>{{ padStartZero(item.value) }}</span>
        </button>
      </div>
    </template>
  </picker-popup>
</template>

<script lang="ts">
import {
  defineComponent,
  computed,
  ref,
  watch,
  nextTick,
  ComputedRef,
  Ref,
  PropType,
} from 'vue'
import { isSameHour, isSameMinute, isValid, set } from 'date-fns'
import PickerPopup from './PickerPopup.vue'

interface Item {
  value: number
  date: Date
  selected: boolean | undefined
  ref: Ref<null | HTMLElement>
}

function scrollParentToChild(parent: HTMLElement, child: HTMLElement) {
  const parentRect = parent.getBoundingClientRect()
  const parentViewableArea = {
    height: parent.clientHeight,
    width: parent.clientWidth,
  }

  const childRect = child.getBoundingClientRect()
  const isViewable =
    childRect.top >= parentRect.top &&
    childRect.bottom <= parentRect.top + parentViewableArea.height

  if (!isViewable) {
    const scrollTop = childRect.top - parentRect.top
    const scrollBot = childRect.bottom - parentRect.bottom
    if (Math.abs(scrollTop) < Math.abs(scrollBot)) {
      parent.scrollTop += scrollTop
    } else {
      parent.scrollTop += scrollBot
    }
  }
}

export default defineComponent({
  components: {
    PickerPopup,
  },
  emits: {
    select: (date: Date) => isValid(date),
    back: () => true,
  },
  props: {
    selected: {
      type: Date as PropType<Date>,
      required: false,
    },
    pageDate: {
      type: Date as PropType<Date>,
      required: true,
    },
    visible: {
      type: Boolean,
      required: true,
    },
    disabledTime: {
      type: Object as PropType<{
        dates?: Date[]
        predicate?: (target: Date) => boolean
      }>,
      required: false,
    },
  },
  setup(props, { emit }) {
    const hoursListRef = ref(null as HTMLElement | null)
    const minutesListRef = ref(null as HTMLElement | null)

    const currentDate = computed(() => props.pageDate ?? props.selected)

    const hours = ref(currentDate.value.getHours())
    const minutes = ref(currentDate.value.getMinutes())

    watch(
      () => props.selected,
      (value: Date | undefined) => {
        let newHours = 0
        let newMinutes = 0

        if (value) {
          newHours = value.getHours()
          newMinutes = value.getMinutes()
        }

        hours.value = newHours
        minutes.value = newMinutes
      }
    )

    const hoursList: ComputedRef<Item[]> = computed(() =>
      [...Array(24).keys()].map(
        (value): Item => ({
          value,
          date: set(new Date(currentDate.value.getTime()), {
            hours: value,
            minutes: minutes.value,
            seconds: 0,
          }),
          selected: hours.value === value,
          ref: ref(null),
        })
      )
    )
    const minutesList: ComputedRef<Item[]> = computed(() =>
      [...Array(60).keys()].map((value) => ({
        value,
        date: set(new Date(currentDate.value.getTime()), {
          hours: hours.value,
          minutes: value,
          seconds: 0,
        }),
        selected: minutes.value === value,
        ref: ref(null),
      }))
    )

    const selectMinutes = (item: Item) => {
      minutes.value = item.value

      emit('select', item.date)
    }

    const scroll = () => {
      const currentHour = hoursList.value.find(
        (item) => item.ref.value?.classList?.contains('selected') ?? false
      )
      const currentMinute = minutesList.value.find(
        (item) => item.ref.value?.classList?.contains('selected') ?? false
      )

      if (currentHour && currentMinute) {
        scrollParentToChild(hoursListRef.value!, currentHour.ref.value!)
        scrollParentToChild(minutesListRef.value!, currentMinute.ref.value!)
      }
    }

    watch(
      () => props.visible,
      (visible) => {
        if (visible) {
          nextTick(scroll)
        }
      }
    )

    const isEnabled = (target: Date): boolean => {
      if (
        props.disabledTime?.dates?.some(
          (date) => isSameHour(target, date) && isSameMinute(target, date)
        )
      ) {
        return false
      }
      if (props.disabledTime?.predicate?.(target)) return false
      return true
    }

    const padStartZero = (item: number): string => `0${item}`.substr(-2)

    return {
      hoursListRef,
      minutesListRef,
      hours,
      minutes,
      hoursList,
      minutesList,
      padStartZero,
      selectMinutes,
      isEnabled,
      scroll,
    }
  },
})
</script>

<style scoped>
.v3dp__column {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  height: 190px;
}
</style>


================================================
FILE: src/datepicker/YearPicker.vue
================================================
<template>
  <picker-popup
    :columnCount="3"
    :leftDisabled="leftDisabled"
    :rightDisabled="rightDisabled"
    :items="years"
    viewMode="year"
    @left="previousPage"
    @right="nextPage"
    @elementClick="$emit('select', $event)"
  >
    <template #heading>{{ heading }}</template>
  </picker-popup>
</template>

<script lang="ts">
import { defineComponent, computed, ref, watchEffect, PropType } from 'vue'
import {
  startOfDecade,
  endOfDecade,
  eachYearOfInterval,
  getYear,
  subYears,
  addYears,
  isAfter,
  isBefore,
  getDecade,
  isValid,
} from 'date-fns'
import PickerPopup, { Item } from './PickerPopup.vue'

export default defineComponent({
  components: {
    PickerPopup,
  },
  emits: {
    'update:pageDate': (date: Date) => isValid(date),
    select: (date: Date) => isValid(date),
  },
  props: {
    selected: {
      type: Date as PropType<Date>,
      required: false,
    },
    pageDate: {
      type: Date as PropType<Date>,
      required: true,
    },
    lowerLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
    upperLimit: {
      type: Date as PropType<Date>,
      required: false,
    },
  },
  setup(props, { emit }) {
    const from = computed(() => startOfDecade(props.pageDate))
    const to = computed(() => endOfDecade(props.pageDate))

    const isEnabled = (
      target: Date,
      lower: Date | undefined,
      upper: Date | undefined
    ): boolean => {
      if (!lower && !upper) return true
      if (lower && getYear(target) < getYear(lower)) return false
      if (upper && getYear(target) > getYear(upper)) return false
      return true
    }

    const years = computed(() =>
      eachYearOfInterval({
        start: from.value,
        end: to.value,
      }).map(
        (value): Item => ({
          value,
          key: String(getYear(value)),
          display: getYear(value),
          selected:
            !!props.selected && getYear(value) === getYear(props.selected),
          disabled: !isEnabled(value, props.lowerLimit, props.upperLimit),
        })
      )
    )

    const heading = computed(() => {
      const start = getYear(from.value)
      const end = getYear(to.value)

      return `${start} - ${end}`
    })

    const leftDisabled = computed(
      () =>
        props.lowerLimit &&
        (getDecade(props.lowerLimit) === getDecade(props.pageDate) ||
          isBefore(props.pageDate, props.lowerLimit))
    )
    const rightDisabled = computed(
      () =>
        props.upperLimit &&
        (getDecade(props.upperLimit) === getDecade(props.pageDate) ||
          isAfter(props.pageDate, props.upperLimit))
    )

    const previousPage = () =>
      emit('update:pageDate', subYears(props.pageDate, 10))
    const nextPage = () => emit('update:pageDate', addYears(props.pageDate, 10))

    return {
      years,
      heading,
      leftDisabled,
      rightDisabled,
      previousPage,
      nextPage,
    }
  },
})
</script>


================================================
FILE: src/index.css
================================================
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* text-align: center; */
  color: #2c3e50;
  margin-top: 60px;
}


================================================
FILE: src/main.js
================================================
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'

window.app = createApp(App).mount('#app')


================================================
FILE: src/vue-shim.d.ts
================================================
declare module '*.vue' {
  import { defineComponent } from 'vue'
  const Component: ReturnType<typeof defineComponent>
  export default Component
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "ES2019",
    "module": "esnext",
    "lib": ["ESNext", "DOM"],
    // this enables stricter inference for data properties on `this`
    "strict": true,
    "jsx": "preserve",
    "moduleResolution": "node",
    "declaration": true
  },
  "exclude": ["node_modules", "dist"]
}


================================================
FILE: vite.config.ts
================================================
import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
import dts from 'vite-plugin-dts'

export default defineConfig({
  build: {
    cssCodeSplit: true,
    lib: {
      // Could also be a dictionary or array of multiple entry points
      entry: resolve(__dirname, 'src/datepicker/Datepicker.vue'),
      name: 'Datepicker',
      formats: ['es', 'umd'],
      // the proper extensions will be added
      fileName: 'vue3-datepicker',
    },
    rollupOptions: {
      // make sure to externalize deps that shouldn't be bundled
      // into your library
      external: ['vue', 'date-fns', 'date-fns/fp', 'date-fns/locale'],
      output: {
        // Provide global variables to use in the UMD build
        // for externalized deps
        globals: {
          vue: 'Vue',
          'date-fns': 'date-fns',
        },
        assetFileNames: (assetInfo) => {
          if (assetInfo.name === 'style.css') return 'vue3-datepicker.css'
          return assetInfo.name!
        },
      },
    },
  },
  plugins: [
    vue(),
    cssInjectedByJsPlugin(),
    dts({
      entryRoot: 'src/datepicker',
      outputDir: 'dist/types',
    }),
  ],
  optimizeDeps: {
    include: ['date-fns/locale', 'date-fns/fp'],
  },
})
Download .txt
gitextract_cw1ibvv4/

├── .github/
│   └── workflows/
│       ├── docs-autobuild.yml
│       ├── formatting-check.yml
│       └── npm-publish.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README.md
├── docs/
│   ├── .vitepress/
│   │   ├── config.js
│   │   └── theme/
│   │       ├── custom.css
│   │       └── index.js
│   ├── config.md
│   ├── examples.md
│   └── index.md
├── index.html
├── package.json
├── src/
│   ├── App.vue
│   ├── datepicker/
│   │   ├── Datepicker.vue
│   │   ├── DayPicker.vue
│   │   ├── MonthPicker.vue
│   │   ├── PickerPopup.vue
│   │   ├── Timepicker.vue
│   │   └── YearPicker.vue
│   ├── index.css
│   ├── main.js
│   └── vue-shim.d.ts
├── tsconfig.json
└── vite.config.ts
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
  {
    "path": ".github/workflows/docs-autobuild.yml",
    "chars": 960,
    "preview": "name: Documentation release\n\non:\n  push:\n    branches: [master]\n    paths:\n      - 'docs/**'\n      - 'package.json'\n  wo"
  },
  {
    "path": ".github/workflows/formatting-check.yml",
    "chars": 323,
    "preview": "name: Verify the code\non:\n  pull_request:\n\njobs:\n  verify-formatting:\n    name: Check formatting\n    runs-on: ubuntu-lat"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "chars": 706,
    "preview": "# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created\n# For "
  },
  {
    "path": ".gitignore",
    "chars": 62,
    "preview": "node_modules\n.DS_Store\n.idea\ndist\n*.local\n**/.vitepress/cache\n"
  },
  {
    "path": ".prettierignore",
    "chars": 27,
    "preview": "**/dist\n**/.vitepress/cache"
  },
  {
    "path": ".prettierrc",
    "chars": 96,
    "preview": "{\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"vueIndentScriptAndStyle\": false,\n  \"tabWidth\": 2\n}\n"
  },
  {
    "path": "LICENSE",
    "chars": 1073,
    "preview": "MIT License\n\nCopyright (c) 2020 Ilia Borovitinov\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "README.md",
    "chars": 7278,
    "preview": "# Vue 3 Datepicker\n\nDocumentation: https://icehaunter.github.io/vue3-datepicker/\n\nThis is a basic (at least for now) rei"
  },
  {
    "path": "docs/.vitepress/config.js",
    "chars": 585,
    "preview": "module.exports = {\n  title: 'Vue 3 Datepicker',\n  description: 'Datepicker component suitable for Vue 3',\n  base: '/vue3"
  },
  {
    "path": "docs/.vitepress/theme/custom.css",
    "chars": 82,
    "preview": ".vp-doc input[type='text'] {\n  border: 1px solid lightgray;\n  padding: 4px 8px;\n}\n"
  },
  {
    "path": "docs/.vitepress/theme/index.js",
    "chars": 94,
    "preview": "import DefaultTheme from 'vitepress/theme'\nimport './custom.css'\n\nexport default DefaultTheme\n"
  },
  {
    "path": "docs/config.md",
    "chars": 8381,
    "preview": "# Configuration\n\n## Props\n\n### `v-model`\n\n- Type: `Date`\n- Required: yes\n\nThe actual date that will be selected. The com"
  },
  {
    "path": "docs/examples.md",
    "chars": 5289,
    "preview": "<script setup>\nimport Datepicker from '../src/datepicker/Datepicker.vue'\nimport { ref } from 'vue'\nimport { add } from '"
  },
  {
    "path": "docs/index.md",
    "chars": 7575,
    "preview": "<script setup>\nimport Datepicker from '../src/datepicker/Datepicker.vue'\nimport { ref } from 'vue'\nconst picked = ref(ne"
  },
  {
    "path": "index.html",
    "chars": 337,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <"
  },
  {
    "path": "package.json",
    "chars": 1597,
    "preview": "{\n  \"author\": {\n    \"email\": \"icehaunter@gmail.com\",\n    \"name\": \"Ilya Borovitinov\"\n  },\n  \"license\": \"MIT\",\n  \"engines\""
  },
  {
    "path": "src/App.vue",
    "chars": 3760,
    "preview": "<template>\n  <img alt=\"Vue logo\" src=\"./assets/logo.png\" />\n  <div>\n    <datepicker\n      @opened=\"log('opened')\"\n      "
  },
  {
    "path": "src/datepicker/Datepicker.vue",
    "chars": 13475,
    "preview": "<template>\n  <div\n    class=\"v3dp__datepicker\"\n    :style=\"variables($attrs.style as Record<string, string> | undefined)"
  },
  {
    "path": "src/datepicker/DayPicker.vue",
    "chars": 5616,
    "preview": "<template>\n  <picker-popup\n    headingClickable\n    :leftDisabled=\"leftDisabled\"\n    :rightDisabled=\"rightDisabled\"\n    "
  },
  {
    "path": "src/datepicker/MonthPicker.vue",
    "chars": 3392,
    "preview": "<template>\n  <picker-popup\n    headingClickable\n    :columnCount=\"3\"\n    :items=\"months\"\n    :leftDisabled=\"leftDisabled"
  },
  {
    "path": "src/datepicker/PickerPopup.vue",
    "chars": 5918,
    "preview": "<template>\n  <div\n    class=\"v3dp__popout\"\n    :class=\"`v3dp__popout-${viewMode}`\"\n    :style=\"{ ['--popout-column-defin"
  },
  {
    "path": "src/datepicker/Timepicker.vue",
    "chars": 5677,
    "preview": "<template>\n  <picker-popup\n    headingClickable\n    :columnCount=\"2\"\n    :leftDisabled=\"true\"\n    :rightDisabled=\"true\"\n"
  },
  {
    "path": "src/datepicker/YearPicker.vue",
    "chars": 2966,
    "preview": "<template>\n  <picker-popup\n    :columnCount=\"3\"\n    :leftDisabled=\"leftDisabled\"\n    :rightDisabled=\"rightDisabled\"\n    "
  },
  {
    "path": "src/index.css",
    "chars": 205,
    "preview": "#app {\n  font-family: Avenir, Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoot"
  },
  {
    "path": "src/main.js",
    "chars": 124,
    "preview": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport './index.css'\n\nwindow.app = createApp(App).mount('#ap"
  },
  {
    "path": "src/vue-shim.d.ts",
    "chars": 148,
    "preview": "declare module '*.vue' {\n  import { defineComponent } from 'vue'\n  const Component: ReturnType<typeof defineComponent>\n "
  },
  {
    "path": "tsconfig.json",
    "chars": 316,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2019\",\n    \"module\": \"esnext\",\n    \"lib\": [\"ESNext\", \"DOM\"],\n    // this enabl"
  },
  {
    "path": "vite.config.ts",
    "chars": 1338,
    "preview": "import { resolve } from 'path'\nimport { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport cssInject"
  }
]

About this extraction

This page contains the full source code of the icehaunter/vue3-datepicker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (75.6 KB), approximately 19.8k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!