├── .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 | [](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 |
--------------------------------------------------------------------------------