├── .github
└── workflows
│ ├── latest.yml
│ └── release.yml
├── LICENSE
├── README.md
├── demo
├── README.md
├── package.json
├── public
│ └── index.html
├── rollup.config.js
└── src
│ ├── App.svelte
│ └── main.js
├── dist
├── minify.cmd
├── template_minifier.js
├── wc-datepicker-node.js
├── wc-datepicker-node.min.js
├── wc-datepicker.js
└── wc-datepicker.min.js
└── package.json
/.github/workflows/latest.yml:
--------------------------------------------------------------------------------
1 | name: Latest
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | verify:
7 | name: Verify
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout
11 | uses: actions/checkout@v1
12 | - name: Setup
13 | uses: actions/setup-node@v1
14 | with:
15 | node-version: 14
16 | - name: Cache
17 | uses: actions/cache@v1
18 | with:
19 | path: node_modules
20 | key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package.json') }}
21 | restore-keys: |
22 | ${{ runner.OS }}-npm-cache-
23 | - name: Install
24 | run: npm i
25 | - name: Test
26 | run: npm run test --if-present
27 | - name: Lint
28 | run: npm run lint --if-present
29 | - name: Types
30 | run: npm run types --if-present
31 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - 'v*'
7 |
8 | jobs:
9 | check:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@master
14 | - name: Setup
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: 14
18 | - name: Cache
19 | uses: actions/cache@v1
20 | with:
21 | path: node_modules
22 | key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package.json') }}
23 | restore-keys: |
24 | ${{ runner.OS }}-npm-cache-
25 | - name: Verify
26 | run: |
27 | npm i
28 | npm run preversion
29 | npm:
30 | runs-on: ubuntu-latest
31 | needs: check
32 | steps:
33 | - name: Checkout
34 | uses: actions/checkout@master
35 | - name: Publish
36 | run: |
37 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_AUTH_TOKEN }}" > ~/.npmrc
38 | npm publish --access public
39 | gh:
40 | runs-on: ubuntu-latest
41 | needs: check
42 | steps:
43 | - name: Checkout
44 | uses: actions/checkout@master
45 | - name: Publish
46 | run: |
47 | echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc
48 | ORG="$(echo '${{ github.repository }}' | cut -d'/' -f1)"
49 | echo "registry=https://npm.pkg.github.com/$ORG" > .npmrc
50 | npm publish
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 VanillaWC
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | # wc-datepicker
12 |
13 | A web component that wraps a text input element and adds date-picker functionality to it.
14 |
15 | Live demo available [here.](http://135.181.40.67/wc-datepicker/)
16 |
17 | ## Features
18 |
19 | Wc-datepicker is a stand-alone vanilla JS web component that does not use shadow DOM. The component wraps a text input element and adds date-picker functionality to it. The calendar will appear when the input element gets focus.
20 |
21 | Component features include:
22 |
23 | - highly customizable calendar layout
24 | - configurable names of months and week days
25 | - first day of week selection: sunday or monday
26 | - date format customization
27 | - initial date setting
28 | - written date input & validation
29 | - keyboard accessible calendar (tabindex)
30 | - rapid month/year switching with long press
31 | - Angular compatibility (see below)
32 |
33 | ## Including the component to an HTML file
34 |
35 | 1. Import polyfill, this is not needed for modern browsers:
36 |
37 | ```html
38 |
39 | ```
40 |
41 | 2. Import custom element:
42 |
43 | ```html
44 |
45 | ```
46 |
47 | 3. Start using it!
48 |
49 | ```html
50 |
51 |
52 |
53 | ```
54 | ## Including the component from NPM
55 |
56 | 1. Install and import polyfill, this is not needed for modern browsers:
57 |
58 | See https://www.npmjs.com/package/@webcomponents/custom-elements
59 |
60 | 2. Install wc-datepicker NPM package:
61 |
62 | ```console
63 | npm i @vanillawc/wc-datepicker
64 | ```
65 |
66 | 3. Import custom element:
67 |
68 | ```javascript
69 | import '@vanillawc/wc-datepicker'
70 | ```
71 |
72 | 4. Start using it:
73 |
74 | ```javascript
75 | var picker = document.createElement('wc-datepicker')
76 | var input = document.createElement('input')
77 | input.setAttribute('type', 'text')
78 | picker.appendChild(input)
79 | document.body.appendChild(picker)
80 | ```
81 | ## Attributes
82 |
83 | Currently component has only one custom attribute that can be assigned a value in the HTML tag:
84 |
85 | Name | Type | Description | Unit / Values | Default value
86 | -------------- | --------- | ------------------------| -------------------| --------------
87 | init-date | String | Initial date in the input field | Date in "dd.mm.yyyy" format or "current" to select current date | None
88 |
89 |
90 | Following component attributes are boolean attributes, also known as valueless attributes. The presence of a boolean attribute in the HTML tag represents the true value, and the absence of the attribute represents the false value:
91 |
92 | Name | Description | if attribute is defined | If attribute is not defined
93 | -------|-------------|-------------------------|----------------------------
94 | ignore-on-focus | Calendar appearance after the input element gets focus| Calendar won't appear| Calendar appears
95 | sunday-first | First day of the calendar week | Sunday is first | Monday is first
96 | persist-on-select | Calendar visibility after the date has been selected | Calendar won't disappear | Calendar disappears
97 | show-close-icon | Calendar close icon visibility | Icon is visible | Icon is hidden
98 |
99 | Usage examples:
100 |
101 | ```html
102 |
103 |
104 |
105 | ```
106 |
107 | ```html
108 |
109 |
110 |
111 | ```
112 |
113 |
114 | Following custom attributes can be specified at build time or dynamically at runtime:
115 |
116 | Name | Type | Description | Unit / Values | Default value
117 | --- | --- | --- | --- | ---
118 | dayNames | Array of strings | Week day names |Week day names from Monday to Sunday | Mon, Tue, Wed, Thu, Fri, Sat, Sun
119 | monthNames | Array of strings | Month names |Month names from January to December | Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
120 | ignoreOnFocus | Boolean | Calendar appearance after the input element gets focus |true for not appearing false for appearing | false
121 | sundayFirst | Boolean | Starting day of the week |true for Sunday false for Monday | false
122 | persistOnSelect | Boolean | Calendar visibility after the date has been selected |true for visible false for hidden | false
123 | showCloseIcon | Boolean | Calendar close icon visibility |true for visible false for hidden | false
124 | initDate | String | Initial date in the input field |Date in "dd.mm.yyyy" format or "current" to select current date | None
125 | longPressThreshold | Number | Month/year switch long press threshold |Milliseconds | 500
126 | longPressInterval | Number | Long press month/year switch interval |Milliseconds | 150
127 |
128 | Usage example:
129 |
130 | ```javascript
131 | var picker = document.createElement('wc-datepicker')
132 | picker.dayNames = ['Mo','Tu','We','Th','Fr','Sa','Su']
133 | picker.initDate = 'current'
134 | var input = document.createElement("input")
135 | input.setAttribute('type', 'text')
136 | picker.appendChild(input)
137 | document.body.appendChild(picker)
138 | ```
139 | **Regarding dynamic and runtime usage of the component, custom attributes should always be set before the datepicker element is appended to DOM or initialized.**
140 |
141 | Although *init()* method can be called multiple times, not all attributes can be changed after the initial init.
142 |
143 | Regarding *init-date* (*initDate*) attribute, notice that the initial date format is determined by *_returnDateString()* and *_parseAndValidateInputStr()* methods, see chapter **Date format and validation** below.
144 |
145 | ## Methods
146 |
147 | ### init()
148 | Initializes date-picker functionality. This method is called automatically when the datepicker element is appended to DOM.
149 |
150 | This method has no effect, if the element does not have an input element as a child.
151 |
152 | If the datepicker is appended to DOM before the input element is appended to datepicker, *init()* must be called to make datepicker work.
153 | ### setFocusOnCal()
154 | The calendar will appear and get the focus when this method is called.
155 |
156 | On touch UIs this method can be used to prevent the keyboard from appearing, as the text input field won't get the focus.
157 |
158 | This method has no effect, if *init()* has not been called. If *ignoreOnFocus* has been set to true, this method is the only way to make the calendar appear.
159 | ### getDateString()
160 | Returns the date as string. Default format is "mm.dd.yyyy".
161 |
162 | Returns null if the input field does not contain a valid date.
163 |
164 | Date format and validity is determined by *_returnDateString()* and *_parseAndValidateInputStr()* methods.
165 | ### getDateObject()
166 | Returns the date as standard JS date object.
167 |
168 | Returns null if the input field does not contain a valid date.
169 |
170 | **Notice:**
171 |
172 | Since datepicker returns the object date in local time, UTC getter methods should not be used when processing the returned date further. Neither *Date.toJSON()* nor *Date.toISOString()* methods should be used, as they return the date in UTC format too.
173 | ## Date format and validation
174 | Default format is "mm.dd.yyyy".
175 |
176 | Date format can be changed by modifying *_returnDateString()* method.
177 |
178 | When date is written to input field, it is validated automatically if datepicker is initialized.
179 |
180 | If the date format to be used is changed, then the validating method must be modified also.
181 |
182 | The validating method to be modified is *_parseAndValidateInputStr()*
183 |
184 | It must return an object with either 1 or 4 properties:
185 | * valid - a boolean value indicating whether the date string is valid or not
186 | * day - a number value indicating the day of month (1 - 31) of the valid date string
187 | * month - a number value indicating the month (0-11) of the valid date string
188 | * year - a number value indicating the year of the valid date string
189 |
190 | Methods *getDateString()* and *getDateObject()* can also be used for validating the date, see above.
191 | ## Events
192 | If the input element loses focus and the date string is not valid, text input element shall dispatch [invalid](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/invalid_event) event.
193 |
194 | When the date string is edited to be a valid date or a new date is selected from the calendar, text input element shall dispatch *dateselect* event, which is a non-standard custom event.
195 |
196 | *Notice that event dispatching has changed since version 0.0.3. Starting from version 0.0.4, it is the wrapped input element instead of the custom element that shall dispatch events. Custom event name has been changed from datechange to dateselect.*
197 |
198 | ## Style and layout
199 | The style is defined in the HTML template string inside the component's contructor.
200 | Styling can be moved to an external CSS file by cutting and pasting everything that's inside style tags and then removing the void tags.
201 |
202 | Calendar width and height can be adjusted by modifying font-sizes and paddings:
203 | ```css
204 | .calDayName, .calDayStyle, .calAdjacentMonthDay, #calTitle {
205 | padding:5px;
206 | font-size:20px;
207 | text-align:center;
208 | }
209 | .calCtrl {
210 | font-size:20px;
211 | padding:0px 8px;
212 | user-select:none;
213 | }
214 | ```
215 |
216 | Calendar's adjacent month day numbers can be changed to invisible by replacing the color definition in *.calAdjacentMonthDay* with
217 | ```css
218 | visibility:hidden;
219 | ```
220 |
221 | ## Angular usage
222 |
223 | Using FLUX dataflow and one-way binding:
224 |
225 | ```html
226 |
227 |
233 |
234 | ```
235 |
236 | Ignoring component's invalid event and validating date externally:
237 |
238 | ```html
239 |
240 |
246 |
247 | ```
248 |
249 | Using distinct icon/button to activate the calendar:
250 |
251 | ```html
252 |
253 |
258 |
259 |
260 |
264 |
265 | ```
266 | ## Building
267 | Unminified scripts in the dist folder can be used and modified as such, there are no build scripts available for them.
268 |
269 | Building is done by executing the minifier script minify.cmd, which is a Linux bash shell script.
270 |
271 | Minify.cmd can be found from dist folder.
272 |
273 | Building (minifying) requires [terser](https://github.com/terser/terser) command line tool to be installed. It can be installed with following command:
274 | ```console
275 | npm install terser -g
276 | ```
277 | ## Contributing
278 | Questions, suggestions and bug reports are welcome. Safari testing would be nice.
279 |
280 | ## License
281 | Copyright (c) 2019-2020 Jussi Utunen
282 |
283 | Licensed under the MIT License
284 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | *Psst — looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
2 |
3 | ---
4 |
5 | # svelte app
6 |
7 | This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
8 |
9 | To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
10 |
11 | ```bash
12 | npx degit sveltejs/template svelte-app
13 | cd svelte-app
14 | ```
15 |
16 | *Note that you will need to have [Node.js](https://nodejs.org) installed.*
17 |
18 |
19 | ## Get started
20 |
21 | Install the dependencies...
22 |
23 | ```bash
24 | cd svelte-app
25 | npm install
26 | ```
27 |
28 | ...then start [Rollup](https://rollupjs.org):
29 |
30 | ```bash
31 | npm run dev
32 | ```
33 |
34 | Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
35 |
36 | By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
37 |
38 |
39 | ## Building and running in production mode
40 |
41 | To create an optimised version of the app:
42 |
43 | ```bash
44 | npm run build
45 | ```
46 |
47 | You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
48 |
49 |
50 | ## Single-page app mode
51 |
52 | By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
53 |
54 | If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
55 |
56 | ```js
57 | "start": "sirv public --single"
58 | ```
59 |
60 |
61 | ## Deploying to the web
62 |
63 | ### With [now](https://zeit.co/now)
64 |
65 | Install `now` if you haven't already:
66 |
67 | ```bash
68 | npm install -g now
69 | ```
70 |
71 | Then, from within your project folder:
72 |
73 | ```bash
74 | cd public
75 | now deploy --name my-project
76 | ```
77 |
78 | As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
79 |
80 | ### With [surge](https://surge.sh/)
81 |
82 | Install `surge` if you haven't already:
83 |
84 | ```bash
85 | npm install -g surge
86 | ```
87 |
88 | Then, from within your project folder:
89 |
90 | ```bash
91 | npm run build
92 | surge public my-project.surge.sh
93 | ```
94 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-app",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "build": "rollup -c",
6 | "dev": "rollup -c -w",
7 | "start": "sirv public"
8 | },
9 | "devDependencies": {
10 | "rollup": "^1.12.0",
11 | "rollup-plugin-commonjs": "^10.0.0",
12 | "rollup-plugin-livereload": "^1.0.0",
13 | "rollup-plugin-node-resolve": "^5.2.0",
14 | "rollup-plugin-svelte": "^5.0.3",
15 | "rollup-plugin-terser": "^5.1.2",
16 | "svelte": "^3.0.0"
17 | },
18 | "dependencies": {
19 | "sirv-cli": "^0.4.4",
20 | "svelte-highlight": "^0.3.0",
21 | "@vanillawc/wc-datepicker": "^0.0.1"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/demo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | wc-datepicker demo
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/demo/rollup.config.js:
--------------------------------------------------------------------------------
1 | import svelte from 'rollup-plugin-svelte';
2 | import resolve from 'rollup-plugin-node-resolve';
3 | import commonjs from 'rollup-plugin-commonjs';
4 | import livereload from 'rollup-plugin-livereload';
5 | import { terser } from 'rollup-plugin-terser';
6 |
7 | const production = !process.env.ROLLUP_WATCH;
8 |
9 | export default {
10 | input: 'src/main.js',
11 | output: {
12 | sourcemap: true,
13 | format: 'iife',
14 | name: 'app',
15 | file: 'public/build/bundle.js'
16 | },
17 | plugins: [
18 | svelte({
19 | // enable run-time checks when not in production
20 | dev: !production,
21 | // we'll extract any component CSS out into
22 | // a separate file — better for performance
23 | css: css => {
24 | css.write('public/build/bundle.css');
25 | }
26 | }),
27 |
28 | // If you have external dependencies installed from
29 | // npm, you'll most likely need these plugins. In
30 | // some cases you'll need additional configuration —
31 | // consult the documentation for details:
32 | // https://github.com/rollup/rollup-plugin-commonjs
33 | resolve({
34 | browser: true,
35 | dedupe: importee => importee === 'svelte' || importee.startsWith('svelte/')
36 | }),
37 | commonjs(),
38 |
39 | // In dev mode, call `npm run start` once
40 | // the bundle has been generated
41 | !production && serve(),
42 |
43 | // Watch the `public` directory and refresh the
44 | // browser on changes when not in production
45 | !production && livereload('public'),
46 |
47 | // If we're building for production (npm run build
48 | // instead of npm run dev), minify
49 | production && terser()
50 | ],
51 | watch: {
52 | clearScreen: false
53 | }
54 | };
55 |
56 | function serve() {
57 | let started = false;
58 |
59 | return {
60 | writeBundle() {
61 | if (!started) {
62 | started = true;
63 |
64 | require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
65 | stdio: ['ignore', 'inherit', 'inherit'],
66 | shell: true
67 | });
68 | }
69 | }
70 | };
71 | }
--------------------------------------------------------------------------------
/demo/src/App.svelte:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 | {@html github}
20 |
21 |
22 |
23 |
24 | wc-datepicker web component is available from GitHub and NPM registry
25 |
26 |
27 | wc-datepicker demo
28 |
29 |
30 |
31 | Default behaviour, calendar appears when input element gets focus:
32 |