├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── LICENSE ├── README.md ├── custom-elements.json ├── demo ├── custom-input.js ├── custom-nested-input.js └── index.html ├── index.ts ├── package-lock.json ├── package.json ├── src ├── CdnManager.ts ├── LitFlatpickr.ts ├── LocaleLoader.ts ├── StyleLoader.ts ├── plugins │ └── PluginLoader.ts └── styles │ └── Themes.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [*.json] 24 | indent_size = 2 25 | 26 | [*.{html,js,md}] 27 | block_comment_start = /** 28 | block_comment = * 29 | block_comment_end = */ 30 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'import', 'html'], 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/eslint-recommended', 8 | 'plugin:@typescript-eslint/recommended', 9 | 'plugin:import/errors', 10 | 'plugin:import/warnings', 11 | ], 12 | rules: { 13 | // disable the rule for all files 14 | '@typescript-eslint/explicit-function-return-type': 'off', 15 | '@typescript-eslint/no-non-null-assertion': 'off', 16 | '@typescript-eslint/no-explicit-any': 'off', 17 | '@typescript-eslint/ban-ts-ignore': 'off', 18 | 'import/named': 'off', 19 | 'import/no-unresolved': 'off', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## editors 2 | /.idea 3 | /.vscode 4 | 5 | ## system files 6 | .DS_Store 7 | 8 | ## npm 9 | /node_modules/ 10 | /npm-debug.log 11 | 12 | ## testing 13 | /coverage/ 14 | 15 | ## temp folders 16 | /.tmp/ 17 | 18 | # build 19 | /_site/ 20 | /dist/ 21 | /out-tsc/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 lit-flatpickr 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lit-flatpickr 2 | 3 | [![npm version](https://badge.fury.io/js/lit-flatpickr.svg)](https://badge.fury.io/js/lit-flatpickr) 4 | 5 | [Flatpickr](https://github.com/flatpickr/flatpickr) for Lit Element 6 | 7 | ## Installation 8 | 9 | Lit-Flatpickr can be installed through npm 10 | 11 | ```bash 12 | npm install lit-flatpickr 13 | ``` 14 | 15 | ## Usage 16 | 17 | It is highly recommended that you use lit-flatpickr as a wrapper for your own custom input elements, to provide the 18 | best User experience for the users. 19 | 20 | ```js 21 | import 'lit-flatpickr'; 22 | import { html, LitElement } from 'lit'; 23 | 24 | class MyApp extends LitElement { 25 | getValue() { 26 | this.shadowRoot.querySelector('#my-date-picker').getValue(); 27 | } 28 | 29 | render() { 30 | return html` 31 | 40 | `; 41 | } 42 | } 43 | ``` 44 | 45 | #### Styling the input component 46 | 47 | ```js 48 | html` 49 | 55 | `; 56 | ` 57 | ``` 58 | 59 | #### Using your own custom input Web Component 60 | 61 | ```js 62 | // Web Component 63 | html` 64 | 65 | 66 | 67 | `; 68 | 69 | // HTML 70 | html` 71 | 72 |
73 | 74 |
75 |
76 | `; 77 | ``` 78 | 79 | #### Define your own CDN source 80 | 81 | ```javascript 82 | import { setCDNBase } from 'lit-flatpickr/src/CdnManager.js'; 83 | 84 | setCDNBase('https://unpkg.com/flatpickr@4.6.13/dist/'); 85 | ``` 86 | 87 | ## Properties 88 | 89 | | Property | Attribute | Type | Default | Description | 90 | | ----------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 91 | | `allowInput` | `allowInput` | `boolean` | false | Allows the user to enter a date directly input the input field. By default, direct entry is disabled. | 92 | | `altFormat` | `altFormat` | `string` | "F j, Y" | Exactly the same as date format, but for the altInput field | 93 | | `altInput` | `altInput` | `boolean` | false | Show the user a readable date (as per altFormat), but return something totally different to the server. | 94 | | `altInputClass` | `altInputClass` | `string` | "" | This class will be added to the input element created by the altInput option.
Note that altInput already inherits classes from the original input. | 95 | | `ariaDateFormat` | `ariaDateFormat` | `string` | "F j, Y" | Defines how the date will be formatted in the aria-label for calendar days, using the same tokens as dateFormat.
If you change this, you should choose a value that will make sense if a screen reader reads it out loud | 96 | | `clickOpens` | `clickOpens` | `boolean` | true | Whether clicking on the input should open the picker.
You could disable this if you wish to open the calendar manually with.open() | 97 | | `dateFormat` | `dateFormat` | `string` | "Y-m-d" | A string of characters which are used to define how the date will be displayed in the input box. | 98 | | `defaultDate` | `defaultDate` | `DateOption \| DateOption[]` | | Sets the initial selected date(s).

If you're using mode: "multiple" or a range calendar supply an Array of
Date objects or an Array of date strings which follow your dateFormat.

Otherwise, you can supply a single Date object or a date string. | 99 | | `defaultHour` | `defaultHour` | `number` | 12 | Initial value of the hour element. | 100 | | `defaultMinute` | `defaultMinute` | `number` | 0 | Initial value of the minute element. | 101 | | `disable` | `disable` | `DateLimit[]` | [] | Dates selected to be unavailable for selection. | 102 | | `disableMobile` | `disableMobile` | `boolean` | false | Set disableMobile to true to always use the non-native picker.
By default, flatpickr utilizes native datetime widgets unless certain options (e.g. disable) are used. | 103 | | `enable` | `enable` | `DateLimit[]` | [] | Dates selected to be available for selection. | 104 | | `enableSeconds` | `enableSeconds` | `boolean` | false | Enables seconds in the time picker | 105 | | `enableTime` | `enableTime` | `boolean` | false | Enables time picker | 106 | | `formatDateFn` | `formatDateFn` | `((date: Date, format: string, locale: Locale) => string) \| undefined` | | Allows using a custom date formatting function instead of the built-in
handling for date formats using dateFormat, altFormat, etc.

Function format: (date: Date, format: string, locale: Locale) => string | 107 | | `hourIncrement` | `hourIncrement` | `number` | 1 | Adjusts the step for the hour input (incl. scrolling) | 108 | | `inline` | `inline` | `boolean` | false | Displays the calendar inline | 109 | | `maxDate` | `maxDate` | `string \| number \| Date \| undefined` | | The maximum date that a user can pick to (inclusive). | 110 | | `minDate` | `minDate` | `string \| number \| Date \| undefined` | | The minimum date that a user can pick to (inclusive). | 111 | | `minuteIncrement` | `minuteIncrement` | `number` | 5 | Adjusts the step for the minute input (incl. scrolling) | 112 | | `mode` | `mode` | `"single" \| "multiple" \| "range"` | "single" | "single", "multiple", or "range" | 113 | | `nextArrow` | `nextArrow` | `string` | ">" | HTML for the arrow icon, used to switch months. | 114 | | `noCalendar` | `noCalendar` | `boolean` | false | Hides the day selection in calendar.
Use it along with enableTime to create a time picker. | 115 | | `onChange` | `onChange` | `Hook \| undefined` | | Function(s) to trigger on every date selection | 116 | | `onClose` | `onClose` | `Hook \| undefined` | | Function(s) to trigger every time the calendar is closed | 117 | | `onMonthChange` | `onMonthChange` | `Hook \| undefined` | | Function(s) to trigger every time the calendar month is changed by the user or programmatically | 118 | | `onOpen` | `onOpen` | `Hook \| undefined` | | Function(s) to trigger every time the calendar is opened | 119 | | `onReady` | `onReady` | `Hook \| undefined` | | Function(s) to trigger when the calendar is ready | 120 | | `onValueUpdate` | `onValueUpdate` | `Hook \| undefined` | | Function(s) to trigger when the input value is updated with a new date string | 121 | | `onYearChange` | `onYearChange` | `Hook \| undefined` | | Function(s) to trigger every time the calendar year is changed by the user or programmatically | 122 | | `parseDateFn` | `parseDateFn` | `((date: string, format: string) => Date) \| undefined` | | Function that expects a date string and must return a Date object.

Function format: (date: string, format: string) => string | 123 | | `position` | `position` | `"auto" \| "above" \| "below"` | "auto" | Where the calendar is rendered relative to the input | 124 | | `prevArrow` | `prevArrow` | `string` | "<" | HTML for the arrow icon, used to switch months. | 125 | | `shorthandCurrentMonth` | `shorthandCurrentMonth` | `boolean` | false | Show the month using the shorthand version (ie, Sep instead of September) | 126 | | `showMonths` | `showMonths` | `number` | 1 | The number of months showed | 127 | | `static` | `static` | `boolean` | false | Position the calendar inside the wrapper and next to the input element | 128 | | `theme` | `theme` | `"light" \| "dark" \| "material_blue" \| "material_red" \| "material_green" \| "material_orange" \| "airbnb" \| "confetti" \| "none"` | "light" | The set theme of flatpickr. Use "none" if you would like to provide custom theme on your own. | 129 | | `time_24hr` | `time_24hr` | `boolean` | false | Displays the time picker in 24 hour mode without AM/PM selection when enabled | 130 | | `weekNumbers` | `weekNumbers` | `boolean` | false | Enabled display of week numbers in calendar | 131 | | `wrap` | `wrap` | `boolean` | false | flatpickr can parse an input group of textboxes and buttons, common in Bootstrap and other frameworks.
This permits additional markup, as well as custom elements to trigger the state of the calendar. | 132 | 133 | ## Methods 134 | 135 | | Method | Type | 136 | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 137 | | `changeMonth` | `(monthNum: number, isOffset?: boolean): void` | 138 | | `clear` | `(): void` | 139 | | `close` | `(): void` | 140 | | `destroy` | `(): void` | 141 | | `formatDate` | `(dateObj: Date, formatStr: string): string` | 142 | | `getValue` | `(): string` | 143 | | `getConfig` | `(): ParsedOptions` | 144 | | `getCurrentMonth` | `(): number` | 145 | | `getCurrentYear` | `(): number` | 146 | | `getOptions` | `(): Partial` | 147 | | `getSelectedDates` | `(): Date[]` | 148 | | `init` | `(): Promise` | 149 | | `initializeComponent` | `(): void` | 150 | | `jumpToDate` | `(date: Date, triggerChange: boolean): void` | 151 | | `open` | `(): void` | 152 | | `parseDate` | `(dateStr: string, dateFormat: string): Date \| undefined` | 153 | | `redraw` | `(): void` | 154 | | `set` | `(option: "allowInput" \| "altFormat" \| "altInput" \| "altInputClass" \| "animate" \| "appendTo" \| "ariaDateFormat" \| "clickOpens" \| "closeOnSelect" \| "conjunction" \| "dateFormat" \| "defaultDate" \| ... 48 more ... \| { ...; }, value?: any): void` | 155 | | `setDate` | `(date: string \| number \| Date \| DateOption[], triggerChange: boolean, dateStrFormat: string): void` | 156 | | `toggle` | `(): void` | 157 | -------------------------------------------------------------------------------- /custom-elements.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "experimental", 3 | "tags": [ 4 | { 5 | "name": "custom-input", 6 | "path": "./demo/custom-input.js" 7 | }, 8 | { 9 | "name": "first-level-input", 10 | "path": "./demo/custom-nested-input.js" 11 | }, 12 | { 13 | "name": "second-level-input", 14 | "path": "./demo/custom-nested-input.js" 15 | }, 16 | { 17 | "name": "slotted-input", 18 | "path": "./demo/custom-nested-input.js" 19 | }, 20 | { 21 | "name": "lit-flatpickr", 22 | "path": "./src/LitFlatpickr.ts", 23 | "attributes": [ 24 | { 25 | "name": "placeholder", 26 | "description": "Placeholder text for input element provided by lit-flatpickr", 27 | "type": "string", 28 | "default": "\"\"" 29 | }, 30 | { 31 | "name": "altFormat", 32 | "description": "Exactly the same as date format, but for the altInput field", 33 | "type": "string", 34 | "default": "\"F j, Y\"" 35 | }, 36 | { 37 | "name": "altInput", 38 | "description": "Show the user a readable date (as per altFormat), but return something totally different to the server.", 39 | "type": "boolean", 40 | "default": "false" 41 | }, 42 | { 43 | "name": "altInputClass", 44 | "description": "This class will be added to the input element created by the altInput option.\nNote that altInput already inherits classes from the original input.", 45 | "type": "string", 46 | "default": "\"\"" 47 | }, 48 | { 49 | "name": "allowInput", 50 | "description": "Allows the user to enter a date directly input the input field. By default, direct entry is disabled.", 51 | "type": "boolean", 52 | "default": "false" 53 | }, 54 | { 55 | "name": "ariaDateFormat", 56 | "description": "Defines how the date will be formatted in the aria-label for calendar days, using the same tokens as dateFormat.\nIf you change this, you should choose a value that will make sense if a screen reader reads it out loud", 57 | "type": "string", 58 | "default": "\"F j, Y\"" 59 | }, 60 | { 61 | "name": "clickOpens", 62 | "description": "Whether clicking on the input should open the picker.\nYou could disable this if you wish to open the calendar manually with.open()", 63 | "type": "boolean", 64 | "default": "true" 65 | }, 66 | { 67 | "name": "dateFormat", 68 | "description": "A string of characters which are used to define how the date will be displayed in the input box.", 69 | "type": "string", 70 | "default": "\"Y-m-d\"" 71 | }, 72 | { 73 | "name": "defaultDate", 74 | "description": "Sets the initial selected date(s).\n\nIf you're using mode: \"multiple\" or a range calendar supply an Array of\nDate objects or an Array of date strings which follow your dateFormat.\n\nOtherwise, you can supply a single Date object or a date string.", 75 | "type": "DateOption|DateOption[]" 76 | }, 77 | { 78 | "name": "defaultHour", 79 | "description": "Initial value of the hour element.", 80 | "type": "number", 81 | "default": "12" 82 | }, 83 | { 84 | "name": "defaultMinute", 85 | "description": "Initial value of the minute element.", 86 | "type": "number", 87 | "default": "0" 88 | }, 89 | { 90 | "name": "disable", 91 | "description": "Dates selected to be unavailable for selection.", 92 | "type": "DateLimit[]", 93 | "default": "[]" 94 | }, 95 | { 96 | "name": "disableMobile", 97 | "description": "Set disableMobile to true to always use the non-native picker.\nBy default, flatpickr utilizes native datetime widgets unless certain options (e.g. disable) are used.", 98 | "type": "boolean", 99 | "default": "false" 100 | }, 101 | { 102 | "name": "enable", 103 | "description": "Dates selected to be available for selection.", 104 | "type": "DateLimit[] | undefined", 105 | "default": "\"undefined\"" 106 | }, 107 | { 108 | "name": "enableTime", 109 | "description": "Enables time picker", 110 | "type": "boolean", 111 | "default": "false" 112 | }, 113 | { 114 | "name": "enableSeconds", 115 | "description": "Enables seconds in the time picker", 116 | "type": "boolean", 117 | "default": "false" 118 | }, 119 | { 120 | "name": "formatDateFn", 121 | "description": "Allows using a custom date formatting function instead of the built-in\nhandling for date formats using dateFormat, altFormat, etc.\n\nFunction format: (date: Date, format: string, locale: Locale) => string", 122 | "type": "((date: Date, format: string, locale: Locale) => string) | undefined" 123 | }, 124 | { 125 | "name": "hourIncrement", 126 | "description": "Adjusts the step for the hour input (incl. scrolling)", 127 | "type": "number", 128 | "default": "1" 129 | }, 130 | { 131 | "name": "minuteIncrement", 132 | "description": "Adjusts the step for the minute input (incl. scrolling)", 133 | "type": "number", 134 | "default": "5" 135 | }, 136 | { 137 | "name": "inline", 138 | "description": "Displays the calendar inline", 139 | "type": "boolean", 140 | "default": "false" 141 | }, 142 | { 143 | "name": "maxDate", 144 | "description": "The maximum date that a user can pick to (inclusive).", 145 | "type": "string | number | Date | undefined" 146 | }, 147 | { 148 | "name": "minDate", 149 | "description": "The minimum date that a user can pick to (inclusive).", 150 | "type": "string | number | Date | undefined" 151 | }, 152 | { 153 | "name": "mode", 154 | "description": "\"single\", \"multiple\", or \"range\"", 155 | "type": "\"single\" | \"multiple\" | \"range\"", 156 | "default": "\"single\"" 157 | }, 158 | { 159 | "name": "nextArrow", 160 | "description": "HTML for the arrow icon, used to switch months.", 161 | "type": "string", 162 | "default": "\">\"" 163 | }, 164 | { 165 | "name": "prevArrow", 166 | "description": "HTML for the arrow icon, used to switch months.", 167 | "type": "string", 168 | "default": "\"<\"" 169 | }, 170 | { 171 | "name": "noCalendar", 172 | "description": "Hides the day selection in calendar.\nUse it along with enableTime to create a time picker.", 173 | "type": "boolean", 174 | "default": "false" 175 | }, 176 | { 177 | "name": "onChange", 178 | "description": "Function(s) to trigger on every date selection", 179 | "type": "Hook | undefined" 180 | }, 181 | { 182 | "name": "onClose", 183 | "description": "Function(s) to trigger every time the calendar is closed", 184 | "type": "Hook | undefined" 185 | }, 186 | { 187 | "name": "onOpen", 188 | "description": "Function(s) to trigger every time the calendar is opened", 189 | "type": "Hook | undefined" 190 | }, 191 | { 192 | "name": "onReady", 193 | "description": "Function(s) to trigger when the calendar is ready", 194 | "type": "Hook | undefined" 195 | }, 196 | { 197 | "name": "onMonthChange", 198 | "description": "Function(s) to trigger every time the calendar month is changed by the user or programmatically", 199 | "type": "Hook | undefined" 200 | }, 201 | { 202 | "name": "onYearChange", 203 | "description": "Function(s) to trigger every time the calendar year is changed by the user or programmatically", 204 | "type": "Hook | undefined" 205 | }, 206 | { 207 | "name": "onValueUpdate", 208 | "description": "Function(s) to trigger when the input value is updated with a new date string", 209 | "type": "Hook | undefined" 210 | }, 211 | { 212 | "name": "parseDateFn", 213 | "description": "Function that expects a date string and must return a Date object.\n\nFunction format: (date: string, format: string) => string", 214 | "type": "((date: string, format: string) => Date) | undefined" 215 | }, 216 | { 217 | "name": "position", 218 | "description": "Where the calendar is rendered relative to the input", 219 | "type": "\"auto\" | \"above\" | \"below\"", 220 | "default": "\"auto\"" 221 | }, 222 | { 223 | "name": "shorthandCurrentMonth", 224 | "description": "Show the month using the shorthand version (ie, Sep instead of September)", 225 | "type": "boolean", 226 | "default": "false" 227 | }, 228 | { 229 | "name": "showMonths", 230 | "description": "The number of months showed", 231 | "type": "number", 232 | "default": "1" 233 | }, 234 | { 235 | "name": "static", 236 | "description": "Position the calendar inside the wrapper and next to the input element", 237 | "type": "boolean", 238 | "default": "false" 239 | }, 240 | { 241 | "name": "time_24hr", 242 | "description": "Displays the time picker in 24 hour mode without AM/PM selection when enabled", 243 | "type": "boolean", 244 | "default": "false" 245 | }, 246 | { 247 | "name": "weekNumbers", 248 | "description": "Enabled display of week numbers in calendar", 249 | "type": "boolean", 250 | "default": "false" 251 | }, 252 | { 253 | "name": "wrap", 254 | "description": "flatpickr can parse an input group of textboxes and buttons, common in Bootstrap and other frameworks.\nThis permits additional markup, as well as custom elements to trigger the state of the calendar.", 255 | "type": "boolean", 256 | "default": "false" 257 | }, 258 | { 259 | "name": "theme", 260 | "description": "The set theme of flatpickr.", 261 | "type": " \"light\" | \"dark\" | \"material_blue\" | \"material_red\" | \"material_green\" | \"material_orange\" | \"airbnb\" | \"confetti\" | \"none\" ", 262 | "default": "\"light\"" 263 | }, 264 | { 265 | "name": "firstDayOfWeek", 266 | "type": "number", 267 | "default": "1" 268 | }, 269 | { 270 | "name": "locale", 271 | "type": "string | undefined" 272 | }, 273 | { 274 | "name": "default-to-today", 275 | "type": "boolean", 276 | "default": "false" 277 | } 278 | ], 279 | "properties": [ 280 | { 281 | "name": "placeholder", 282 | "attribute": "placeholder", 283 | "description": "Placeholder text for input element provided by lit-flatpickr", 284 | "type": "string", 285 | "default": "\"\"" 286 | }, 287 | { 288 | "name": "altFormat", 289 | "attribute": "altFormat", 290 | "description": "Exactly the same as date format, but for the altInput field", 291 | "type": "string", 292 | "default": "\"F j, Y\"" 293 | }, 294 | { 295 | "name": "altInput", 296 | "attribute": "altInput", 297 | "description": "Show the user a readable date (as per altFormat), but return something totally different to the server.", 298 | "type": "boolean", 299 | "default": "false" 300 | }, 301 | { 302 | "name": "altInputClass", 303 | "attribute": "altInputClass", 304 | "description": "This class will be added to the input element created by the altInput option.\nNote that altInput already inherits classes from the original input.", 305 | "type": "string", 306 | "default": "\"\"" 307 | }, 308 | { 309 | "name": "allowInput", 310 | "attribute": "allowInput", 311 | "description": "Allows the user to enter a date directly input the input field. By default, direct entry is disabled.", 312 | "type": "boolean", 313 | "default": "false" 314 | }, 315 | { 316 | "name": "ariaDateFormat", 317 | "attribute": "ariaDateFormat", 318 | "description": "Defines how the date will be formatted in the aria-label for calendar days, using the same tokens as dateFormat.\nIf you change this, you should choose a value that will make sense if a screen reader reads it out loud", 319 | "type": "string", 320 | "default": "\"F j, Y\"" 321 | }, 322 | { 323 | "name": "clickOpens", 324 | "attribute": "clickOpens", 325 | "description": "Whether clicking on the input should open the picker.\nYou could disable this if you wish to open the calendar manually with.open()", 326 | "type": "boolean", 327 | "default": "true" 328 | }, 329 | { 330 | "name": "dateFormat", 331 | "attribute": "dateFormat", 332 | "description": "A string of characters which are used to define how the date will be displayed in the input box.", 333 | "type": "string", 334 | "default": "\"Y-m-d\"" 335 | }, 336 | { 337 | "name": "defaultDate", 338 | "attribute": "defaultDate", 339 | "description": "Sets the initial selected date(s).\n\nIf you're using mode: \"multiple\" or a range calendar supply an Array of\nDate objects or an Array of date strings which follow your dateFormat.\n\nOtherwise, you can supply a single Date object or a date string.", 340 | "type": "DateOption|DateOption[]" 341 | }, 342 | { 343 | "name": "defaultHour", 344 | "attribute": "defaultHour", 345 | "description": "Initial value of the hour element.", 346 | "type": "number", 347 | "default": "12" 348 | }, 349 | { 350 | "name": "defaultMinute", 351 | "attribute": "defaultMinute", 352 | "description": "Initial value of the minute element.", 353 | "type": "number", 354 | "default": "0" 355 | }, 356 | { 357 | "name": "disable", 358 | "attribute": "disable", 359 | "description": "Dates selected to be unavailable for selection.", 360 | "type": "DateLimit[]", 361 | "default": "[]" 362 | }, 363 | { 364 | "name": "disableMobile", 365 | "attribute": "disableMobile", 366 | "description": "Set disableMobile to true to always use the non-native picker.\nBy default, flatpickr utilizes native datetime widgets unless certain options (e.g. disable) are used.", 367 | "type": "boolean", 368 | "default": "false" 369 | }, 370 | { 371 | "name": "enable", 372 | "attribute": "enable", 373 | "description": "Dates selected to be available for selection.", 374 | "type": "DateLimit[] | undefined", 375 | "default": "\"undefined\"" 376 | }, 377 | { 378 | "name": "enableTime", 379 | "attribute": "enableTime", 380 | "description": "Enables time picker", 381 | "type": "boolean", 382 | "default": "false" 383 | }, 384 | { 385 | "name": "enableSeconds", 386 | "attribute": "enableSeconds", 387 | "description": "Enables seconds in the time picker", 388 | "type": "boolean", 389 | "default": "false" 390 | }, 391 | { 392 | "name": "formatDateFn", 393 | "attribute": "formatDateFn", 394 | "description": "Allows using a custom date formatting function instead of the built-in\nhandling for date formats using dateFormat, altFormat, etc.\n\nFunction format: (date: Date, format: string, locale: Locale) => string", 395 | "type": "((date: Date, format: string, locale: Locale) => string) | undefined" 396 | }, 397 | { 398 | "name": "hourIncrement", 399 | "attribute": "hourIncrement", 400 | "description": "Adjusts the step for the hour input (incl. scrolling)", 401 | "type": "number", 402 | "default": "1" 403 | }, 404 | { 405 | "name": "minuteIncrement", 406 | "attribute": "minuteIncrement", 407 | "description": "Adjusts the step for the minute input (incl. scrolling)", 408 | "type": "number", 409 | "default": "5" 410 | }, 411 | { 412 | "name": "inline", 413 | "attribute": "inline", 414 | "description": "Displays the calendar inline", 415 | "type": "boolean", 416 | "default": "false" 417 | }, 418 | { 419 | "name": "maxDate", 420 | "attribute": "maxDate", 421 | "description": "The maximum date that a user can pick to (inclusive).", 422 | "type": "string | number | Date | undefined" 423 | }, 424 | { 425 | "name": "minDate", 426 | "attribute": "minDate", 427 | "description": "The minimum date that a user can pick to (inclusive).", 428 | "type": "string | number | Date | undefined" 429 | }, 430 | { 431 | "name": "mode", 432 | "attribute": "mode", 433 | "description": "\"single\", \"multiple\", or \"range\"", 434 | "type": "\"single\" | \"multiple\" | \"range\"", 435 | "default": "\"single\"" 436 | }, 437 | { 438 | "name": "nextArrow", 439 | "attribute": "nextArrow", 440 | "description": "HTML for the arrow icon, used to switch months.", 441 | "type": "string", 442 | "default": "\">\"" 443 | }, 444 | { 445 | "name": "prevArrow", 446 | "attribute": "prevArrow", 447 | "description": "HTML for the arrow icon, used to switch months.", 448 | "type": "string", 449 | "default": "\"<\"" 450 | }, 451 | { 452 | "name": "noCalendar", 453 | "attribute": "noCalendar", 454 | "description": "Hides the day selection in calendar.\nUse it along with enableTime to create a time picker.", 455 | "type": "boolean", 456 | "default": "false" 457 | }, 458 | { 459 | "name": "onChange", 460 | "attribute": "onChange", 461 | "description": "Function(s) to trigger on every date selection", 462 | "type": "Hook | undefined" 463 | }, 464 | { 465 | "name": "onClose", 466 | "attribute": "onClose", 467 | "description": "Function(s) to trigger every time the calendar is closed", 468 | "type": "Hook | undefined" 469 | }, 470 | { 471 | "name": "onOpen", 472 | "attribute": "onOpen", 473 | "description": "Function(s) to trigger every time the calendar is opened", 474 | "type": "Hook | undefined" 475 | }, 476 | { 477 | "name": "onReady", 478 | "attribute": "onReady", 479 | "description": "Function(s) to trigger when the calendar is ready", 480 | "type": "Hook | undefined" 481 | }, 482 | { 483 | "name": "onMonthChange", 484 | "attribute": "onMonthChange", 485 | "description": "Function(s) to trigger every time the calendar month is changed by the user or programmatically", 486 | "type": "Hook | undefined" 487 | }, 488 | { 489 | "name": "onYearChange", 490 | "attribute": "onYearChange", 491 | "description": "Function(s) to trigger every time the calendar year is changed by the user or programmatically", 492 | "type": "Hook | undefined" 493 | }, 494 | { 495 | "name": "onValueUpdate", 496 | "attribute": "onValueUpdate", 497 | "description": "Function(s) to trigger when the input value is updated with a new date string", 498 | "type": "Hook | undefined" 499 | }, 500 | { 501 | "name": "parseDateFn", 502 | "attribute": "parseDateFn", 503 | "description": "Function that expects a date string and must return a Date object.\n\nFunction format: (date: string, format: string) => string", 504 | "type": "((date: string, format: string) => Date) | undefined" 505 | }, 506 | { 507 | "name": "position", 508 | "attribute": "position", 509 | "description": "Where the calendar is rendered relative to the input", 510 | "type": "\"auto\" | \"above\" | \"below\"", 511 | "default": "\"auto\"" 512 | }, 513 | { 514 | "name": "shorthandCurrentMonth", 515 | "attribute": "shorthandCurrentMonth", 516 | "description": "Show the month using the shorthand version (ie, Sep instead of September)", 517 | "type": "boolean", 518 | "default": "false" 519 | }, 520 | { 521 | "name": "showMonths", 522 | "attribute": "showMonths", 523 | "description": "The number of months showed", 524 | "type": "number", 525 | "default": "1" 526 | }, 527 | { 528 | "name": "static", 529 | "attribute": "static", 530 | "description": "Position the calendar inside the wrapper and next to the input element", 531 | "type": "boolean", 532 | "default": "false" 533 | }, 534 | { 535 | "name": "time_24hr", 536 | "attribute": "time_24hr", 537 | "description": "Displays the time picker in 24 hour mode without AM/PM selection when enabled", 538 | "type": "boolean", 539 | "default": "false" 540 | }, 541 | { 542 | "name": "weekNumbers", 543 | "attribute": "weekNumbers", 544 | "description": "Enabled display of week numbers in calendar", 545 | "type": "boolean", 546 | "default": "false" 547 | }, 548 | { 549 | "name": "wrap", 550 | "attribute": "wrap", 551 | "description": "flatpickr can parse an input group of textboxes and buttons, common in Bootstrap and other frameworks.\nThis permits additional markup, as well as custom elements to trigger the state of the calendar.", 552 | "type": "boolean", 553 | "default": "false" 554 | }, 555 | { 556 | "name": "theme", 557 | "attribute": "theme", 558 | "description": "The set theme of flatpickr.", 559 | "type": " \"light\" | \"dark\" | \"material_blue\" | \"material_red\" | \"material_green\" | \"material_orange\" | \"airbnb\" | \"confetti\" | \"none\" ", 560 | "default": "\"light\"" 561 | }, 562 | { 563 | "name": "firstDayOfWeek", 564 | "attribute": "firstDayOfWeek", 565 | "type": "number", 566 | "default": "1" 567 | }, 568 | { 569 | "name": "locale", 570 | "attribute": "locale", 571 | "type": "string | undefined" 572 | }, 573 | { 574 | "name": "defaultToToday", 575 | "attribute": "default-to-today", 576 | "type": "boolean", 577 | "default": "false" 578 | } 579 | ] 580 | } 581 | ] 582 | } -------------------------------------------------------------------------------- /demo/custom-input.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from 'lit'; 2 | 3 | export default class CustomInput extends LitElement { 4 | render() { 5 | return html`
`; 6 | } 7 | } 8 | 9 | if (!customElements.get('custom-input')) { 10 | customElements.define('custom-input', CustomInput); 11 | } 12 | -------------------------------------------------------------------------------- /demo/custom-nested-input.js: -------------------------------------------------------------------------------- 1 | import { LitElement, html } from 'lit'; 2 | import './custom-input'; 3 | 4 | class FirstLevelInput extends LitElement { 5 | render() { 6 | return html``; 7 | } 8 | } 9 | 10 | class SecondLevelInput extends LitElement { 11 | render() { 12 | return html``; 13 | } 14 | } 15 | 16 | class SlottedInput extends LitElement { 17 | render() { 18 | return html``; 19 | } 20 | } 21 | 22 | if (!customElements.get('first-level-input')) { 23 | customElements.define('first-level-input', FirstLevelInput); 24 | } 25 | 26 | if (!customElements.get('second-level-input')) { 27 | customElements.define('second-level-input', SecondLevelInput); 28 | } 29 | 30 | if (!customElements.get('slotted-input')) { 31 | customElements.define('slotted-input', SlottedInput); 32 | } 33 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 |

With default input

23 | 33 | 34 |

With custom locale

35 | 46 | 47 |

With custom first day of the week

48 | 58 | 59 |

With a custom input

60 | 68 |
69 | 70 |
71 |
72 | 73 |

With custom web component

74 | 75 | 76 | 77 | 78 |

With Custom nested web components

79 | 80 | 81 | 82 | 83 |

With Custom nested web components inserting to slots

84 | 85 | 86 | 87 | 88 |

With a Week Select Plugin

89 | 90 |
91 | 92 |
93 |
94 | 95 |

With a Month Select Plugin

96 | 97 |
98 | 99 |
100 |
101 | 102 |

With a Month Select Plugin and formatting

103 | 104 |
105 | 106 |
107 |
108 | 109 |

With a Month Select Plugin and formatting and localization

110 | 111 |
112 | 113 |
114 |
115 | 116 |

Range selection

117 | 118 |
119 | 120 |
121 |
122 | 123 | 124 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export { LitFlatpickr } from './src/LitFlatpickr.js'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lit-flatpickr", 3 | "version": "0.4.0", 4 | "description": "A Flatpickr port to Lit Element", 5 | "repository": "https://github.com/Matsuuu/lit-flatpickr", 6 | "author": "Matsuuu", 7 | "license": "MIT", 8 | "main": "dist/index.js", 9 | "module": "dist/index.js", 10 | "files": [ 11 | "dist/**/*", 12 | "custom-elements.json" 13 | ], 14 | "scripts": { 15 | "start": "concurrently --kill-others --names tsc,dev-server \"npm run tsc:watch\" \"wds --app-index demo/index.html --node-resolve --watch --open\"", 16 | "tsc:watch": "tsc --watch", 17 | "lint:eslint": "eslint --ext .ts,.html . --ignore-path .gitignore", 18 | "format:eslint": "eslint --ext .ts,.html . --fix --ignore-path .gitignore", 19 | "lint:prettier": "prettier \"**/*.js\" \"**/*.ts\" --check --ignore-path .gitignore", 20 | "format:prettier": "prettier \"**/*.js\" \"**/*.ts\" --write --ignore-path .gitignore", 21 | "lint": "npm run lint:eslint && npm run lint:prettier", 22 | "format": "npm run format:eslint && npm run format:prettier", 23 | "analyze": "wca analyze --format json --outFile custom-elements.json", 24 | "build": "tsc --build tsconfig.json" 25 | }, 26 | "dependencies": { 27 | "flatpickr": "^4.6.13", 28 | "lit": "^2.0.0", 29 | "tslib": "^1.11.0" 30 | }, 31 | "devDependencies": { 32 | "@open-wc/eslint-config": "^2.0.0", 33 | "@types/node": "13.11.1", 34 | "@typescript-eslint/eslint-plugin": "^2.20.0", 35 | "@typescript-eslint/parser": "^2.20.0", 36 | "@web/dev-server": "^0.1.25", 37 | "concurrently": "^5.1.0", 38 | "eslint": "^6.1.0", 39 | "eslint-config-prettier": "^6.11.0", 40 | "husky": "^1.0.0", 41 | "lint-staged": "^8.0.0", 42 | "prettier": "^2.0.4", 43 | "typescript": "~3.8.2", 44 | "web-component-analyzer": "^1.1.6" 45 | }, 46 | "eslintConfig": { 47 | "extends": [ 48 | "@open-wc/eslint-config", 49 | "eslint-config-prettier" 50 | ] 51 | }, 52 | "prettier": { 53 | "singleQuote": true, 54 | "arrowParens": "avoid", 55 | "printWidth": 120 56 | }, 57 | "husky": { 58 | "hooks": { 59 | "pre-commit": "lint-staged" 60 | } 61 | }, 62 | "lint-staged": { 63 | "*.ts": [ 64 | "eslint --fix", 65 | "prettier --write", 66 | "git add" 67 | ] 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CdnManager.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | const FLATPICKR_VERSION = window._FLATPICKR_VERSION || '4.6.13'; 3 | 4 | let CDN_BASE = `https://unpkg.com/flatpickr@${FLATPICKR_VERSION}/dist/`; 5 | 6 | export function setCDNBase(cdnBaseUrl: string) { 7 | CDN_BASE = cdnBaseUrl; 8 | } 9 | 10 | export function getCDNBase() { 11 | return CDN_BASE; 12 | } 13 | -------------------------------------------------------------------------------- /src/LitFlatpickr.ts: -------------------------------------------------------------------------------- 1 | import { html, LitElement, css } from 'lit'; 2 | import { customElement, property } from 'lit/decorators.js'; 3 | import 'flatpickr'; 4 | import { FlatpickrTheme } from './styles/Themes'; 5 | import StyleLoader from './StyleLoader'; 6 | import { DateLimit, DateOption, Hook, Options, ParsedOptions } from 'flatpickr/dist/types/options'; 7 | import { Locale } from 'flatpickr/dist/types/locale'; 8 | import { Instance } from 'flatpickr/dist/types/instance'; 9 | import { loadLocale } from './LocaleLoader'; 10 | import { loadPlugins } from './plugins/PluginLoader'; 11 | 12 | /* eslint-disable @typescript-eslint/no-explicit-any */ 13 | declare const flatpickr: any; 14 | 15 | @customElement('lit-flatpickr') 16 | export class LitFlatpickr extends LitElement { 17 | /** 18 | * Placeholder text for input element provided by lit-flatpickr 19 | * */ 20 | @property({ type: String }) 21 | placeholder = ''; 22 | /** 23 | * Exactly the same as date format, but for the altInput field 24 | * @prop 25 | * @type string 26 | **/ 27 | @property({ type: String }) 28 | altFormat = 'F j, Y'; 29 | /** 30 | * Show the user a readable date (as per altFormat), but return something totally different to the server. 31 | * @prop 32 | * @type boolean 33 | * */ 34 | @property({ type: Boolean }) 35 | altInput = false; 36 | /** 37 | * This class will be added to the input element created by the altInput option. 38 | * Note that altInput already inherits classes from the original input. 39 | * @prop 40 | * @type string 41 | * */ 42 | @property({ type: String }) 43 | altInputClass = ''; 44 | /** 45 | * Allows the user to enter a date directly input the input field. By default, direct entry is disabled. 46 | * @prop 47 | * @type boolean 48 | **/ 49 | @property({ type: Boolean }) 50 | allowInput = false; 51 | /** 52 | * Defines how the date will be formatted in the aria-label for calendar days, using the same tokens as dateFormat. 53 | * If you change this, you should choose a value that will make sense if a screen reader reads it out loud 54 | * @prop 55 | * @type string 56 | **/ 57 | @property({ type: String }) 58 | ariaDateFormat = 'F j, Y'; 59 | 60 | /** 61 | * Whether clicking on the input should open the picker. 62 | * You could disable this if you wish to open the calendar manually with.open() 63 | * @prop 64 | * @type boolean 65 | * */ 66 | @property({ type: Boolean }) 67 | clickOpens = true; 68 | 69 | /** 70 | * A string of characters which are used to define how the date will be displayed in the input box. 71 | * @prop 72 | * @type string 73 | * */ 74 | @property({ type: String }) 75 | dateFormat = 'Y-m-d'; 76 | 77 | /** 78 | * Sets the initial selected date(s). 79 | * 80 | * If you're using mode: "multiple" or a range calendar supply an Array of 81 | * Date objects or an Array of date strings which follow your dateFormat. 82 | * 83 | * Otherwise, you can supply a single Date object or a date string. 84 | * @prop 85 | * @type {DateOption|DateOption[]} 86 | * */ 87 | @property({ type: Object }) 88 | defaultDate?: DateOption | DateOption[]; 89 | 90 | /** 91 | * Initial value of the hour element. 92 | * @prop 93 | * @type number 94 | * */ 95 | @property({ type: Number }) 96 | defaultHour = 12; 97 | 98 | /** 99 | * Initial value of the minute element. 100 | * @prop 101 | * @type number 102 | * */ 103 | @property({ type: Number }) 104 | defaultMinute = 0; 105 | 106 | /** 107 | * Dates selected to be unavailable for selection. 108 | * @prop 109 | * @type DateLimit[] 110 | * */ 111 | @property({ type: Array }) 112 | disable: DateLimit[] = []; 113 | 114 | /** 115 | * Set disableMobile to true to always use the non-native picker. 116 | * By default, flatpickr utilizes native datetime widgets unless certain options (e.g. disable) are used. 117 | * @prop 118 | * @type boolean 119 | * */ 120 | @property({ type: Boolean }) 121 | disableMobile = false; 122 | 123 | /** 124 | * Dates selected to be available for selection. 125 | * @prop 126 | * @type DateLimit[] 127 | * */ 128 | @property({ type: Array }) 129 | enable: DateLimit[] | undefined = undefined; 130 | 131 | /** 132 | * Enables time picker 133 | * @prop 134 | * @type boolean 135 | * */ 136 | @property({ type: Boolean }) 137 | enableTime = false; 138 | 139 | /** 140 | * Enables seconds in the time picker 141 | * @prop 142 | * @type boolean 143 | * */ 144 | @property({ type: Boolean }) 145 | enableSeconds = false; 146 | 147 | /** 148 | * Allows using a custom date formatting function instead of the built-in 149 | * handling for date formats using dateFormat, altFormat, etc. 150 | * 151 | * Function format: (date: Date, format: string, locale: Locale) => string 152 | * 153 | * @prop 154 | * @type Function 155 | * */ 156 | @property({ type: Function }) 157 | formatDateFn?: (date: Date, format: string, locale: Locale) => string; 158 | 159 | /** 160 | * Adjusts the step for the hour input (incl. scrolling) 161 | * @prop 162 | * @type number 163 | * */ 164 | @property({ type: Number }) 165 | hourIncrement = 1; 166 | 167 | /** 168 | * Adjusts the step for the minute input (incl. scrolling) 169 | * @prop 170 | * @type number 171 | * */ 172 | @property({ type: Number }) 173 | minuteIncrement = 5; 174 | 175 | /** 176 | * Displays the calendar inline 177 | * @prop 178 | * @type boolean 179 | * */ 180 | @property({ type: Boolean }) 181 | inline = false; 182 | 183 | /** 184 | * The maximum date that a user can pick to (inclusive). 185 | * @prop 186 | * @type DateOption 187 | * */ 188 | @property({ type: String }) 189 | maxDate?: DateOption; 190 | 191 | /** 192 | * The minimum date that a user can pick to (inclusive). 193 | * @prop 194 | * @type DateOption 195 | * */ 196 | @property({ type: String }) 197 | minDate?: DateOption; 198 | 199 | /** 200 | * "single", "multiple", "time" or "range" 201 | * @prop 202 | * @type {"single" | "multiple" | "range"} 203 | * */ 204 | @property({ type: String }) 205 | mode: 'single' | 'multiple' | 'range' | 'time' = 'single'; 206 | 207 | /** 208 | * HTML for the arrow icon, used to switch months. 209 | * @prop 210 | * @type string 211 | * */ 212 | @property({ type: String }) 213 | nextArrow = '>'; 214 | 215 | /** 216 | * HTML for the arrow icon, used to switch months. 217 | * @prop 218 | * @type string 219 | * */ 220 | @property({ type: String }) 221 | prevArrow = '<'; 222 | 223 | /** 224 | * Hides the day selection in calendar. 225 | * Use it along with enableTime to create a time picker. 226 | * @prop 227 | * @type boolean 228 | * */ 229 | @property({ type: Boolean }) 230 | noCalendar = false; 231 | 232 | /** 233 | * Function(s) to trigger on every date selection 234 | * @prop 235 | * @type Function 236 | * */ 237 | @property({ type: Function }) 238 | onChange?: Hook; 239 | 240 | /** 241 | * Function(s) to trigger every time the calendar is closed 242 | * @prop 243 | * @type Function 244 | * */ 245 | @property({ type: Function }) 246 | onClose?: Hook; 247 | 248 | /** 249 | * Function(s) to trigger every time the calendar is opened 250 | * @prop 251 | * @type Function 252 | * */ 253 | @property({ type: Function }) 254 | onOpen?: Hook; 255 | 256 | /** 257 | * Function(s) to trigger when the calendar is ready 258 | * @prop 259 | * @type Function 260 | * */ 261 | @property({ type: Function }) 262 | onReady?: Hook; 263 | 264 | /** 265 | * Function(s) to trigger every time the calendar month is changed by the user or programmatically 266 | * @prop 267 | * @type Function 268 | * */ 269 | @property({ type: Function }) 270 | onMonthChange?: Hook; 271 | 272 | /** 273 | * Function(s) to trigger every time the calendar year is changed by the user or programmatically 274 | * @prop 275 | * @type Function 276 | * */ 277 | @property({ type: Function }) 278 | onYearChange?: Hook; 279 | 280 | /** 281 | * Function(s) to trigger when the input value is updated with a new date string 282 | * @prop 283 | * @type Function 284 | * */ 285 | @property({ type: Function }) 286 | onValueUpdate?: Hook; 287 | 288 | /** 289 | * Function that expects a date string and must return a Date object. 290 | * 291 | * Function format: (date: string, format: string) => string 292 | * 293 | * @prop 294 | * @type Function 295 | **/ 296 | @property({ type: Function }) 297 | parseDateFn?: (date: string, format: string) => Date; 298 | 299 | /** 300 | * Where the calendar is rendered relative to the input 301 | * @prop 302 | * @type {"auto" | "above" | "below"} 303 | * */ 304 | @property({ type: String }) 305 | position: 'auto' | 'above' | 'below' = 'auto'; 306 | 307 | /** 308 | * Show the month using the shorthand version (ie, Sep instead of September) 309 | * @prop 310 | * @type boolean 311 | * */ 312 | @property({ type: Boolean }) 313 | shorthandCurrentMonth = false; 314 | 315 | /** 316 | * The number of months showed 317 | * @prop 318 | * @type number 319 | * */ 320 | @property({ type: Number }) 321 | showMonths = 1; 322 | 323 | /** 324 | * Position the calendar inside the wrapper and next to the input element 325 | * @prop 326 | * @type boolean 327 | **/ 328 | @property({ type: Boolean }) 329 | static = false; 330 | 331 | /** 332 | * Displays the time picker in 24 hour mode without AM/PM selection when enabled 333 | * @prop 334 | * @type boolean 335 | * */ 336 | @property({ type: Boolean }) 337 | time_24hr = false; 338 | 339 | /** 340 | * Enabled display of week numbers in calendar 341 | * @prop 342 | * @type boolean 343 | * */ 344 | @property({ type: Boolean }) 345 | weekNumbers = false; 346 | 347 | /** 348 | * flatpickr can parse an input group of textboxes and buttons, common in Bootstrap and other frameworks. 349 | * This permits additional markup, as well as custom elements to trigger the state of the calendar. 350 | * @prop 351 | * @type boolean 352 | * */ 353 | @property({ type: Boolean }) 354 | wrap = false; 355 | 356 | /** 357 | * The set theme of flatpickr. 358 | * @prop 359 | * @type { "light" | "dark" | "material_blue" | "material_red" | "material_green" | "material_orange" | "airbnb" | "confetti" | "none" } 360 | * */ 361 | @property({ type: String }) 362 | theme = 'light'; 363 | 364 | @property({ type: Number }) 365 | firstDayOfWeek = 1; 366 | 367 | @property({ type: String }) 368 | locale: string | undefined; 369 | 370 | @property({ type: Boolean, attribute: 'default-to-today' }) 371 | defaultToToday = false; 372 | 373 | @property({ type: Boolean, attribute: 'week-select' }) 374 | weekSelect = false; 375 | 376 | @property({ type: Boolean, attribute: 'month-select' }) 377 | monthSelect = false; 378 | 379 | @property({ type: Boolean, attribute: 'confirm-date' }) 380 | confirmDate = false; 381 | 382 | _instance?: Instance; 383 | _inputElement?: HTMLInputElement; 384 | 385 | @property({ type: Boolean }) 386 | _hasSlottedElement = false; 387 | 388 | static get styles() { 389 | return [ 390 | css` 391 | :host { 392 | width: fit-content; 393 | display: block; 394 | cursor: pointer; 395 | background: #fff; 396 | color: #000; 397 | overflow: hidden; 398 | } 399 | 400 | ::slotted(*) { 401 | cursor: pointer; 402 | } 403 | 404 | input { 405 | width: 100%; 406 | height: 100%; 407 | font-size: inherit; 408 | cursor: pointer; 409 | background: inherit; 410 | box-sizing: border-box; 411 | outline: none; 412 | color: inherit; 413 | border: none; 414 | } 415 | `, 416 | ]; 417 | } 418 | 419 | firstUpdated() { 420 | this._hasSlottedElement = this.checkForSlottedElement(); 421 | } 422 | 423 | updated() { 424 | // TODO: Might not need to init every time updated, but only 425 | // when relevant stuff changes 426 | this.init(); 427 | } 428 | 429 | getToday() { 430 | const today = new Date(); 431 | const dateString = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`; 432 | return dateString; 433 | } 434 | 435 | checkForSlottedElement(): boolean { 436 | const slottedElem = this.shadowRoot?.querySelector('slot'); 437 | // We don't want to think that a whitespace / line break is a node 438 | const assignedNodes = slottedElem ? slottedElem.assignedNodes().filter(this.removeTextNodes) : []; 439 | 440 | return slottedElem != null && assignedNodes && assignedNodes.length > 0; 441 | } 442 | 443 | getSlottedElement(): Element | undefined { 444 | if (!this._hasSlottedElement) { 445 | return undefined; 446 | } 447 | const slottedElem = this.shadowRoot?.querySelector('slot'); 448 | const slottedElemNodes: Array | undefined = slottedElem?.assignedNodes().filter(this.removeTextNodes); 449 | if (!slottedElemNodes || slottedElemNodes.length < 1) { 450 | return undefined; 451 | } 452 | return slottedElemNodes[0] as Element; 453 | } 454 | 455 | removeTextNodes(node: Node): boolean { 456 | return node.nodeName !== '#text'; 457 | } 458 | 459 | async init(): Promise { 460 | const styleLoader = new StyleLoader(this.theme as FlatpickrTheme); 461 | await styleLoader.initStyles(); 462 | if (this.locale) { 463 | await loadLocale(this.locale); 464 | } 465 | await this.initializeComponent(); 466 | } 467 | 468 | async getOptions(): Promise { 469 | /* eslint-disable @typescript-eslint/no-explicit-any */ 470 | let options = { 471 | altFormat: this.altFormat, 472 | altInput: this.altInput, 473 | altInputClass: this.altInputClass, 474 | allowInput: this.allowInput, 475 | ariaDateFormat: this.ariaDateFormat, 476 | clickOpens: this.clickOpens, 477 | dateFormat: this.dateFormat, 478 | defaultDate: this.defaultToToday ? this.getToday() : this.defaultDate, 479 | defaultHour: this.defaultHour, 480 | defaultMinute: this.defaultMinute, 481 | disable: this.disable, 482 | disableMobile: this.disableMobile, 483 | enable: this.enable, 484 | enableTime: this.enableTime, 485 | enableSeconds: this.enableSeconds, 486 | formatDate: this.formatDateFn, 487 | hourIncrement: this.hourIncrement, 488 | inline: this.inline, 489 | maxDate: this.maxDate, 490 | minDate: this.minDate, 491 | minuteIncrement: this.minuteIncrement, 492 | mode: this.mode, 493 | nextArrow: this.nextArrow, 494 | prevArrow: this.prevArrow, 495 | noCalendar: this.noCalendar, 496 | onChange: this.onChange, 497 | onClose: this.onClose, 498 | onOpen: this.onOpen, 499 | onReady: this.onReady, 500 | onMonthChange: this.onMonthChange, 501 | onYearChange: this.onYearChange, 502 | onValueUpdate: this.onValueUpdate, 503 | parseDate: this.parseDateFn, 504 | position: this.position, 505 | shorthandCurrentMonth: this.shorthandCurrentMonth, 506 | showMonths: this.showMonths, 507 | static: this.static, 508 | // eslint-disable-next-line @typescript-eslint/camelcase 509 | time_24hr: this.time_24hr, 510 | weekNumbers: this.weekNumbers, 511 | wrap: this.wrap, 512 | locale: this.locale, 513 | plugins: [], 514 | } as any; 515 | options = await loadPlugins(this, options); 516 | Object.keys(options).forEach(key => { 517 | if (options[key] === undefined) delete options[key]; 518 | }); 519 | return options; 520 | } 521 | 522 | async initializeComponent(): Promise { 523 | if (this._instance) { 524 | if (Object.prototype.hasOwnProperty.call(this, 'destroy')) { 525 | this._instance.destroy(); 526 | } 527 | } 528 | 529 | let inputElement: HTMLInputElement | null; 530 | if (this._hasSlottedElement) { 531 | // If lit-flatpickr has a slotted element, it means that 532 | // the user wants to use their custom input. 533 | inputElement = this.findInputField(); 534 | } else { 535 | inputElement = this.shadowRoot?.querySelector('input') as HTMLInputElement; 536 | } 537 | 538 | if (inputElement) { 539 | this._inputElement = inputElement as HTMLInputElement; 540 | flatpickr.l10ns.default.firstDayOfWeek = this.firstDayOfWeek; 541 | const options = await this.getOptions(); 542 | this._instance = flatpickr(inputElement, options); 543 | } 544 | } 545 | 546 | findInputField(): HTMLInputElement | null { 547 | let inputElement: HTMLInputElement | null = null; 548 | // First we check if the slotted element is just light dom HTML 549 | inputElement = this.querySelector('input'); 550 | if (inputElement) { 551 | return inputElement as HTMLInputElement; 552 | } 553 | // If not, we traverse down the slotted element's dom/shadow dom until we 554 | // find a dead-end or an input 555 | const slottedElement: Element | undefined = this.getSlottedElement(); 556 | if (typeof slottedElement !== undefined) { 557 | inputElement = this.searchWebComponentForInputElement(slottedElement as Element); 558 | } 559 | 560 | return inputElement ? (inputElement as HTMLInputElement) : null; 561 | } 562 | 563 | /** 564 | * Traverse the shadow dom tree and search for input from it 565 | * and it's children 566 | * */ 567 | searchWebComponentForInputElement(element: Element): HTMLInputElement | null { 568 | let inputElement: HTMLInputElement | null = this.getInputFieldInElement(element); 569 | if (inputElement) return inputElement; 570 | 571 | const webComponentsInChildren = this.getWebComponentsInsideElement(element); 572 | for (let i = 0; i < webComponentsInChildren.length; i++) { 573 | inputElement = this.searchWebComponentForInputElement(webComponentsInChildren[i]); 574 | if (inputElement) { 575 | break; 576 | } 577 | } 578 | return inputElement; 579 | } 580 | 581 | /** 582 | * Check if said element's dom tree contains a input element 583 | * */ 584 | getInputFieldInElement(element: Element): HTMLInputElement | null { 585 | let inputElement: HTMLInputElement | null = null; 586 | if (element.shadowRoot) { 587 | inputElement = element.shadowRoot.querySelector('input'); 588 | } else { 589 | inputElement = element.querySelector('input'); 590 | } 591 | return inputElement; 592 | } 593 | 594 | getWebComponentsInsideElement(element: Element): Array { 595 | if (element.shadowRoot) { 596 | return [ 597 | ...Array.from(element.querySelectorAll('*')), 598 | ...Array.from(element.shadowRoot.querySelectorAll('*')), 599 | ].filter((elem: Element) => elem.shadowRoot); 600 | } else { 601 | return Array.from(element.querySelectorAll('*')).filter((elem: Element) => elem.shadowRoot); 602 | } 603 | } 604 | 605 | changeMonth(monthNum: number, isOffset = true): void { 606 | if (!this._instance) return; 607 | this._instance.changeMonth(monthNum, isOffset); 608 | } 609 | 610 | clear(): void { 611 | if (!this._instance) return; 612 | this._instance.clear(); 613 | } 614 | 615 | close(): void { 616 | if (!this._instance) return; 617 | this._instance.close(); 618 | } 619 | 620 | destroy(): void { 621 | if (!this._instance) return; 622 | this._instance.destroy(); 623 | } 624 | 625 | formatDate(dateObj: Date, formatStr: string): string { 626 | if (!this._instance) return ''; 627 | return this._instance.formatDate(dateObj, formatStr); 628 | } 629 | 630 | jumpToDate(date: Date, triggerChange: boolean) { 631 | if (!this._instance) return; 632 | this._instance.jumpToDate(date, triggerChange); 633 | } 634 | 635 | open(): void { 636 | if (!this._instance) return; 637 | this._instance.open(); 638 | } 639 | 640 | parseDate(dateStr: string, dateFormat: string): Date | undefined { 641 | if (!this._instance) return undefined; 642 | return this._instance.parseDate(dateStr, dateFormat); 643 | } 644 | 645 | redraw(): void { 646 | if (!this._instance) return; 647 | this._instance.redraw(); 648 | } 649 | 650 | /* eslint-disable @typescript-eslint/no-explicit-any */ 651 | set( 652 | option: 653 | | keyof Options 654 | | { 655 | [k in keyof Options]?: Options[k]; 656 | }, 657 | value?: any 658 | ): void { 659 | if (!this._instance) return; 660 | this._instance.set(option, value); 661 | } 662 | 663 | setDate(date: DateOption | DateOption[], triggerChange: boolean, dateStrFormat: string): void { 664 | if (!this._instance) return; 665 | this._instance.setDate(date, triggerChange, dateStrFormat); 666 | } 667 | 668 | toggle(): void { 669 | if (!this._instance) return; 670 | } 671 | 672 | getSelectedDates(): Array { 673 | if (!this._instance) return []; 674 | return this._instance.selectedDates; 675 | } 676 | 677 | getCurrentYear(): number { 678 | if (!this._instance) return -1; 679 | return this._instance.currentYear; 680 | } 681 | 682 | getCurrentMonth(): number { 683 | if (!this._instance) return -1; 684 | return this._instance.currentMonth; 685 | } 686 | 687 | getConfig(): ParsedOptions { 688 | if (!this._instance) return {} as ParsedOptions; 689 | return this._instance.config; 690 | } 691 | 692 | getValue(): string { 693 | if (!this._inputElement) return ''; 694 | return this._inputElement.value; 695 | } 696 | 697 | render() { 698 | return html` 699 | ${!this._hasSlottedElement 700 | ? html`` 701 | : html``} 702 | 703 | `; 704 | } 705 | } 706 | -------------------------------------------------------------------------------- /src/LocaleLoader.ts: -------------------------------------------------------------------------------- 1 | import { getCDNBase } from './CdnManager'; 2 | 3 | export async function loadLocale(locale: string) { 4 | const themeUrl = getCDNBase() + 'l10n/' + locale + '.js'; 5 | const themeData = await import(themeUrl); 6 | } 7 | -------------------------------------------------------------------------------- /src/StyleLoader.ts: -------------------------------------------------------------------------------- 1 | import { getStyleRepository, FlatpickrTheme } from './styles/Themes'; 2 | import { getCDNBase } from './CdnManager'; 3 | 4 | export default class StyleLoader { 5 | constructor(public theme: FlatpickrTheme) { 6 | this.theme = theme; 7 | } 8 | 9 | async initStyles(): Promise { 10 | const themeUrl = getStyleRepository(this.theme); 11 | const themeIsLoaded: boolean = this.isThemeLoaded(); 12 | if (!themeIsLoaded) { 13 | this.appendThemeStyles(themeUrl); 14 | await this.waitForStyleToLoad(() => this.isThemeLoaded()); 15 | } 16 | } 17 | 18 | /** 19 | * We want to prevent the styles from flickering, so we halt the 20 | * initialization process until the styles have been loaded 21 | * */ 22 | waitForStyleToLoad(checkFunction: Function): Promise { 23 | return new Promise((resolve, reject) => { 24 | const checkIfStylesHaveLoaded = (iteration = 0) => { 25 | if (checkFunction()) return resolve(); 26 | if (iteration > 10) { 27 | throw Error('Styles took too long to load, or were not able to be loaded'); 28 | reject(); 29 | } 30 | setTimeout(() => checkIfStylesHaveLoaded(iteration++), 100); 31 | }; 32 | checkIfStylesHaveLoaded(); 33 | }); 34 | } 35 | 36 | isThemeLoaded(): boolean { 37 | // special theme value to prevent any loading of styles 38 | if (this.theme === FlatpickrTheme.none) return true; 39 | 40 | const styleSheetSources: Array = Array.from(document.styleSheets).map(ss => ss.href); 41 | return styleSheetSources.some(sss => sss != null && new RegExp(getCDNBase() + 'themes').test(sss)); 42 | } 43 | 44 | appendThemeStyles(themeUrl: string): void { 45 | const styleElem = document.createElement('link'); 46 | styleElem.rel = 'stylesheet'; 47 | styleElem.type = 'text/css'; 48 | styleElem.href = themeUrl; 49 | document.head.append(styleElem); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/plugins/PluginLoader.ts: -------------------------------------------------------------------------------- 1 | import { LitFlatpickr } from '../LitFlatpickr'; 2 | import { getCDNBase } from '../CdnManager'; 3 | 4 | export async function loadPlugins(instance: LitFlatpickr, options: any) { 5 | if (instance.weekSelect) { 6 | // @ts-ignore 7 | const weekSelectPluginImport = await import(getCDNBase() + 'esm/plugins/weekSelect/weekSelect.js'); 8 | const weekSelectPlugin = weekSelectPluginImport.default; 9 | options = { 10 | ...options, 11 | plugins: [...options.plugins, weekSelectPlugin()], 12 | onChange: function () { 13 | const weekNumber = this.selectedDates[0] ? this.config.getWeek(this.selectedDates[0]) : null; 14 | this.input.value = weekNumber; 15 | }, 16 | }; 17 | } 18 | 19 | if (instance.monthSelect) { 20 | // @ts-ignore 21 | const monthSelectPluginImport = await import(getCDNBase() + 'esm/plugins/monthSelect/index.js'); 22 | const monthSelectPlugin = monthSelectPluginImport.default; 23 | options = { 24 | ...options, 25 | plugins: [ 26 | ...options.plugins, 27 | monthSelectPlugin({ 28 | shorthand: false, 29 | dateFormat: instance.dateFormat, 30 | altFormat: instance.altFormat, 31 | }), 32 | ], 33 | }; 34 | const styles = document.createElement('link'); 35 | styles.rel = 'stylesheet'; 36 | styles.href = getCDNBase() + 'plugins/monthSelect/style.css'; 37 | document.head.appendChild(styles); 38 | } 39 | 40 | return options; 41 | } 42 | -------------------------------------------------------------------------------- /src/styles/Themes.ts: -------------------------------------------------------------------------------- 1 | import { getCDNBase } from '../CdnManager'; 2 | 3 | export enum FlatpickrTheme { 4 | light = 'light', 5 | dark = 'dark', 6 | materialBlue = 'material_blue', 7 | materialGreen = 'material_green', 8 | materialOrange = 'material_orange', 9 | materialRed = 'material_red', 10 | airbnb = 'airbnb', 11 | confetti = 'confetti', 12 | none = 'none', 13 | } 14 | 15 | export function getStyleRepository(theme: FlatpickrTheme): string { 16 | return `${getCDNBase()}themes/${theme}.css`; 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "noEmitOnError": true, 7 | "lib": ["es2017", "dom"], 8 | "strict": true, 9 | "esModuleInterop": false, 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "importHelpers": true, 13 | "outDir": "dist", 14 | "sourceMap": true, 15 | "inlineSources": true, 16 | "rootDir": "./", 17 | "declaration": true 18 | }, 19 | "include": ["**/*.ts"] 20 | } 21 | --------------------------------------------------------------------------------