PageSourceSearch

https://www.zi.de/_assets/4483a80560873394161ba712d6ca7e9e/JS/_datepicker-7c518a59.js

js zi.de collected 2026-09-25 18:38:50 UTC 89,374 bytes, 2,904 lines download raw bytes

vendor: 6,211 bytes, lines 1-196
1function lastItemOf(arr) {
2  return arr[arr.length - 1];
3}
4
5// push only the items not included in the array
6function pushUnique(arr, ...items) {
7  items.forEach((item) => {
8    if (arr.includes(item)) {
9      return;
10    }
11    arr.push(item);
12  });
13  return arr;
14}
15
16function stringToArray(str, separator) {
17  // convert empty string to an empty array
18  return str ? str.split(separator) : [];
19}
20
21function isInRange(testVal, min, max) {
22  const minOK = min === undefined || testVal >= min;
23  const maxOK = max === undefined || testVal <= max;
24  return minOK && maxOK;
25}
26
27function limitToRange(val, min, max) {
28  if (val < min) {
29    return min;
30  }
31  if (val > max) {
32    return max;
33  }
34  return val;
35}
36
37function createTagRepeat(tagName, repeat, attributes = {}, index = 0, html = '') {
38  const openTagSrc = Object.keys(attributes).reduce((src, attr) => {
39    let val = attributes[attr];
40    if (typeof val === 'function') {
41      val = val(index);
42    }
43    return `${src} ${attr}="${val}"`;
44  }, tagName);
45  html += `<${openTagSrc}></${tagName}>`;
46
47  const next = index + 1;
48  return next < repeat
49    ? createTagRepeat(tagName, repeat, attributes, next, html)
50    : html;
51}
52
53// Remove the spacing surrounding tags for HTML parser not to create text nodes
54// before/after elements
55function optimizeTemplateHTML(html) {
56  return html.replace(/>\s+/g, '>').replace(/\s+</, '<');
57}
58
59function stripTime(timeValue) {
60  return new Date(timeValue).setHours(0, 0, 0, 0);
61}
62
63function today() {
64  return new Date().setHours(0, 0, 0, 0);
65}
66
67// Get the time value of the start of given date or year, month and day
68function dateValue(...args) {
69  switch (args.length) {
70    case 0:
71      return today();
72    case 1:
73      return stripTime(args[0]);
74  }
75
76  // use setFullYear() to keep 2-digit year from being mapped to 1900-1999
77  const newDate = new Date(0);
78  newDate.setFullYear(...args);
79  return newDate.setHours(0, 0, 0, 0);
80}
81
82function addDays(date, amount) {
83  const newDate = new Date(date);
84  return newDate.setDate(newDate.getDate() + amount);
85}
86
87function addWeeks(date, amount) {
88  return addDays(date, amount * 7);
89}
90
91function addMonths(date, amount) {
92  // If the day of the date is not in the new month, the last day of the new
93  // month will be returned. e.g. Jan 31 + 1 month → Feb 28 (not Mar 03)
94  const newDate = new Date(date);
95  const monthsToSet = newDate.getMonth() + amount;
96  let expectedMonth = monthsToSet % 12;
97  if (expectedMonth < 0) {
98    expectedMonth += 12;
99  }
100
101  const time = newDate.setMonth(monthsToSet);
102  return newDate.getMonth() !== expectedMonth ? newDate.setDate(0) : time;
103}
104
105function addYears(date, amount) {
106  // If the date is Feb 29 and the new year is not a leap year, Feb 28 of the
107  // new year will be returned.
108  const newDate = new Date(date);
109  const expectedMonth = newDate.getMonth();
110  const time = newDate.setFullYear(newDate.getFullYear() + amount);
111  return expectedMonth === 1 && newDate.getMonth() === 2 ? newDate.setDate(0) : time;
112}
113
114// Calculate the distance bettwen 2 days of the week
115function dayDiff(day, from) {
116  return (day - from + 7) % 7;
117}
118
119// Get the date of the specified day of the week of given base date
120function dayOfTheWeekOf(baseDate, dayOfWeek, weekStart = 0) {
121  const baseDay = new Date(baseDate).getDay();
122  return addDays(baseDate, dayDiff(dayOfWeek, weekStart) - dayDiff(baseDay, weekStart));
123}
124
125function calcWeekNum(dayOfTheWeek, sameDayOfFirstWeek) {
126  return Math.round((dayOfTheWeek - sameDayOfFirstWeek) / 604800000) + 1;
127}
128
129// Get the ISO week number of a date
130function getIsoWeek(date) {
131  // - Start of ISO week is Monday
132  // - Use Thursday for culculation because the first Thursday of ISO week is
133  //   always in January
134  const thuOfTheWeek = dayOfTheWeekOf(date, 4, 1);
135  // - Week 1 in ISO week is the week including Jan 04
136  // - Use the Thu of given date's week (instead of given date itself) to
137  //   calculate week 1 of the year so that Jan 01 - 03 won't be miscalculated
138  //   as week 0 when Jan 04 is Mon - Wed
139  const firstThu = dayOfTheWeekOf(new Date(thuOfTheWeek).setMonth(0, 4), 4, 1);
140  // return Math.round((thuOfTheWeek - firstThu) / 604800000) + 1;
141  return calcWeekNum(thuOfTheWeek, firstThu);
142}
143
144// Calculate week number in traditional week number system
145// @see https://en.wikipedia.org/wiki/Week#Other_week_numbering_systems
146function calcTraditionalWeekNumber(date, weekStart) {
147  // - Week 1 of traditional week is the week including the Jan 01
148  // - Use Jan 01 of given date's year to calculate the start of week 1
149  const startOfFirstWeek = dayOfTheWeekOf(new Date(date).setMonth(0, 1), weekStart, weekStart);
150  const startOfTheWeek = dayOfTheWeekOf(date, weekStart, weekStart);
151  const weekNum = calcWeekNum(startOfTheWeek, startOfFirstWeek);
152  if (weekNum < 53) {
153    return weekNum;
154  }
155  // If the 53rd week includes Jan 01, it's actually next year's week 1
156  const weekOneOfNextYear = dayOfTheWeekOf(new Date(date).setDate(32), weekStart, weekStart);
157  return startOfTheWeek === weekOneOfNextYear ? 1 : weekNum;
158}
159
160// Get the Western traditional week number of a date
161function getWesternTradWeek(date) {
162  // Start of Western traditionl week is Sunday
163  return calcTraditionalWeekNumber(date, 0);
164}
165
166// Get the Middle Eastern week number of a date
167function getMidEasternWeek(date) {
168  // Start of Middle Eastern week is Saturday
169  return calcTraditionalWeekNumber(date, 6);
170}
171
172// Get the start year of the period of years that includes given date
173// years: length of the year period
174function startOfYearPeriod(date, years) {
175  /* @see https://en.wikipedia.org/wiki/Year_zero#ISO_8601 */
176  const year = new Date(date).getFullYear();
177  return Math.floor(year / years) * years;
178}
179
180// Convert date to the first/last date of the month/year of the date
181function regularizeDate(date, timeSpan, useLastDate) {
182  if (timeSpan !== 1 && timeSpan !== 2) {
183    return date;
184  }
185  const newDate = new Date(date);
186  if (timeSpan === 1) {
187    useLastDate
188      ? newDate.setMonth(newDate.getMonth() + 1, 0)
189      : newDate.setDate(1);
190  } else {
191    useLastDate
192      ? newDate.setFullYear(newDate.getFullYear() + 1, 0, 0)
193      : newDate.setMonth(0, 1);
194  }
195  return newDate.setHours(0, 0, 0, 0);
196}
vendor: 5,194 bytes, lines 196-380
196
197
198// pattern for format parts
199const reFormatTokens = /dd?|DD?|mm?|MM?|yy?(?:yy)?/;
200// pattern for non date parts
201const reNonDateParts = /[\s!-/:-@[-`{-~年月日]+/;
202// cache for persed formats
203let knownFormats = {};
204// parse funtions for date parts
205const parseFns = {
206  y(date, year) {
207    return new Date(date).setFullYear(parseInt(year, 10));
208  },
209  m(date, month, locale) {
210    const newDate = new Date(date);
211    let monthIndex = parseInt(month, 10) - 1;
212
213    if (isNaN(monthIndex)) {
214      if (!month) {
215        return NaN;
216      }
217
218      const monthName = month.toLowerCase();
219      const compareNames = name => name.toLowerCase().startsWith(monthName);
220      // compare with both short and full names because some locales have periods
221      // in the short names (not equal to the first X letters of the full names)
222      monthIndex = locale.monthsShort.findIndex(compareNames);
223      if (monthIndex < 0) {
224        monthIndex = locale.months.findIndex(compareNames);
225      }
226      if (monthIndex < 0) {
227        return NaN;
228      }
229    }
230
231    newDate.setMonth(monthIndex);
232    return newDate.getMonth() !== normalizeMonth(monthIndex)
233      ? newDate.setDate(0)
234      : newDate.getTime();
235  },
236  d(date, day) {
237    return new Date(date).setDate(parseInt(day, 10));
238  },
239};
240// format functions for date parts
241const formatFns = {
242  d(date) {
243    return date.getDate();
244  },
245  dd(date) {
246    return padZero(date.getDate(), 2);
247  },
248  D(date, locale) {
249    return locale.daysShort[date.getDay()];
250  },
251  DD(date, locale) {
252    return locale.days[date.getDay()];
253  },
254  m(date) {
255    return date.getMonth() + 1;
256  },
257  mm(date) {
258    return padZero(date.getMonth() + 1, 2);
259  },
260  M(date, locale) {
261    return locale.monthsShort[date.getMonth()];
262  },
263  MM(date, locale) {
264    return locale.months[date.getMonth()];
265  },
266  y(date) {
267    return date.getFullYear();
268  },
269  yy(date) {
270    return padZero(date.getFullYear(), 2).slice(-2);
271  },
272  yyyy(date) {
273    return padZero(date.getFullYear(), 4);
274  },
275};
276
277// get month index in normal range (0 - 11) from any number
278function normalizeMonth(monthIndex) {
279  return monthIndex > -1 ? monthIndex % 12 : normalizeMonth(monthIndex + 12);
280}
281
282function padZero(num, length) {
283  return num.toString().padStart(length, '0');
284}
285
286function parseFormatString(format) {
287  if (typeof format !== 'string') {
288    throw new Error("Invalid date format.");
289  }
290  if (format in knownFormats) {
291    return knownFormats[format];
292  }
293
294  // sprit the format string into parts and seprators
295  const separators = format.split(reFormatTokens);
296  const parts = format.match(new RegExp(reFormatTokens, 'g'));
297  if (separators.length === 0 || !parts) {
298    throw new Error("Invalid date format.");
299  }
300
301  // collect format functions used in the format
302  const partFormatters = parts.map(token => formatFns[token]);
303
304  // collect parse function keys used in the format
305  // iterate over parseFns' keys in order to keep the order of the keys.
306  const partParserKeys = Object.keys(parseFns).reduce((keys, key) => {
307    const token = parts.find(part => part[0] !== 'D' && part[0].toLowerCase() === key);
308    if (token) {
309      keys.push(key);
310    }
311    return keys;
312  }, []);
313
314  return knownFormats[format] = {
315    parser(dateStr, locale) {
316      const dateParts = dateStr.split(reNonDateParts).reduce((dtParts, part, index) => {
317        if (part.length > 0 && parts[index]) {
318          const token = parts[index][0];
319          if (token === 'M') {
320            dtParts.m = part;
321          } else if (token !== 'D') {
322            dtParts[token] = part;
323          }
324        }
325        return dtParts;
326      }, {});
327
328      // iterate over partParserkeys so that the parsing is made in the oder
329      // of year, month and day to prevent the day parser from correcting last
330      // day of month wrongly
331      return partParserKeys.reduce((origDate, key) => {
332        const newDate = parseFns[key](origDate, dateParts[key], locale);
333        // ingnore the part failed to parse
334        return isNaN(newDate) ? origDate : newDate;
335      }, today());
336    },
337    formatter(date, locale) {
338      let dateStr = partFormatters.reduce((str, fn, index) => {
339        return str += `${separators[index]}${fn(date, locale)}`;
340      }, '');
341      // separators' length is always parts' length + 1,
342      return dateStr += lastItemOf(separators);
343    },
344  };
345}
346
347function parseDate(dateStr, format, locale) {
348  if (dateStr instanceof Date || typeof dateStr === 'number') {
349    const date = stripTime(dateStr);
350    return isNaN(date) ? undefined : date;
351  }
352  if (!dateStr) {
353    return undefined;
354  }
355  if (dateStr === 'today') {
356    return today();
357  }
358
359  if (format && format.toValue) {
360    const date = format.toValue(dateStr, format, locale);
361    return isNaN(date) ? undefined : stripTime(date);
362  }
363
364  return parseFormatString(format).parser(dateStr, locale);
365}
366
367function formatDate(date, format, locale) {
368  if (isNaN(date) || (!date && date !== 0)) {
369    return '';
370  }
371
372  const dateObj = typeof date === 'number' ? new Date(date) : date;
373
374  if (format.toDisplay) {
375    return format.toDisplay(dateObj, format, locale);
376  }
377
378  return parseFormatString(format).formatter(dateObj, locale);
379}
380
vendor: 64,513 bytes, lines 381-2508
381const range = document.createRange();
382
383function parseHTML(html) {
384  return range.createContextualFragment(html);
385}
386
387function getParent(el) {
388  return el.parentElement
389    || (el.parentNode instanceof ShadowRoot ? el.parentNode.host : undefined);
390}
391
392function isActiveElement(el) {
393  return el.getRootNode().activeElement === el;
394}
395
396function hideElement(el) {
397  if (el.style.display === 'none') {
398    return;
399  }
400  // back up the existing display setting in data-style-display
401  if (el.style.display) {
402    el.dataset.styleDisplay = el.style.display;
403  }
404  el.style.display = 'none';
405}
406
407function showElement(el) {
408  if (el.style.display !== 'none') {
409    return;
410  }
411  if (el.dataset.styleDisplay) {
412    // restore backed-up dispay property
413    el.style.display = el.dataset.styleDisplay;
414    delete el.dataset.styleDisplay;
415  } else {
416    el.style.display = '';
417  }
418}
419
420function emptyChildNodes(el) {
421  if (el.firstChild) {
422    el.removeChild(el.firstChild);
423    emptyChildNodes(el);
424  }
425}
426
427function replaceChildNodes(el, newChildNodes) {
428  emptyChildNodes(el);
429  if (newChildNodes instanceof DocumentFragment) {
430    el.appendChild(newChildNodes);
431  } else if (typeof newChildNodes === 'string') {
432    el.appendChild(parseHTML(newChildNodes));
433  } else if (typeof newChildNodes.forEach === 'function') {
434    newChildNodes.forEach((node) => {
435      el.appendChild(node);
436    });
437  }
438}
439
440const listenerRegistry = new WeakMap();
441const {addEventListener, removeEventListener} = EventTarget.prototype;
442
443// Register event listeners to a key object
444// listeners: array of listener definitions;
445//   - each definition must be a flat array of event target and the arguments
446//     used to call addEventListener() on the target
447function registerListeners(keyObj, listeners) {
448  let registered = listenerRegistry.get(keyObj);
449  if (!registered) {
450    registered = [];
451    listenerRegistry.set(keyObj, registered);
452  }
453  listeners.forEach((listener) => {
454    addEventListener.call(...listener);
455    registered.push(listener);
456  });
457}
458
459function unregisterListeners(keyObj) {
460  let listeners = listenerRegistry.get(keyObj);
461  if (!listeners) {
462    return;
463  }
464  listeners.forEach((listener) => {
465    removeEventListener.call(...listener);
466  });
467  listenerRegistry.delete(keyObj);
468}
469
470// Event.composedPath() polyfill for Edge
471// based on https://gist.github.com/kleinfreund/e9787d73776c0e3750dcfcdc89f100ec
472if (!Event.prototype.composedPath) {
473  const getComposedPath = (node, path = []) => {
474    path.push(node);
475
476    let parent;
477    if (node.parentNode) {
478      parent = node.parentNode;
479    } else if (node.host) { // ShadowRoot
480      parent = node.host;
481    } else if (node.defaultView) {  // Document
482      parent = node.defaultView;
483    }
484    return parent ? getComposedPath(parent, path) : path;
485  };
486
487  Event.prototype.composedPath = function () {
488    return getComposedPath(this.target);
489  };
490}
491
492function findFromPath(path, criteria, currentTarget) {
493  const [node, ...rest] = path;
494  if (criteria(node)) {
495    return node;
496  }
497  if (node === currentTarget || node.tagName === 'HTML' || rest.length === 0) {
498    // stop when reaching currentTarget or <html>
499    return;
500  }
501  return findFromPath(rest, criteria, currentTarget);
502}
503
504// Search for the actual target of a delegated event
505function findElementInEventPath(ev, selector) {
506  const criteria = typeof selector === 'function'
507    ? selector
508    : el => el instanceof Element && el.matches(selector);
509  return findFromPath(ev.composedPath(), criteria, ev.currentTarget);
510}
511
512// default locales
513var locales = {
514  en: {
515    days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
516    daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
517    daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
518    months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
519    monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
520    today: "Today",
521    clear: "Clear",
522    titleFormat: "MM y"
523  }
524};
525
526// config options updatable by setOptions() and their default values
527var defaultOptions = {
528  autohide: false,
529  beforeShowDay: null,
530  beforeShowDecade: null,
531  beforeShowMonth: null,
532  beforeShowYear: null,
533  clearButton: false,
534  dateDelimiter: ',',
535  datesDisabled: [],
536  daysOfWeekDisabled: [],
537  daysOfWeekHighlighted: [],
538  defaultViewDate: undefined, // placeholder, defaults to today() by the program
539  disableTouchKeyboard: false,
540  enableOnReadonly: true,
541  format: 'mm/dd/yyyy',
542  language: 'en',
543  maxDate: null,
544  maxNumberOfDates: 1,
545  maxView: 3,
546  minDate: null,
547  nextArrow: '»',
548  orientation: 'auto',
549  pickLevel: 0,
550  prevArrow: '«',
551  showDaysOfWeek: true,
552  showOnClick: true,
553  showOnFocus: true,
554  startView: 0,
555  title: '',
556  todayButton: false,
557  todayButtonMode: 0,
558  todayHighlight: false,
559  updateOnBlur: true,
560  weekNumbers: 0,
561  weekStart: 0,
562};
563
564const {
565  language: defaultLang,
566  format: defaultFormat,
567  weekStart: defaultWeekStart,
568} = defaultOptions;
569
570// Reducer function to filter out invalid day-of-week from the input
571function sanitizeDOW(dow, day) {
572  return dow.length < 6 && day >= 0 && day < 7
573    ? pushUnique(dow, day)
574    : dow;
575}
576
577function determineGetWeekMethod(numberingMode, weekStart) {
578  const methodId = numberingMode === 4
579    ? (weekStart === 6 ? 3 : !weekStart + 1)
580    : numberingMode;
581  switch (methodId) {
582    case 1:
583      return getIsoWeek;
584    case 2:
585      return getWesternTradWeek;
586    case 3:
587      return getMidEasternWeek;
588  }
589}
590
591function updateWeekStart(newValue, config, weekNumbers) {
592  config.weekStart = newValue;
593  config.weekEnd = (newValue + 6) % 7;
594  if (weekNumbers === 4) {
595    config.getWeekNumber = determineGetWeekMethod(4, newValue);
596  }
597  return newValue;
598}
599
600// validate input date. if invalid, fallback to the original value
601function validateDate(value, format, locale, origValue) {
602  const date = parseDate(value, format, locale);
603  return date !== undefined ? date : origValue;
604}
605
606// Validate viewId. if invalid, fallback to the original value
607function validateViewId(value, origValue, max = 3) {
608  const viewId = parseInt(value, 10);
609  return viewId >= 0 && viewId <= max ? viewId : origValue;
610}
611
612function replaceOptions(options, from, to, convert = undefined) {
613  if (from in options) {
614    if (!(to in options)) {
615      options[to] = convert ? convert(options[from]) : options[from];
616    }
617    delete options[from];
618  }
619}
620
621// Create Datepicker configuration to set
622function processOptions(options, datepicker) {
623  const inOpts = Object.assign({}, options);
624  const config = {};
625  const locales = datepicker.constructor.locales;
626  const rangeEnd = !!datepicker.rangeSideIndex;
627  let {
628    datesDisabled,
629    format,
630    language,
631    locale,
632    maxDate,
633    maxView,
634    minDate,
635    pickLevel,
636    startView,
637    weekNumbers,
638    weekStart,
639  } = datepicker.config || {};
640
641  // for backword compatibility
642  replaceOptions(inOpts, 'calendarWeeks', 'weekNumbers', val => val ? 1 : 0);
643  replaceOptions(inOpts, 'clearBtn', 'clearButton');
644  replaceOptions(inOpts, 'todayBtn', 'todayButton');
645  replaceOptions(inOpts, 'todayBtnMode', 'todayButtonMode');
646
647  if (inOpts.language) {
648    let lang;
649    if (inOpts.language !== language) {
650      if (locales[inOpts.language]) {
651        lang = inOpts.language;
652      } else {
653        // Check if langauge + region tag can fallback to the one without
654        // region (e.g. fr-CA → fr)
655        lang = inOpts.language.split('-')[0];
656        if (!locales[lang]) {
657          lang = false;
658        }
659      }
660    }
661    delete inOpts.language;
662    if (lang) {
663      language = config.language = lang;
664
665      // update locale as well when updating language
666      const origLocale = locale || locales[defaultLang];
667      // use default language's properties for the fallback
668      locale = Object.assign({
669        format: defaultFormat,
670        weekStart: defaultWeekStart
671      }, locales[defaultLang]);
672      if (language !== defaultLang) {
673        Object.assign(locale, locales[language]);
674      }
675      config.locale = locale;
676      // if format and/or weekStart are the same as old locale's defaults,
677      // update them to new locale's defaults
678      if (format === origLocale.format) {
679        format = config.format = locale.format;
680      }
681      if (weekStart === origLocale.weekStart) {
682        weekStart = updateWeekStart(locale.weekStart, config, weekNumbers);
683      }
684    }
685  }
686
687  if (inOpts.format) {
688    const hasToDisplay = typeof inOpts.format.toDisplay === 'function';
689    const hasToValue = typeof inOpts.format.toValue === 'function';
690    const validFormatString = reFormatTokens.test(inOpts.format);
691    if ((hasToDisplay && hasToValue) || validFormatString) {
692      format = config.format = inOpts.format;
693    }
694    delete inOpts.format;
695  }
696
697  //*** pick level ***//
698  let newPickLevel = pickLevel;
699  if ('pickLevel' in inOpts) {
700    newPickLevel = validateViewId(inOpts.pickLevel, pickLevel, 2);
701    delete inOpts.pickLevel;
702  }
703  if (newPickLevel !== pickLevel) {
704    if (newPickLevel > pickLevel) {
705      // complement current minDate/madDate so that the existing range will be
706      // expanded to fit the new level later
707      if (!('minDate' in inOpts)) {
708        inOpts.minDate = minDate;
709      }
710      if (!('maxDate' in inOpts)) {
711        inOpts.maxDate = maxDate;
712      }
713    }
714    // complement datesDisabled so that it will be reset later
715    if (datesDisabled && !inOpts.datesDisabled) {
716      inOpts.datesDisabled = [];
717    }
718    pickLevel = config.pickLevel = newPickLevel;
719  }
720
721  //*** dates ***//
722  // while min and maxDate for "no limit" in the options are better to be null
723  // (especially when updating), the ones in the config have to be undefined
724  // because null is treated as 0 (= unix epoch) when comparing with time value
725  let minDt = minDate;
726  let maxDt = maxDate;
727  if ('minDate' in inOpts) {
728    const defaultMinDt = dateValue(0, 0, 1);
729    minDt = inOpts.minDate === null
730      ? defaultMinDt  // set 0000-01-01 to prevent negative values for year
731      : validateDate(inOpts.minDate, format, locale, minDt);
732    if (minDt !== defaultMinDt) {
733      minDt = regularizeDate(minDt, pickLevel, false);
734    }
735    delete inOpts.minDate;
736  }
737  if ('maxDate' in inOpts) {
738    maxDt = inOpts.maxDate === null
739      ? undefined
740      : validateDate(inOpts.maxDate, format, locale, maxDt);
741    if (maxDt !== undefined) {
742      maxDt = regularizeDate(maxDt, pickLevel, true);
743    }
744    delete inOpts.maxDate;
745  }
746  if (maxDt < minDt) {
747    minDate = config.minDate = maxDt;
748    maxDate = config.maxDate = minDt;
749  } else {
750    if (minDate !== minDt) {
751      minDate = config.minDate = minDt;
752    }
753    if (maxDate !== maxDt) {
754      maxDate = config.maxDate = maxDt;
755    }
756  }
757
758  if (inOpts.datesDisabled) {
759    const dtsDisabled = inOpts.datesDisabled;
760    if (typeof dtsDisabled === 'function') {
761      config.datesDisabled = null;
762      config.checkDisabled = (timeValue, viewId) => dtsDisabled(
763        new Date(timeValue),
764        viewId,
765        rangeEnd
766      );
767    } else {
768      const disabled = config.datesDisabled = dtsDisabled.reduce((dates, dt) => {
769        const date = parseDate(dt, format, locale);
770        return date !== undefined
771          ? pushUnique(dates, regularizeDate(date, pickLevel, rangeEnd))
772          : dates;
773      }, []);
774      config.checkDisabled = timeValue => disabled.includes(timeValue);
775    }
776    delete inOpts.datesDisabled;
777  }
778  if ('defaultViewDate' in inOpts) {
779    const viewDate = parseDate(inOpts.defaultViewDate, format, locale);
780    if (viewDate !== undefined) {
781      config.defaultViewDate = viewDate;
782    }
783    delete inOpts.defaultViewDate;
784  }
785
786  //*** days of week ***//
787  if ('weekStart' in inOpts) {
788    const wkStart = Number(inOpts.weekStart) % 7;
789    if (!isNaN(wkStart)) {
790      weekStart = updateWeekStart(wkStart, config, weekNumbers);
791    }
792    delete inOpts.weekStart;
793  }
794  if (inOpts.daysOfWeekDisabled) {
795    config.daysOfWeekDisabled = inOpts.daysOfWeekDisabled.reduce(sanitizeDOW, []);
796    delete inOpts.daysOfWeekDisabled;
797  }
798  if (inOpts.daysOfWeekHighlighted) {
799    config.daysOfWeekHighlighted = inOpts.daysOfWeekHighlighted.reduce(sanitizeDOW, []);
800    delete inOpts.daysOfWeekHighlighted;
801  }
802
803  //*** week numbers ***//
804  if ('weekNumbers' in inOpts) {
805    let method = inOpts.weekNumbers;
806    if (method) {
807      const getWeekNumber = typeof method === 'function'
808        ? (timeValue, startOfWeek) => method(new Date(timeValue), startOfWeek)
809        : determineGetWeekMethod((method = parseInt(method, 10)), weekStart);
810      if (getWeekNumber) {
811        weekNumbers = config.weekNumbers = method;
812        config.getWeekNumber = getWeekNumber;
813      }
814    } else {
815      weekNumbers = config.weekNumbers = 0;
816      config.getWeekNumber = null;
817    }
818    delete inOpts.weekNumbers;
819  }
820
821  //*** multi date ***//
822  if ('maxNumberOfDates' in inOpts) {
823    const maxNumberOfDates = parseInt(inOpts.maxNumberOfDates, 10);
824    if (maxNumberOfDates >= 0) {
825      config.maxNumberOfDates = maxNumberOfDates;
826      config.multidate = maxNumberOfDates !== 1;
827    }
828    delete inOpts.maxNumberOfDates;
829  }
830  if (inOpts.dateDelimiter) {
831    config.dateDelimiter = String(inOpts.dateDelimiter);
832    delete inOpts.dateDelimiter;
833  }
834
835  //*** view ***//
836  let newMaxView = maxView;
837  if ('maxView' in inOpts) {
838    newMaxView = validateViewId(inOpts.maxView, maxView);
839    delete inOpts.maxView;
840  }
841  // ensure max view >= pick level
842  newMaxView = pickLevel > newMaxView ? pickLevel : newMaxView;
843  if (newMaxView !== maxView) {
844    maxView = config.maxView = newMaxView;
845  }
846
847  let newStartView = startView;
848  if ('startView' in inOpts) {
849    newStartView = validateViewId(inOpts.startView, newStartView);
850    delete inOpts.startView;
851  }
852  // ensure pick level <= start view <= max view
853  if (newStartView < pickLevel) {
854    newStartView = pickLevel;
855  } else if (newStartView > maxView) {
856    newStartView = maxView;
857  }
858  if (newStartView !== startView) {
859    config.startView = newStartView;
860  }
861
862  //*** template ***//
863  if (inOpts.prevArrow) {
864    const prevArrow = parseHTML(inOpts.prevArrow);
865    if (prevArrow.childNodes.length > 0) {
866      config.prevArrow = prevArrow.childNodes;
867    }
868    delete inOpts.prevArrow;
869  }
870  if (inOpts.nextArrow) {
871    const nextArrow = parseHTML(inOpts.nextArrow);
872    if (nextArrow.childNodes.length > 0) {
873      config.nextArrow = nextArrow.childNodes;
874    }
875    delete inOpts.nextArrow;
876  }
877
878  //*** misc ***//
879  if ('disableTouchKeyboard' in inOpts) {
880    config.disableTouchKeyboard = 'ontouchstart' in document && !!inOpts.disableTouchKeyboard;
881    delete inOpts.disableTouchKeyboard;
882  }
883  if (inOpts.orientation) {
884    const orientation = inOpts.orientation.toLowerCase().split(/\s+/g);
885    config.orientation = {
886      x: orientation.find(x => (x === 'left' || x === 'right')) || 'auto',
887      y: orientation.find(y => (y === 'top' || y === 'bottom')) || 'auto',
888    };
889    delete inOpts.orientation;
890  }
891  if ('todayButtonMode' in inOpts) {
892    switch(inOpts.todayButtonMode) {
893      case 0:
894      case 1:
895        config.todayButtonMode = inOpts.todayButtonMode;
896    }
897    delete inOpts.todayButtonMode;
898  }
899
900  //*** copy the rest ***//
901  Object.entries(inOpts).forEach(([key, value]) => {
902    if (value !== undefined && key in defaultOptions) {
903      config[key] = value;
904    }
905  });
906
907  return config;
908}
909
910const defaultShortcutKeys = {
911  show: {key: 'ArrowDown'},
912  hide: null,
913  toggle: {key: 'Escape'},
914  prevButton: {key: 'ArrowLeft', ctrlOrMetaKey: true},
915  nextButton: {key: 'ArrowRight', ctrlOrMetaKey: true},
916  viewSwitch: {key: 'ArrowUp', ctrlOrMetaKey: true},
917  clearButton: {key: 'Backspace', ctrlOrMetaKey: true},
918  todayButton: {key: '.', ctrlOrMetaKey: true},
919  exitEditMode: {key: 'ArrowDown', ctrlOrMetaKey: true},
920};
921
922function createShortcutKeyConfig(options) {
923  return Object.keys(defaultShortcutKeys).reduce((keyDefs, shortcut) => {
924    const keyDef = options[shortcut] === undefined
925      ? defaultShortcutKeys[shortcut]
926      : options[shortcut];
927    const key = keyDef && keyDef.key;
928    if (!key || typeof key !== 'string') {
929      return keyDefs;
930    }
931
932    const normalizedDef = {
933      key,
934      ctrlOrMetaKey: !!(keyDef.ctrlOrMetaKey || keyDef.ctrlKey || keyDef.metaKey),
935    };
936    if (key.length > 1) {
937      normalizedDef.altKey = !!keyDef.altKey;
938      normalizedDef.shiftKey = !!keyDef.shiftKey;
939    }
940    keyDefs[shortcut] = normalizedDef;
941    return keyDefs;
942  }, {});
943}
944
945const getButtons = buttonList => buttonList
946  .map(classes => `<button type="button" class="%buttonClass% ${classes}" tabindex="-1"></button>`)
947  .join('');
948
949var pickerTemplate = optimizeTemplateHTML(`<div class="datepicker">
950  <div class="datepicker-picker">
951    <div class="datepicker-header">
952      <div class="datepicker-title"></div>
953      <div class="datepicker-controls">
954        ${getButtons([
955          'prev-button prev-btn',
956          'view-switch',
957          'next-button next-btn',
958        ])}
959      </div>
960    </div>
961    <div class="datepicker-main"></div>
962    <div class="datepicker-footer">
963      <div class="datepicker-controls">
964        ${getButtons([
965          'today-button today-btn',
966          'clear-button clear-btn',
967        ])}
968      </div>
969    </div>
970  </div>
971</div>`);
972
973var daysTemplate = optimizeTemplateHTML(`<div class="days">
974  <div class="days-of-week">${createTagRepeat('span', 7, {class: 'dow'})}</div>
975  <div class="datepicker-grid">${createTagRepeat('span', 42)}</div>
976</div>`);
977
978var weekNumbersTemplate = optimizeTemplateHTML(`<div class="week-numbers calendar-weeks">
979  <div class="days-of-week"><span class="dow"></span></div>
980  <div class="weeks">${createTagRepeat('span', 6, {class: 'week'})}</div>
981</div>`);
982
983// Base class of the view classes
984class View {
985  constructor(picker, config) {
986    Object.assign(this, config, {
987      picker,
988      element: parseHTML(`<div class="datepicker-view"></div>`).firstChild,
989      selected: [],
990      isRangeEnd: !!picker.datepicker.rangeSideIndex,
991    });
992    this.init(this.picker.datepicker.config);
993  }
994
995  init(options) {
996    if ('pickLevel' in options) {
997      this.isMinView = this.id === options.pickLevel;
998    }
999    this.setOptions(options);
1000    this.updateFocus();
1001    this.updateSelection();
1002  }
1003
1004  prepareForRender(switchLabel, prevButtonDisabled, nextButtonDisabled) {
1005    // refresh disabled years on every render in order to clear the ones added
1006    // by beforeShow hook at previous render
1007    this.disabled = [];
1008
1009    const picker = this.picker;
1010    picker.setViewSwitchLabel(switchLabel);
1011    picker.setPrevButtonDisabled(prevButtonDisabled);
1012    picker.setNextButtonDisabled(nextButtonDisabled);
1013  }
1014
1015  setDisabled(date, classList) {
1016    classList.add('disabled');
1017    pushUnique(this.disabled, date);
1018  }
1019
1020  // Execute beforeShow() callback and apply the result to the element
1021  // args:
1022  performBeforeHook(el, timeValue) {
1023    let result = this.beforeShow(new Date(timeValue));
1024    switch (typeof result) {
1025      case 'boolean':
1026        result = {enabled: result};
1027        break;
1028      case 'string':
1029        result = {classes: result};
1030    }
1031
1032    if (result) {
1033      const classList = el.classList;
1034      if (result.enabled === false) {
1035        this.setDisabled(timeValue, classList);
1036      }
1037      if (result.classes) {
1038        const extraClasses = result.classes.split(/\s+/);
1039        classList.add(...extraClasses);
1040        if (extraClasses.includes('disabled')) {
1041          this.setDisabled(timeValue, classList);
1042        }
1043      }
1044      if (result.content) {
1045        replaceChildNodes(el, result.content);
1046      }
1047    }
1048  }
1049
1050  renderCell(el, content, cellVal, date, {selected, range}, outOfScope, extraClasses = []) {
1051    el.textContent = content;
1052    if (this.isMinView) {
1053      el.dataset.date = date;
1054    }
1055
1056    const classList = el.classList;
1057    el.className = `datepicker-cell ${this.cellClass}`;
1058    if (cellVal < this.first) {
1059      classList.add('prev');
1060    } else if (cellVal > this.last) {
1061      classList.add('next');
1062    }
1063    classList.add(...extraClasses);
1064    if (outOfScope || this.checkDisabled(date, this.id)) {
1065      this.setDisabled(date, classList);
1066    }
1067    if (range) {
1068      const [rangeStart, rangeEnd] = range;
1069      if (cellVal > rangeStart && cellVal < rangeEnd) {
1070        classList.add('range');
1071      }
1072      if (cellVal === rangeStart) {
1073        classList.add('range-start');
1074      }
1075      if (cellVal === rangeEnd) {
1076        classList.add('range-end');
1077      }
1078    }
1079    if (selected.includes(cellVal)) {
1080      classList.add('selected');
1081    }
1082    if (cellVal === this.focused) {
1083      classList.add('focused');
1084    }
1085
1086    if (this.beforeShow) {
1087      this.performBeforeHook(el, date);
1088    }
1089  }
1090
1091  refreshCell(el, cellVal, selected, [rangeStart, rangeEnd]) {
1092    const classList = el.classList;
1093    classList.remove('range', 'range-start', 'range-end', 'selected', 'focused');
1094    if (cellVal > rangeStart && cellVal < rangeEnd) {
1095      classList.add('range');
1096    }
1097    if (cellVal === rangeStart) {
1098      classList.add('range-start');
1099    }
1100    if (cellVal === rangeEnd) {
1101      classList.add('range-end');
1102    }
1103    if (selected.includes(cellVal)) {
1104      classList.add('selected');
1105    }
1106    if (cellVal === this.focused) {
1107      classList.add('focused');
1108    }
1109  }
1110
1111  changeFocusedCell(cellIndex) {
1112    this.grid.querySelectorAll('.focused').forEach((el) => {
1113      el.classList.remove('focused');
1114    });
1115    this.grid.children[cellIndex].classList.add('focused');
1116  }
1117}
1118
1119class DaysView extends View {
1120  constructor(picker) {
1121    super(picker, {
1122      id: 0,
1123      name: 'days',
1124      cellClass: 'day',
1125    });
1126  }
1127
1128  init(options, onConstruction = true) {
1129    if (onConstruction) {
1130      const inner = parseHTML(daysTemplate).firstChild;
1131      this.dow = inner.firstChild;
1132      this.grid = inner.lastChild;
1133      this.element.appendChild(inner);
1134    }
1135    super.init(options);
1136  }
1137
1138  setOptions(options) {
1139    let updateDOW;
1140
1141    if ('minDate' in options) {
1142      this.minDate = options.minDate;
1143    }
1144    if ('maxDate' in options) {
1145      this.maxDate = options.maxDate;
1146    }
1147    if (options.checkDisabled) {
1148      this.checkDisabled = options.checkDisabled;
1149    }
1150    if (options.daysOfWeekDisabled) {
1151      this.daysOfWeekDisabled = options.daysOfWeekDisabled;
1152      updateDOW = true;
1153    }
1154    if (options.daysOfWeekHighlighted) {
1155      this.daysOfWeekHighlighted = options.daysOfWeekHighlighted;
1156    }
1157    if ('todayHighlight' in options) {
1158      this.todayHighlight = options.todayHighlight;
1159    }
1160    if ('weekStart' in options) {
1161      this.weekStart = options.weekStart;
1162      this.weekEnd = options.weekEnd;
1163      updateDOW = true;
1164    }
1165    if (options.locale) {
1166      const locale = this.locale = options.locale;
1167      this.dayNames = locale.daysMin;
1168      this.switchLabelFormat = locale.titleFormat;
1169      updateDOW = true;
1170    }
1171    if ('beforeShowDay' in options) {
1172      this.beforeShow = typeof options.beforeShowDay === 'function'
1173        ? options.beforeShowDay
1174        : undefined;
1175    }
1176
1177    if ('weekNumbers' in options) {
1178      if (options.weekNumbers && !this.weekNumbers) {
1179        const weeksElem = parseHTML(weekNumbersTemplate).firstChild;
1180        this.weekNumbers = {
1181          element: weeksElem,
1182          dow: weeksElem.firstChild,
1183          weeks: weeksElem.lastChild,
1184        };
1185        this.element.insertBefore(weeksElem, this.element.firstChild);
1186      } else if (this.weekNumbers && !options.weekNumbers) {
1187        this.element.removeChild(this.weekNumbers.element);
1188        this.weekNumbers = null;
1189      }
1190    }
1191
1192    if ('getWeekNumber' in options) {
1193      this.getWeekNumber = options.getWeekNumber;
1194    }
1195
1196    if ('showDaysOfWeek' in options) {
1197      if (options.showDaysOfWeek) {
1198        showElement(this.dow);
1199        if (this.weekNumbers) {
1200          showElement(this.weekNumbers.dow);
1201        }
1202      } else {
1203        hideElement(this.dow);
1204        if (this.weekNumbers) {
1205          hideElement(this.weekNumbers.dow);
1206        }
1207      }
1208    }
1209
1210    // update days-of-week when locale, daysOfweekDisabled or weekStart is changed
1211    if (updateDOW) {
1212      Array.from(this.dow.children).forEach((el, index) => {
1213        const dow = (this.weekStart + index) % 7;
1214        el.textContent = this.dayNames[dow];
1215        el.className = this.daysOfWeekDisabled.includes(dow) ? 'dow disabled' : 'dow';
1216      });
1217    }
1218  }
1219
1220  // Apply update on the focused date to view's settings
1221  updateFocus() {
1222    const viewDate = new Date(this.picker.viewDate);
1223    const viewYear = viewDate.getFullYear();
1224    const viewMonth = viewDate.getMonth();
1225    const firstOfMonth = dateValue(viewYear, viewMonth, 1);
1226    const start = dayOfTheWeekOf(firstOfMonth, this.weekStart, this.weekStart);
1227
1228    this.first = firstOfMonth;
1229    this.last = dateValue(viewYear, viewMonth + 1, 0);
1230    this.start = start;
1231    this.focused = this.picker.viewDate;
1232  }
1233
1234  // Apply update on the selected dates to view's settings
1235  updateSelection() {
1236    const {dates, rangepicker} = this.picker.datepicker;
1237    this.selected = dates;
1238    if (rangepicker) {
1239      this.range = rangepicker.dates;
1240    }
1241  }
1242
1243   // Update the entire view UI
1244  render() {
1245    // update today marker on ever render
1246    this.today = this.todayHighlight ? today() : undefined;
1247
1248    this.prepareForRender(
1249      formatDate(this.focused, this.switchLabelFormat, this.locale),
1250      this.first <= this.minDate,
1251      this.last >= this.maxDate
1252    );
1253
1254    if (this.weekNumbers) {
1255      const weekStart = this.weekStart;
1256      const startOfWeek = dayOfTheWeekOf(this.first, weekStart, weekStart);
1257      Array.from(this.weekNumbers.weeks.children).forEach((el, index) => {
1258        const dateOfWeekStart = addWeeks(startOfWeek, index);
1259        el.textContent = this.getWeekNumber(dateOfWeekStart, weekStart);
1260        if (index > 3) {
1261          el.classList[dateOfWeekStart > this.last ? 'add' : 'remove']('next');
1262        }
1263      });
1264    }
1265    Array.from(this.grid.children).forEach((el, index) => {
1266      const current = addDays(this.start, index);
1267      const dateObj = new Date(current);
1268      const day = dateObj.getDay();
1269      const extraClasses = [];
1270
1271      if (this.today === current) {
1272        extraClasses.push('today');
1273      }
1274      if (this.daysOfWeekHighlighted.includes(day)) {
1275        extraClasses.push('highlighted');
1276      }
1277
1278      this.renderCell(
1279        el,
1280        dateObj.getDate(),
1281        current,
1282        current,
1283        this,
1284        current < this.minDate
1285          || current > this.maxDate
1286          || this.daysOfWeekDisabled.includes(day),
1287        extraClasses
1288      );
1289    });
1290  }
1291
1292  // Update the view UI by applying the changes of selected and focused items
1293  refresh() {
1294    const range = this.range || [];
1295    Array.from(this.grid.children).forEach((el) => {
1296      this.refreshCell(el, Number(el.dataset.date), this.selected, range);
1297    });
1298  }
1299
1300  // Update the view UI by applying the change of focused item
1301  refreshFocus() {
1302    this.changeFocusedCell(Math.round((this.focused - this.start) / 86400000));
1303  }
1304}
1305
1306function computeMonthRange(range, thisYear) {
1307  if (!range || !range[0] || !range[1]) {
1308    return;
1309  }
1310
1311  const [[startY, startM], [endY, endM]] = range;
1312  if (startY > thisYear || endY < thisYear) {
1313    return;
1314  }
1315  return [
1316    startY === thisYear ? startM : -1,
1317    endY === thisYear ? endM : 12,
1318  ];
1319}
1320
1321class MonthsView extends View {
1322  constructor(picker) {
1323    super(picker, {
1324      id: 1,
1325      name: 'months',
1326      cellClass: 'month',
1327    });
1328  }
1329
1330  init(options, onConstruction = true) {
1331    if (onConstruction) {
1332      this.grid = this.element;
1333      this.element.classList.add('months', 'datepicker-grid');
1334      this.grid.appendChild(parseHTML(createTagRepeat('span', 12, {'data-month': ix => ix})));
1335      this.first = 0;
1336      this.last = 11;
1337    }
1338    super.init(options);
1339  }
1340
1341  setOptions(options) {
1342    if (options.locale) {
1343      this.monthNames = options.locale.monthsShort;
1344    }
1345    if ('minDate' in options) {
1346      if (options.minDate === undefined) {
1347        this.minYear = this.minMonth = this.minDate = undefined;
1348      } else {
1349        const minDateObj = new Date(options.minDate);
1350        this.minYear = minDateObj.getFullYear();
1351        this.minMonth = minDateObj.getMonth();
1352        this.minDate = minDateObj.setDate(1);
1353      }
1354    }
1355    if ('maxDate' in options) {
1356      if (options.maxDate === undefined) {
1357        this.maxYear = this.maxMonth = this.maxDate = undefined;
1358      } else {
1359        const maxDateObj = new Date(options.maxDate);
1360        this.maxYear = maxDateObj.getFullYear();
1361        this.maxMonth = maxDateObj.getMonth();
1362        this.maxDate = dateValue(this.maxYear, this.maxMonth + 1, 0);
1363      }
1364    }
1365    if (options.checkDisabled) {
1366      this.checkDisabled = this.isMinView || options.datesDisabled === null
1367        ? options.checkDisabled
1368        : () => false;
1369    }
1370    if ('beforeShowMonth' in options) {
1371      this.beforeShow = typeof options.beforeShowMonth === 'function'
1372        ? options.beforeShowMonth
1373        : undefined;
1374    }
1375  }
1376
1377  // Update view's settings to reflect the viewDate set on the picker
1378  updateFocus() {
1379    const viewDate = new Date(this.picker.viewDate);
1380    this.year = viewDate.getFullYear();
1381    this.focused = viewDate.getMonth();
1382  }
1383
1384  // Update view's settings to reflect the selected dates
1385  updateSelection() {
1386    const {dates, rangepicker} = this.picker.datepicker;
1387    this.selected = dates.reduce((selected, timeValue) => {
1388      const date = new Date(timeValue);
1389      const year = date.getFullYear();
1390      const month = date.getMonth();
1391      if (selected[year] === undefined) {
1392        selected[year] = [month];
1393      } else {
1394        pushUnique(selected[year], month);
1395      }
1396      return selected;
1397    }, {});
1398    if (rangepicker && rangepicker.dates) {
1399      this.range = rangepicker.dates.map(timeValue => {
1400        const date = new Date(timeValue);
1401        return isNaN(date) ? undefined : [date.getFullYear(), date.getMonth()];
1402      });
1403    }
1404  }
1405
1406  // Update the entire view UI
1407  render() {
1408    this.prepareForRender(
1409      this.year,
1410      this.year <= this.minYear,
1411      this.year >= this.maxYear
1412    );
1413
1414    const selected = this.selected[this.year] || [];
1415    const yrOutOfRange = this.year < this.minYear || this.year > this.maxYear;
1416    const isMinYear = this.year === this.minYear;
1417    const isMaxYear = this.year === this.maxYear;
1418    const range = computeMonthRange(this.range, this.year);
1419
1420    Array.from(this.grid.children).forEach((el, index) => {
1421      const date = regularizeDate(new Date(this.year, index, 1), 1, this.isRangeEnd);
1422
1423      this.renderCell(
1424        el,
1425        this.monthNames[index],
1426        index,
1427        date,
1428        {selected, range},
1429        yrOutOfRange
1430          || isMinYear && index < this.minMonth
1431          || isMaxYear && index > this.maxMonth
1432      );
1433    });
1434  }
1435
1436  // Update the view UI by applying the changes of selected and focused items
1437  refresh() {
1438    const selected = this.selected[this.year] || [];
1439    const range = computeMonthRange(this.range, this.year) || [];
1440    Array.from(this.grid.children).forEach((el, index) => {
1441      this.refreshCell(el, index, selected, range);
1442    });
1443  }
1444
1445  // Update the view UI by applying the change of focused item
1446  refreshFocus() {
1447    this.changeFocusedCell(this.focused);
1448  }
1449}
1450
1451function toTitleCase(word) {
1452  return [...word].reduce((str, ch, ix) => str += ix ? ch : ch.toUpperCase(), '');
1453}
1454
1455// Class representing the years and decades view elements
1456class YearsView extends View {
1457  constructor(picker, config) {
1458    super(picker, config);
1459  }
1460
1461  init(options, onConstruction = true) {
1462    if (onConstruction) {
1463      this.navStep = this.step * 10;
1464      this.beforeShowOption = `beforeShow${toTitleCase(this.cellClass)}`;
1465      this.grid = this.element;
1466      this.element.classList.add(this.name, 'datepicker-grid');
1467      this.grid.appendChild(parseHTML(createTagRepeat('span', 12)));
1468    }
1469    super.init(options);
1470  }
1471
1472  setOptions(options) {
1473    if ('minDate' in options) {
1474      if (options.minDate === undefined) {
1475        this.minYear = this.minDate = undefined;
1476      } else {
1477        this.minYear = startOfYearPeriod(options.minDate, this.step);
1478        this.minDate = dateValue(this.minYear, 0, 1);
1479      }
1480    }
1481    if ('maxDate' in options) {
1482      if (options.maxDate === undefined) {
1483        this.maxYear = this.maxDate = undefined;
1484      } else {
1485        this.maxYear = startOfYearPeriod(options.maxDate, this.step);
1486        this.maxDate = dateValue(this.maxYear, 11, 31);
1487      }
1488    }
1489    if (options.checkDisabled) {
1490      this.checkDisabled = this.isMinView || options.datesDisabled === null
1491        ? options.checkDisabled
1492        : () => false;
1493    }
1494    if (this.beforeShowOption in options) {
1495      const beforeShow = options[this.beforeShowOption];
1496      this.beforeShow = typeof beforeShow === 'function' ? beforeShow : undefined;
1497    }
1498  }
1499
1500  // Update view's settings to reflect the viewDate set on the picker
1501  updateFocus() {
1502    const viewDate = new Date(this.picker.viewDate);
1503    const first = startOfYearPeriod(viewDate, this.navStep);
1504    const last = first + 9 * this.step;
1505
1506    this.first = first;
1507    this.last = last;
1508    this.start = first - this.step;
1509    this.focused = startOfYearPeriod(viewDate, this.step);
1510  }
1511
1512  // Update view's settings to reflect the selected dates
1513  updateSelection() {
1514    const {dates, rangepicker} = this.picker.datepicker;
1515    this.selected = dates.reduce((years, timeValue) => {
1516      return pushUnique(years, startOfYearPeriod(timeValue, this.step));
1517    }, []);
1518    if (rangepicker && rangepicker.dates) {
1519      this.range = rangepicker.dates.map(timeValue => {
1520        if (timeValue !== undefined) {
1521          return startOfYearPeriod(timeValue, this.step);
1522        }
1523      });
1524    }
1525  }
1526
1527  // Update the entire view UI
1528  render() {
1529    this.prepareForRender(
1530      `${this.first}-${this.last}`,
1531      this.first <= this.minYear,
1532      this.last >= this.maxYear
1533    );
1534
1535    Array.from(this.grid.children).forEach((el, index) => {
1536      const current = this.start + (index * this.step);
1537      const date = regularizeDate(new Date(current, 0, 1), 2, this.isRangeEnd);
1538
1539      el.dataset.year = current;
1540      this.renderCell(
1541        el,
1542        current,
1543        current,
1544        date,
1545        this,
1546        current < this.minYear || current > this.maxYear
1547      );
1548    });
1549  }
1550
1551  // Update the view UI by applying the changes of selected and focused items
1552  refresh() {
1553    const range = this.range || [];
1554    Array.from(this.grid.children).forEach((el) => {
1555      this.refreshCell(el, Number(el.textContent), this.selected, range);
1556    });
1557  }
1558
1559  // Update the view UI by applying the change of focused item
1560  refreshFocus() {
1561    this.changeFocusedCell(Math.round((this.focused - this.start) / this.step));
1562  }
1563}
1564
1565function triggerDatepickerEvent(datepicker, type) {
1566  const options = {
1567    bubbles: true,
1568    cancelable: true,
1569    detail: {
1570      date: datepicker.getDate(),
1571      viewDate: new Date(datepicker.picker.viewDate),
1572      viewId: datepicker.picker.currentView.id,
1573      datepicker,
1574    },
1575  };
1576  datepicker.element.dispatchEvent(new CustomEvent(type, options));
1577}
1578
1579// direction: -1 (to previous), 1 (to next)
1580function goToPrevOrNext(datepicker, direction) {
1581  const {config, picker} = datepicker;
1582  const {currentView, viewDate} = picker;
1583  let newViewDate;
1584  switch (currentView.id) {
1585    case 0:
1586      newViewDate = addMonths(viewDate, direction);
1587      break;
1588    case 1:
1589      newViewDate = addYears(viewDate, direction);
1590      break;
1591    default:
1592      newViewDate = addYears(viewDate, direction * currentView.navStep);
1593  }
1594  newViewDate = limitToRange(newViewDate, config.minDate, config.maxDate);
1595  picker.changeFocus(newViewDate).render();
1596}
1597
1598function switchView(datepicker) {
1599  const viewId = datepicker.picker.currentView.id;
1600  if (viewId === datepicker.config.maxView) {
1601    return;
1602  }
1603  datepicker.picker.changeView(viewId + 1).render();
1604}
1605
1606function clearSelection(datepicker) {
1607  datepicker.setDate({clear: true});
1608}
1609
1610function goToOrSelectToday(datepicker) {
1611  const currentDate = today();
1612  if (datepicker.config.todayButtonMode === 1) {
1613    datepicker.setDate(currentDate, {forceRefresh: true, viewDate: currentDate});
1614  } else {
1615    datepicker.setFocusedDate(currentDate, true);
1616  }
1617}
1618
1619function unfocus(datepicker) {
1620  const onBlur = () => {
1621    if (datepicker.config.updateOnBlur) {
1622      datepicker.update({revert: true});
1623    } else {
1624      datepicker.refresh('input');
1625    }
1626    datepicker.hide();
1627  };
1628  const element = datepicker.element;
1629
1630  if (isActiveElement(element)) {
1631    element.addEventListener('blur', onBlur, {once: true});
1632  } else {
1633    onBlur();
1634  }
1635}
1636
1637function goToSelectedMonthOrYear(datepicker, selection) {
1638  const picker = datepicker.picker;
1639  const viewDate = new Date(picker.viewDate);
1640  const viewId = picker.currentView.id;
1641  const newDate = viewId === 1
1642    ? addMonths(viewDate, selection - viewDate.getMonth())
1643    : addYears(viewDate, selection - viewDate.getFullYear());
1644
1645  picker.changeFocus(newDate).changeView(viewId - 1).render();
1646}
1647
1648function onClickViewSwitch(datepicker) {
1649  switchView(datepicker);
1650}
1651
1652function onClickPrevButton(datepicker) {
1653  goToPrevOrNext(datepicker, -1);
1654}
1655
1656function onClickNextButton(datepicker) {
1657  goToPrevOrNext(datepicker, 1);
1658}
1659
1660// For the picker's main block to delegete the events from `datepicker-cell`s
1661function onClickView(datepicker, ev) {
1662  const target = findElementInEventPath(ev, '.datepicker-cell');
1663  if (!target || target.classList.contains('disabled')) {
1664    return;
1665  }
1666
1667  const {id, isMinView} = datepicker.picker.currentView;
1668  const data = target.dataset;
1669  if (isMinView) {
1670    datepicker.setDate(Number(data.date));
1671  } else if (id === 1) {
1672    goToSelectedMonthOrYear(datepicker, Number(data.month));
1673  } else {
1674    goToSelectedMonthOrYear(datepicker, Number(data.year));
1675  }
1676}
1677
1678function onMousedownPicker(ev) {
1679  ev.preventDefault();
1680}
1681
1682const orientClasses = ['left', 'top', 'right', 'bottom'].reduce((obj, key) => {
1683  obj[key] = `datepicker-orient-${key}`;
1684  return obj;
1685}, {});
1686const toPx = num => num ? `${num}px` : num;
1687
1688function processPickerOptions(picker, options) {
1689  if ('title' in options) {
1690    if (options.title) {
1691      picker.controls.title.textContent = options.title;
1692      showElement(picker.controls.title);
1693    } else {
1694      picker.controls.title.textContent = '';
1695      hideElement(picker.controls.title);
1696    }
1697  }
1698  if (options.prevArrow) {
1699    const prevButton = picker.controls.prevButton;
1700    emptyChildNodes(prevButton);
1701    options.prevArrow.forEach((node) => {
1702      prevButton.appendChild(node.cloneNode(true));
1703    });
1704  }
1705  if (options.nextArrow) {
1706    const nextButton = picker.controls.nextButton;
1707    emptyChildNodes(nextButton);
1708    options.nextArrow.forEach((node) => {
1709      nextButton.appendChild(node.cloneNode(true));
1710    });
1711  }
1712  if (options.locale) {
1713    picker.controls.todayButton.textContent = options.locale.today;
1714    picker.controls.clearButton.textContent = options.locale.clear;
1715  }
1716  if ('todayButton' in options) {
1717    if (options.todayButton) {
1718      showElement(picker.controls.todayButton);
1719    } else {
1720      hideElement(picker.controls.todayButton);
1721    }
1722  }
1723  if ('minDate' in options || 'maxDate' in options) {
1724    const {minDate, maxDate} = picker.datepicker.config;
1725    picker.controls.todayButton.disabled = !isInRange(today(), minDate, maxDate);
1726  }
1727  if ('clearButton' in options) {
1728    if (options.clearButton) {
1729      showElement(picker.controls.clearButton);
1730    } else {
1731      hideElement(picker.controls.clearButton);
1732    }
1733  }
1734}
1735
1736// Compute view date to reset, which will be...
1737// - the last item of the selected dates or defaultViewDate if no selection
1738// - limitted to minDate or maxDate if it exceeds the range
1739function computeResetViewDate(datepicker) {
1740  const {dates, config, rangeSideIndex} = datepicker;
1741  const viewDate = dates.length > 0
1742    ? lastItemOf(dates)
1743    : regularizeDate(config.defaultViewDate, config.pickLevel, rangeSideIndex);
1744  return limitToRange(viewDate, config.minDate, config.maxDate);
1745}
1746
1747// Change current view's view date
1748function setViewDate(picker, newDate) {
1749  if (!('_oldViewDate' in picker) && newDate !== picker.viewDate) {
1750    picker._oldViewDate = picker.viewDate;
1751  }
1752  picker.viewDate = newDate;
1753
1754  // return whether the new date is in different period on time from the one
1755  // displayed in the current view
1756  // when true, the view needs to be re-rendered on the next UI refresh.
1757  const {id, year, first, last} = picker.currentView;
1758  const viewYear = new Date(newDate).getFullYear();
1759  switch (id) {
1760    case 0:
1761      return newDate < first || newDate > last;
1762    case 1:
1763      return viewYear !== year;
1764    default:
1765      return viewYear < first || viewYear > last;
1766  }
1767}
1768
1769function getTextDirection(el) {
1770  return window.getComputedStyle(el).direction;
1771}
1772
1773// find the closet scrollable ancestor elemnt under the body
1774function findScrollParents(el) {
1775  const parent = getParent(el);
1776  if (parent === document.body || !parent) {
1777    return;
1778  }
1779
1780  // checking overflow only is enough because computed overflow cannot be
1781  // visible or a combination of visible and other when either axis is set
1782  // to other than visible.
1783  // (Setting one axis to other than 'visible' while the other is 'visible'
1784  // results in the other axis turning to 'auto')
1785  return window.getComputedStyle(parent).overflow !== 'visible'
1786    ? parent
1787    : findScrollParents(parent);
1788}
1789
1790// Class representing the picker UI
1791class Picker {
1792  constructor(datepicker) {
1793    const {config, inputField} = this.datepicker = datepicker;
1794
1795    const template = pickerTemplate.replace(/%buttonClass%/g, config.buttonClass);
1796    const element = this.element = parseHTML(template).firstChild;
1797    const [header, main, footer] = element.firstChild.children;
1798    const title = header.firstElementChild;
1799    const [prevButton, viewSwitch, nextButton] = header.lastElementChild.children;
1800    const [todayButton, clearButton] = footer.firstChild.children;
1801    const controls = {
1802      title,
1803      prevButton,
1804      viewSwitch,
1805      nextButton,
1806      todayButton,
1807      clearButton,
1808    };
1809    this.main = main;
1810    this.controls = controls;
1811
1812    const elementClass = inputField ? 'dropdown' : 'inline';
1813    element.classList.add(`datepicker-${elementClass}`);
1814
1815    processPickerOptions(this, config);
1816    this.viewDate = computeResetViewDate(datepicker);
1817
1818    // set up event listeners
1819    registerListeners(datepicker, [
1820      [element, 'mousedown', onMousedownPicker],
1821      [main, 'click', onClickView.bind(null, datepicker)],
1822      [controls.viewSwitch, 'click', onClickViewSwitch.bind(null, datepicker)],
1823      [controls.prevButton, 'click', onClickPrevButton.bind(null, datepicker)],
1824      [controls.nextButton, 'click', onClickNextButton.bind(null, datepicker)],
1825      [controls.todayButton, 'click', goToOrSelectToday.bind(null, datepicker)],
1826      [controls.clearButton, 'click', clearSelection.bind(null, datepicker)],
1827    ]);
1828
1829    // set up views
1830    this.views = [
1831      new DaysView(this),
1832      new MonthsView(this),
1833      new YearsView(this, {id: 2, name: 'years', cellClass: 'year', step: 1}),
1834      new YearsView(this, {id: 3, name: 'decades', cellClass: 'decade', step: 10}),
1835    ];
1836    this.currentView = this.views[config.startView];
1837
1838    this.currentView.render();
1839    this.main.appendChild(this.currentView.element);
1840    if (config.container) {
1841      config.container.appendChild(this.element);
1842    } else {
1843      inputField.after(this.element);
1844    }
1845  }
1846
1847  setOptions(options) {
1848    processPickerOptions(this, options);
1849    this.views.forEach((view) => {
1850      view.init(options, false);
1851    });
1852    this.currentView.render();
1853  }
1854
1855  detach() {
1856    this.element.remove();
1857  }
1858
1859  show() {
1860    if (this.active) {
1861      return;
1862    }
1863
1864    const {datepicker, element} = this;
1865    const inputField = datepicker.inputField;
1866    if (inputField) {
1867      // ensure picker's direction matches input's
1868      const inputDirection = getTextDirection(inputField);
1869      if (inputDirection !== getTextDirection(getParent(element))) {
1870        element.dir = inputDirection;
1871      } else if (element.dir) {
1872        element.removeAttribute('dir');
1873      }
1874
1875      // Determine the picker's position first to prevent `orientation: 'auto'`
1876      // from being miscalculated to 'bottom' after the picker temporarily
1877      // shown below the input field expands the document height if the field
1878      // is at the bottom edge of the document
1879      this.place();
1880      element.classList.add('active');
1881
1882      if (datepicker.config.disableTouchKeyboard) {
1883        inputField.blur();
1884      }
1885    } else {
1886      element.classList.add('active');
1887    }
1888    this.active = true;
1889    triggerDatepickerEvent(datepicker, 'show');
1890  }
1891
1892  hide() {
1893    if (!this.active) {
1894      return;
1895    }
1896    this.datepicker.exitEditMode();
1897    this.element.classList.remove('active');
1898    this.active = false;
1899    triggerDatepickerEvent(this.datepicker, 'hide');
1900  }
1901
1902  place() {
1903    const {classList, style} = this.element;
1904    // temporarily display the picker to get its size and offset parent
1905    style.display = 'block';
1906
1907    const {
1908      width: calendarWidth,
1909      height: calendarHeight,
1910    } = this.element.getBoundingClientRect();
1911    const offsetParent = this.element.offsetParent;
1912    // turn the picker back to hidden so that the position is determined with
1913    // the state before it is shown.
1914    style.display = '';
1915
1916    const {config, inputField} = this.datepicker;
1917    const {
1918      left: inputLeft,
1919      top: inputTop,
1920      right: inputRight,
1921      bottom: inputBottom,
1922      width: inputWidth,
1923      height: inputHeight
1924    } = inputField.getBoundingClientRect();
1925    let {x: orientX, y: orientY} = config.orientation;
1926    let left = inputLeft;
1927    let top = inputTop;
1928
1929    // caliculate offsetLeft/Top of inputField
1930    if (offsetParent === document.body || !offsetParent) {
1931      left += window.scrollX;
1932      top += window.scrollY;
1933    } else {
1934      const offsetParentRect = offsetParent.getBoundingClientRect();
1935      left -= offsetParentRect.left - offsetParent.scrollLeft;
1936      top -= offsetParentRect.top - offsetParent.scrollTop;
1937    }
1938
1939    // caliculate the boundaries of the visible area that contains inputField
1940    const scrollParent = findScrollParents(inputField);
1941    let scrollAreaLeft = 0;
1942    let scrollAreaTop = 0;
1943    let {
1944      clientWidth: scrollAreaRight,
1945      clientHeight: scrollAreaBottom,
1946    } = document.documentElement;
1947
1948    if (scrollParent) {
1949      const scrollParentRect = scrollParent.getBoundingClientRect();
1950      if (scrollParentRect.top > 0) {
1951        scrollAreaTop = scrollParentRect.top;
1952      }
1953      if (scrollParentRect.left > 0) {
1954        scrollAreaLeft = scrollParentRect.left;
1955      }
1956      if (scrollParentRect.right < scrollAreaRight) {
1957        scrollAreaRight = scrollParentRect.right;
1958      }
1959      if (scrollParentRect.bottom < scrollAreaBottom) {
1960        scrollAreaBottom = scrollParentRect.bottom;
1961      }
1962    }
1963
1964    // determine the horizontal orientation and left position
1965    let adjustment = 0;
1966    if (orientX === 'auto') {
1967      if (inputLeft < scrollAreaLeft) {
1968        orientX = 'left';
1969        adjustment = scrollAreaLeft - inputLeft;
1970      } else if (inputLeft + calendarWidth > scrollAreaRight) {
1971        orientX = 'right';
1972        if (scrollAreaRight < inputRight) {
1973          adjustment = scrollAreaRight - inputRight;
1974        }
1975      } else if (getTextDirection(inputField) === 'rtl') {
1976        orientX = inputRight - calendarWidth < scrollAreaLeft ? 'left' : 'right';
1977      } else {
1978        orientX = 'left';
1979      }
1980    }
1981    if (orientX === 'right') {
1982      left += inputWidth - calendarWidth;
1983    }
1984    left += adjustment;
1985
1986    // determine the vertical orientation and top position
1987    if (orientY === 'auto') {
1988      if (inputTop - calendarHeight > scrollAreaTop) {
1989        orientY = inputBottom + calendarHeight > scrollAreaBottom ? 'top' : 'bottom';
1990      } else {
1991        orientY = 'bottom';
1992      }
1993    }
1994    if (orientY === 'top') {
1995      top -= calendarHeight;
1996    } else {
1997      top += inputHeight;
1998    }
1999
2000    classList.remove(...Object.values(orientClasses));
2001    classList.add(orientClasses[orientX], orientClasses[orientY]);
2002
2003    style.left = toPx(left);
2004    style.top = toPx(top);
2005  }
2006
2007  setViewSwitchLabel(labelText) {
2008    this.controls.viewSwitch.textContent = labelText;
2009  }
2010
2011  setPrevButtonDisabled(disabled) {
2012    this.controls.prevButton.disabled = disabled;
2013  }
2014
2015  setNextButtonDisabled(disabled) {
2016    this.controls.nextButton.disabled = disabled;
2017  }
2018
2019  changeView(viewId) {
2020    const currentView = this.currentView;
2021    if (viewId !== currentView.id) {
2022      if (!this._oldView) {
2023        this._oldView = currentView;
2024      }
2025      this.currentView = this.views[viewId];
2026      this._renderMethod = 'render';
2027    }
2028    return this;
2029  }
2030
2031  // Change the focused date (view date)
2032  changeFocus(newViewDate) {
2033    this._renderMethod = setViewDate(this, newViewDate) ? 'render' : 'refreshFocus';
2034    this.views.forEach((view) => {
2035      view.updateFocus();
2036    });
2037    return this;
2038  }
2039
2040  // Apply the change of the selected dates
2041  update(viewDate = undefined) {
2042    const newViewDate = viewDate === undefined
2043      ? computeResetViewDate(this.datepicker)
2044      : viewDate;
2045    this._renderMethod = setViewDate(this, newViewDate) ? 'render' : 'refresh';
2046    this.views.forEach((view) => {
2047      view.updateFocus();
2048      view.updateSelection();
2049    });
2050    return this;
2051  }
2052
2053  // Refresh the picker UI
2054  render(quickRender = true) {
2055    const {currentView, datepicker, _oldView: oldView} = this;
2056    const oldViewDate = new Date(this._oldViewDate);
2057    const renderMethod = (quickRender && this._renderMethod) || 'render';
2058    delete this._oldView;
2059    delete this._oldViewDate;
2060    delete this._renderMethod;
2061
2062    currentView[renderMethod]();
2063    if (oldView) {
2064      this.main.replaceChild(currentView.element, oldView.element);
2065      triggerDatepickerEvent(datepicker, 'changeView');
2066    }
2067    if (!isNaN(oldViewDate)) {
2068      const newViewDate = new Date(this.viewDate);
2069      if (newViewDate.getFullYear() !== oldViewDate.getFullYear()) {
2070        triggerDatepickerEvent(datepicker, 'changeYear');
2071      }
2072      if (newViewDate.getMonth() !== oldViewDate.getMonth()) {
2073        triggerDatepickerEvent(datepicker, 'changeMonth');
2074      }
2075    }
2076  }
2077}
2078
2079// Find the closest date that doesn't meet the condition for unavailable date
2080// Returns undefined if no available date is found
2081// addFn: function to calculate the next date
2082//   - args: time value, amount
2083// increase: amount to pass to addFn
2084// testFn: function to test the unavailability of the date
2085//   - args: time value; return: true if unavailable
2086function findNextAvailableOne(date, addFn, increase, testFn, min, max) {
2087  if (!isInRange(date, min, max)) {
2088    return;
2089  }
2090  if (testFn(date)) {
2091    const newDate = addFn(date, increase);
2092    return findNextAvailableOne(newDate, addFn, increase, testFn, min, max);
2093  }
2094  return date;
2095}
2096
2097// direction: -1 (left/up), 1 (right/down)
2098// vertical: true for up/down, false for left/right
2099function moveByArrowKey(datepicker, direction, vertical) {
2100  const picker = datepicker.picker;
2101  const currentView = picker.currentView;
2102  const step = currentView.step || 1;
2103  let viewDate = picker.viewDate;
2104  let addFn;
2105  switch (currentView.id) {
2106    case 0:
2107      viewDate = addDays(viewDate, vertical ? direction * 7 : direction);
2108      addFn = addDays;
2109      break;
2110    case 1:
2111      viewDate = addMonths(viewDate, vertical ? direction * 4 : direction);
2112      addFn = addMonths;
2113      break;
2114    default:
2115      viewDate = addYears(viewDate, direction * (vertical ? 4 : 1) * step);
2116      addFn = addYears;
2117  }
2118  viewDate = findNextAvailableOne(
2119    viewDate,
2120    addFn,
2121    direction < 0 ? -step : step,
2122    (date) => currentView.disabled.includes(date),
2123    currentView.minDate,
2124    currentView.maxDate
2125  );
2126  if (viewDate !== undefined) {
2127    picker.changeFocus(viewDate).render();
2128  }
2129}
2130
2131function onKeydown(datepicker, ev) {
2132  const {config, picker, editMode} = datepicker;
2133  const active = picker.active;
2134  const {key, altKey, shiftKey} = ev;
2135  const ctrlOrMetaKey = ev.ctrlKey || ev.metaKey;
2136  const cancelEvent = () => {
2137    ev.preventDefault();
2138    ev.stopPropagation();
2139  };
2140
2141  // tab/enter keys should not be taken by shortcut keys
2142  if (key === 'Tab') {
2143    unfocus(datepicker);
2144    return;
2145  }
2146  if (key === 'Enter') {
2147    if (!active) {
2148      datepicker.update();
2149    } else if (editMode) {
2150      datepicker.exitEditMode({update: true, autohide: config.autohide});
2151    } else {
2152      const currentView = picker.currentView;
2153      if (currentView.isMinView) {
2154        datepicker.setDate(picker.viewDate);
2155      } else {
2156        picker.changeView(currentView.id - 1).render();
2157        cancelEvent();
2158      }
2159    }
2160    return;
2161  }
2162
2163  const shortcutKeys = config.shortcutKeys;
2164  const keyInfo = {key, ctrlOrMetaKey, altKey, shiftKey};
2165  const shortcut = Object.keys(shortcutKeys).find((item) => {
2166    const keyDef = shortcutKeys[item];
2167    return !Object.keys(keyDef).find(prop => keyDef[prop] !== keyInfo[prop]);
2168  });
2169  if (shortcut) {
2170    let action;
2171    if (shortcut === 'toggle') {
2172      action = shortcut;
2173    } else if (editMode) {
2174      if (shortcut === 'exitEditMode') {
2175        action = shortcut;
2176      }
2177    } else if (active) {
2178      if (shortcut === 'hide') {
2179        action = shortcut;
2180      } else if (shortcut === 'prevButton') {
2181        action = [goToPrevOrNext, [datepicker, -1]];
2182      } else if (shortcut === 'nextButton') {
2183        action = [goToPrevOrNext, [datepicker, 1]];
2184      } else if (shortcut === 'viewSwitch') {
2185        action = [switchView, [datepicker]];
2186      } else if (config.clearButton && shortcut === 'clearButton') {
2187        action = [clearSelection, [datepicker]];
2188      } else if (config.todayButton && shortcut === 'todayButton') {
2189        action = [goToOrSelectToday, [datepicker]];
2190      }
2191    } else if (shortcut === 'show') {
2192      action = shortcut;
2193    }
2194    if (action) {
2195      if (Array.isArray(action)) {
2196        action[0].apply(null, action[1]);
2197      } else {
2198        datepicker[action]();
2199      }
2200      cancelEvent();
2201      return;
2202    }
2203  }
2204
2205  // perform as a regular <input> when picker in hidden or in edit mode
2206  if (!active || editMode) {
2207    return;
2208  }
2209
2210  const handleArrowKeyPress = (direction, vertical) => {
2211    if (shiftKey || ctrlOrMetaKey || altKey) {
2212      datepicker.enterEditMode();
2213    } else {
2214      moveByArrowKey(datepicker, direction, vertical);
2215      ev.preventDefault();
2216    }
2217  };
2218
2219  if (key === 'ArrowLeft') {
2220    handleArrowKeyPress(-1, false);
2221  } else if (key === 'ArrowRight') {
2222    handleArrowKeyPress(1, false);
2223  } else if (key === 'ArrowUp') {
2224    handleArrowKeyPress(-1, true);
2225  } else if (key === 'ArrowDown') {
2226    handleArrowKeyPress(1, true);
2227  } else if (
2228    key === 'Backspace'
2229    || key === 'Delete'
2230      // When autofill is performed, Chromium-based browsers trigger fake
2231      // keydown/keyup events that don't have the `key` property (on Edge,
2232      // keyup only) in addition to the input event. Therefore, we need to
2233      // check the existence of `key`'s value before checking the length.
2234      // (issue #144)
2235    || (key && key.length === 1 && !ctrlOrMetaKey)
2236  ) {
2237    datepicker.enterEditMode();
2238  }
2239}
2240
2241function onFocus(datepicker) {
2242  if (datepicker.config.showOnFocus && !datepicker._showing) {
2243    datepicker.show();
2244  }
2245}
2246
2247// for the prevention for entering edit mode while getting focus on click
2248function onMousedown(datepicker, ev) {
2249  const el = ev.target;
2250  if (datepicker.picker.active || datepicker.config.showOnClick) {
2251    el._active = isActiveElement(el);
2252    el._clicking = setTimeout(() => {
2253      delete el._active;
2254      delete el._clicking;
2255    }, 2000);
2256  }
2257}
2258
2259function onClickInput(datepicker, ev) {
2260  const el = ev.target;
2261  if (!el._clicking) {
2262    return;
2263  }
2264  clearTimeout(el._clicking);
2265  delete el._clicking;
2266
2267  if (el._active) {
2268    datepicker.enterEditMode();
2269  }
2270  delete el._active;
2271
2272  if (datepicker.config.showOnClick) {
2273    datepicker.show();
2274  }
2275}
2276
2277function onPaste(datepicker, ev) {
2278  if (ev.clipboardData.types.includes('text/plain')) {
2279    datepicker.enterEditMode();
2280  }
2281}
2282
2283// for the `document` to delegate the events from outside the picker/input field
2284function onClickOutside(datepicker, ev) {
2285  const {element, picker} = datepicker;
2286  // check both picker's and input's activeness to make updateOnBlur work in
2287  // the cases where...
2288  // - picker is hidden by ESC key press → input stays focused
2289  // - input is unfocused by closing mobile keyboard → piker is kept shown
2290  if (!picker.active && !isActiveElement(element)) {
2291    return;
2292  }
2293  const pickerElem = picker.element;
2294  if (findElementInEventPath(ev, el => el === element || el === pickerElem)) {
2295    return;
2296  }
2297  unfocus(datepicker);
2298}
2299
2300function stringifyDates(dates, config) {
2301  return dates
2302    .map(dt => formatDate(dt, config.format, config.locale))
2303    .join(config.dateDelimiter);
2304}
2305
2306// parse input dates and create an array of time values for selection
2307// returns undefined if there are no valid dates in inputDates
2308// when origDates (current selection) is passed, the function works to mix
2309// the input dates into the current selection
2310function processInputDates(datepicker, inputDates, clear = false) {
2311  if (inputDates.length === 0) {
2312    // empty input is considered valid unless origiDates is passed
2313    return clear ? [] : undefined;
2314  }
2315
2316  const {config, dates: origDates, rangeSideIndex} = datepicker;
2317  const {pickLevel, maxNumberOfDates} = config;
2318  let newDates = inputDates.reduce((dates, dt) => {
2319    let date = parseDate(dt, config.format, config.locale);
2320    if (date === undefined) {
2321      return dates;
2322    }
2323    // adjust to 1st of the month/Jan 1st of the year
2324    // or to the last day of the monh/Dec 31st of the year if the datepicker
2325    // is the range-end picker of a rangepicker
2326    date = regularizeDate(date, pickLevel, rangeSideIndex);
2327    if (
2328      isInRange(date, config.minDate, config.maxDate)
2329      && !dates.includes(date)
2330      && !config.checkDisabled(date, pickLevel)
2331      && (pickLevel > 0 || !config.daysOfWeekDisabled.includes(new Date(date).getDay()))
2332    ) {
2333      dates.push(date);
2334    }
2335    return dates;
2336  }, []);
2337  if (newDates.length === 0) {
2338    return;
2339  }
2340  if (config.multidate && !clear) {
2341    // get the synmetric difference between origDates and newDates
2342    newDates = newDates.reduce((dates, date) => {
2343      if (!origDates.includes(date)) {
2344        dates.push(date);
2345      }
2346      return dates;
2347    }, origDates.filter(date => !newDates.includes(date)));
2348  }
2349  // do length check always because user can input multiple dates regardless of the mode
2350  return maxNumberOfDates && newDates.length > maxNumberOfDates
2351    ? newDates.slice(maxNumberOfDates * -1)
2352    : newDates;
2353}
2354
2355// refresh the UI elements
2356// modes: 1: input only, 2, picker only, 3 both
2357function refreshUI(datepicker, mode = 3, quickRender = true, viewDate = undefined) {
2358  const {config, picker, inputField} = datepicker;
2359  if (mode & 2) {
2360    const newView = picker.active ? config.pickLevel : config.startView;
2361    picker.update(viewDate).changeView(newView).render(quickRender);
2362  }
2363  if (mode & 1 && inputField) {
2364    inputField.value = stringifyDates(datepicker.dates, config);
2365  }
2366}
2367
2368function setDate(datepicker, inputDates, options) {
2369  const config = datepicker.config;
2370  let {clear, render, autohide, revert, forceRefresh, viewDate} = options;
2371  if (render === undefined) {
2372    render = true;
2373  }
2374  if (!render) {
2375    autohide = forceRefresh = false;
2376  } else if (autohide === undefined) {
2377    autohide = config.autohide;
2378  }
2379  viewDate = parseDate(viewDate, config.format, config.locale);
2380
2381  const newDates = processInputDates(datepicker, inputDates, clear);
2382  if (!newDates && !revert) {
2383    return;
2384  }
2385  if (newDates && newDates.toString() !== datepicker.dates.toString()) {
2386    datepicker.dates = newDates;
2387    refreshUI(datepicker, render ? 3 : 1, true, viewDate);
2388    triggerDatepickerEvent(datepicker, 'changeDate');
2389  } else {
2390    refreshUI(datepicker, forceRefresh ? 3 : 1, true, viewDate);
2391  }
2392
2393  if (autohide) {
2394    datepicker.hide();
2395  }
2396}
2397
2398function getOutputConverter(datepicker, format) {
2399  return format
2400    ? date => formatDate(date, format, datepicker.config.locale)
2401    : date => new Date(date);
2402}
2403
2404/**
2405 * Class representing a date picker
2406 */
2407class Datepicker {
2408  /**
2409   * Create a date picker
2410   * @param  {Element} element - element to bind a date picker
2411   * @param  {Object} [options] - config options
2412   * @param  {DateRangePicker} [rangepicker] - DateRangePicker instance the
2413   * date picker belongs to. Use this only when creating date picker as a part
2414   * of date range picker
2415   */
2416  constructor(element, options = {}, rangepicker = undefined) {
2417    element.datepicker = this;
2418    this.element = element;
2419    this.dates = [];
2420
2421    // initialize config
2422    const config = this.config = Object.assign({
2423      buttonClass: (options.buttonClass && String(options.buttonClass)) || 'button',
2424      container: null,
2425      defaultViewDate: today(),
2426      maxDate: undefined,
2427      minDate: undefined,
2428    }, processOptions(defaultOptions, this));
2429
2430    // configure by type
2431    let inputField;
2432    if (element.tagName === 'INPUT') {
2433      inputField = this.inputField = element;
2434      inputField.classList.add('datepicker-input');
2435      if (options.container) {
2436        // omit string type check because it doesn't guarantee to avoid errors
2437        // (invalid selector string causes abend with sytax error)
2438        config.container = options.container instanceof HTMLElement
2439          ? options.container
2440          : document.querySelector(options.container);
2441      }
2442    } else {
2443      config.container = element;
2444    }
2445    if (rangepicker) {
2446      // check validiry
2447      const index = rangepicker.inputs.indexOf(inputField);
2448      const datepickers = rangepicker.datepickers;
2449      if (index < 0 || index > 1 || !Array.isArray(datepickers)) {
2450        throw Error('Invalid rangepicker object.');
2451      }
2452      // attach itaelf to the rangepicker here so that processInputDates() can
2453      // determine if this is the range-end picker of the rangepicker while
2454      // setting inital values when pickLevel > 0
2455      datepickers[index] = this;
2456      this.rangepicker = rangepicker;
2457      this.rangeSideIndex = index;
2458    }
2459
2460    // set up config
2461    this._options = options;
2462    Object.assign(config, processOptions(options, this));
2463    config.shortcutKeys = createShortcutKeyConfig(options.shortcutKeys || {});
2464
2465    // process initial value
2466    const initialDates = stringToArray(
2467      element.value || element.dataset.date,
2468      config.dateDelimiter
2469    );
2470    delete element.dataset.date;
2471    const inputDateValues = processInputDates(this, initialDates);
2472    if (inputDateValues && inputDateValues.length > 0) {
2473      this.dates = inputDateValues;
2474    }
2475    if (inputField) {
2476      inputField.value = stringifyDates(this.dates, config);
2477    }
2478
2479    // set up picekr element
2480    const picker = this.picker = new Picker(this);
2481
2482    const keydownListener = [element, 'keydown', onKeydown.bind(null, this)];
2483    if (inputField) {
2484      registerListeners(this, [
2485        keydownListener,
2486        [inputField, 'focus', onFocus.bind(null, this)],
2487        [inputField, 'mousedown', onMousedown.bind(null, this)],
2488        [inputField, 'click', onClickInput.bind(null, this)],
2489        [inputField, 'paste', onPaste.bind(null, this)],
2490        // To detect a click on outside, just listening to mousedown is enough,
2491        // no need to listen to touchstart.
2492        // Actually, listening to touchstart can be a problem because, while
2493        // mousedown is fired only on tapping but not on swiping/pinching,
2494        // touchstart is fired on swiping/pinching as well.
2495        // (issue #95)
2496        [document, 'mousedown', onClickOutside.bind(null, this)],
2497        [window, 'resize', picker.place.bind(picker)]
2498      ]);
2499    } else {
2500      registerListeners(this, [keydownListener]);
2501      this.show();
2502    }
2503  }
2504
2505  /**
2506   * Format Date object or time value in given format and language
2507   * @param  {Date|Number} date - date or time value to format
2508   * @param  {String|Object}
2508 format - format string or object that contains
2509   * toDisplay() custom formatter, whose signature is
2510   * - args:
2511   *   - date: {Date} - Date instance of the date passed to the method
2512   *   - format: {Object} - the format object passed to the method
2513   *   - locale: {Object} - locale for the language specified by `lang`
2514   * - return:
2515   *     {String} formatted date
2516   * @param  {String} [lang=en] - language code for the locale to use
2517   * @return {String} formatted date
2518   */
2519  static formatDate(date, format, lang) {
2520    return formatDate(date, format, lang && locales[lang] || locales.en);
2521  }
2522
2523  /**
2524   * Parse date string
2525   * @param  {String|Date|Number} dateStr - date string, Date object or time
2526   * value to parse
2527   * @param  {String|Object} format - format string or object that contains
2528   * toValue() custom parser, whose signature is
2529   * - args:
2530   *   - dateStr: {String|Date|Number} - the dateStr passed to the method
2531   *   - format: {Object} - the format object passed to the method
2532   *   - locale: {Object} - locale for the language specified by `lang`
2533   * - return:
2534   *     {Date|Number} parsed date or its time value
2535   * @param  {String} [lang=en] - language code for the locale to use
2536   * @return {Number} time value of parsed date
2537   */
2538  static parseDate(dateStr, format, lang) {
2539    return parseDate(dateStr, format, lang && locales[lang] || locales.en);
2540  }
2541
2542  /**
2543   * @type {Object} - Installed locales in `[languageCode]: localeObject` format
2544   * en`:_English (US)_ is pre-installed.
2545   */
2546  static get locales() {
2547    return locales;
2548  }
2549
2550  /**
2551   * @type {Boolean} - Whether the picker element is shown. `true` whne shown
2552   */
2553  get active() {
2554    return !!(this.picker && this.picker.active);
2555  }
2556
2557  /**
2558   * @type {HTMLDivElement} - DOM object of picker element
2559   */
2560  get pickerElement() {
2561    return this.picker ? this.picker.element : undefined;
2562  }
2563
2564  /**
2565   * Set new values to the config options
2566   * @param {Object} options - config options to update
2567   */
2568  setOptions(options) {
2569    const newOptions = processOptions(options, this);
2570    Object.assign(this._options, options);
2571    Object.assign(this.config, newOptions);
2572    this.picker.setOptions(newOptions);
2573
2574    refreshUI(this, 3);
2575  }
2576
2577  /**
2578   * Show the picker element
2579   */
2580  show() {
2581    if (this.inputField) {
2582      const {config, inputField} = this;
2583      if (inputField.disabled || (inputField.readOnly && !config.enableOnReadonly)) {
2584        return;
2585      }
2586      if (!isActiveElement(inputField) && !config.disableTouchKeyboard) {
2587        this._showing = true;
2588        inputField.focus();
2589        delete this._showing;
2590      }
2591    }
2592    this.picker.show();
2593  }
2594
2595  /**
2596   * Hide the picker element
2597   * Not available on inline picker
2598   */
2599  hide() {
2600    if (!this.inputField) {
2601      return;
2602    }
2603    this.picker.hide();
2604    this.picker.update().changeView(this.config.startView).render();
2605  }
2606
2607  /**
2608   * Toggle the display of the picker element
2609   * Not available on inline picker
2610   *
2611   * Unlike hide(), the picker does not return to the start view when hiding.
2612   */
2613  toggle() {
2614    if (!this.picker.active) {
2615      this.show();
2616    } else if (this.inputField) {
2617      this.picker.hide();
2618    }
2619  }
2620
2621  /**
2622   * Destroy the Datepicker instance
2623   * @return {Detepicker} - the instance destroyed
2624   */
2625  destroy() {
2626    this.hide();
2627    unregisterListeners(this);
2628    this.picker.detach();
2629    const element = this.element;
2630    element.classList.remove('datepicker-input');
2631    delete element.datepicker;
2632    return this;
2633  }
2634
2635  /**
2636   * Get the selected date(s)
2637   *
2638   * The method returns a Date object of selected date by default, and returns
2639   * an array of selected dates in multidate mode. If format string is passed,
2640   * it returns date string(s) formatted in given format.
2641   *
2642   * @param  {String} [format] - format string to stringify the date(s)
2643   * @return {Date|String|Date[]|String[]} - selected date(s), or if none is
2644   * selected, empty array in multidate mode and undefined in sigledate mode
2645   */
2646  getDate(format = undefined) {
2647    const callback = getOutputConverter(this, format);
2648
2649    if (this.config.multidate) {
2650      return this.dates.map(callback);
2651    }
2652    if (this.dates.length > 0) {
2653      return callback(this.dates[0]);
2654    }
2655  }
2656
2657  /**
2658   * Set selected date(s)
2659   *
2660   * In multidate mode, you can pass multiple dates as a series of arguments
2661   * or an array. (Since each date is parsed individually, the type of the
2662   * dates doesn't have to be the same.)
2663   * The given dates are used to toggle the select status of each date. The
2664   * number of selected dates is kept from exceeding the length set to
2665   * maxNumberOfDates.
2666   *
2667   * With clear: true option, the method can be used to clear the selection
2668   * and to replace the selection instead of toggling in multidate mode.
2669   * If the option is passed with no date arguments or an empty dates array,
2670   * it works as "clear" (clear the selection then set nothing), and if the
2671   * option is passed with new dates to select, it works as "replace" (clear
2672   * the selection then set the given dates)
2673   *
2674   * When render: false option is used, the method omits re-rendering the
2675   * picker element. In this case, you need to call refresh() method later in
2676   * order for the picker element to reflect the changes. The input field is
2677   * refreshed always regardless of this option.
2678   *
2679   * When invalid (unparsable, repeated, disabled or out-of-range) dates are
2680   * passed, the method ignores them and applies only valid ones. In the case
2681   * that all the given dates are invalid, which is distinguished from passing
2682   * no dates, the method considers it as an error and leaves the selection
2683   * untouched. (The input field also remains untouched unless revert: true
2684   * option is used.)
2685   * Replacing the selection with the same date(s) also causes a similar
2686   * situation. In both cases, the method does not refresh the picker element
2687   * unless forceRefresh: true option is used.
2688   *
2689   * If viewDate option is used, the method changes the focused date to the
2690   * specified date instead of the last item of the selection.
2691   *
2692   * @param {...(Date|Number|String)|Array} [dates] - Date strings, Date
2693   * objects, time values or mix of those for new selection
2694   * @param {Object} [options] - function options
2695   * - clear: {boolean} - Whether to clear the existing selection
2696   *     defualt: false
2697   * - render: {boolean} - Whether to re-render the picker element
2698   *     default: true
2699   * - autohide: {boolean} - Whether to hide the picker element after re-render
2700   *     Ignored when used with render: false
2701   *     default: config.autohide
2702   * - revert: {boolean} - Whether to refresh the input field when all the
2703   *     passed dates are invalid
2704   *     default: false
2705   * - forceRefresh: {boolean} - Whether to refresh the picker element when
2706   *     passed dates don't change the existing selection
2707   *     default: false
2708   * - viewDate: {Date|Number|String} - Date to be focused after setiing date(s)
2709   *     default: The last item of the resulting selection, or defaultViewDate
2710   *     config option if none is selected
2711   */
2712  setDate(...args) {
2713    const dates = [...args];
2714    const opts = {};
2715    const lastArg = lastItemOf(args);
2716    if (
2717      lastArg
2718      && typeof lastArg === 'object'
2719      && !Array.isArray(lastArg)
2720      && !(lastArg instanceof Date)
2721    ) {
2722      Object.assign(opts, dates.pop());
2723    }
2724
2725    const inputDates = Array.isArray(dates[0]) ? dates[0] : dates;
2726    setDate(this, inputDates, opts);
2727  }
2728
2729  /**
2730   * Update the selected date(s) with input field's value
2731   * Not available on inline picker
2732   *
2733   * The input field will be refreshed with properly formatted date string.
2734   *
2735   * In the case that all the entered dates are invalid (unparsable, repeated,
2736   * disabled or out-of-range), which is distinguished from empty input field,
2737   * the method leaves the input field untouched as well as the selection by
2738   * default. If revert: true option is used in this case, the input field is
2739   * refreshed with the existing selection.
2740   * The method also doesn't refresh the picker element in this case and when
2741   * the entered dates are the same as the existing selection. If
2742   * forceRefresh: true option is used, the picker element is refreshed in
2743   * these cases too.
2744   *
2745   * @param  {Object} [options] - function options
2746   * - autohide: {boolean} - whether to hide the picker element after refresh
2747   *     default: false
2748   * - revert: {boolean} - Whether to refresh the input field when all the
2749   *     passed dates are invalid
2750   *     default: false
2751   * - forceRefresh: {boolean} - Whether to refresh the picer element when
2752   *     input field's value doesn't change the existing selection
2753   *     default: false
2754   */
2755  update(options = undefined) {
2756    if (!this.inputField) {
2757      return;
2758    }
2759
2760    const opts = Object.assign(options || {}, {clear: true, render: true, viewDate: undefined});
2761    const inputDates = stringToArray(this.inputField.value, this.config.dateDelimiter);
2762    setDate(this, inputDates, opts);
2763  }
2764
2765  /**
2766   * Get the focused date
2767   *
2768   * The method returns a Date object of focused date by default. If format
2769   * string is passed, it returns date string formatted in given format.
2770   *
2771   * @param  {String}
2771 [format] - format string to stringify the date
2772   * @return {Date|String} - focused date (viewDate)
2773   */
2774  getFocusedDate(format = undefined) {
2775    return getOutputConverter(this, format)(this.picker.viewDate);
2776  }
2777
2778  /**
2779   * Set focused date
2780   *
2781   * By default, the method updates the focus on the view shown at the time,
2782   * or the one set to the startView config option if the picker is hidden.
2783   * When resetView: true is passed, the view displayed is changed to the
2784   * pickLevel config option's if the picker is shown.
2785   *
2786   * @param {Date|Number|String} viewDate - date string, Date object, time
2787   * values of the date to focus
2788   * @param {Boolean} [resetView] - whether to change the view to pickLevel
2789   * config option's when the picker is shown. Ignored when the picker is
2790   * hidden
2791   */
2792  setFocusedDate(viewDate, resetView = false) {
2793    const {config, picker, active, rangeSideIndex} = this;
2794    const pickLevel = config.pickLevel;
2795    const newViewDate = parseDate(viewDate, config.format, config.locale);
2796    if (newViewDate === undefined) {
2797      return;
2798    }
2799
2800    picker.changeFocus(regularizeDate(newViewDate, pickLevel, rangeSideIndex));
2801    if (active && resetView) {
2802      picker.changeView(pickLevel);
2803    }
2804    picker.render();
2805  }
2806
2807  /**
2808   * Refresh the picker element and the associated input field
2809   * @param {String} [target] - target item when refreshing one item only
2810   * 'picker' or 'input'
2811   * @param {Boolean} [forceRender] - whether to re-render the picker element
2812   * regardless of its state instead of optimized refresh
2813   */
2814  refresh(target = undefined, forceRender = false) {
2815    if (target && typeof target !== 'string') {
2816      forceRender = target;
2817      target = undefined;
2818    }
2819
2820    let mode;
2821    if (target === 'picker') {
2822      mode = 2;
2823    } else if (target === 'input') {
2824      mode = 1;
2825    } else {
2826      mode = 3;
2827    }
2828    refreshUI(this, mode, !forceRender);
2829  }
2830
2831  /**
2832   * Enter edit mode
2833   * Not available on inline picker or when the picker element is hidden
2834   */
2835  enterEditMode() {
2836    const inputField = this.inputField;
2837    if (!inputField || inputField.readOnly || !this.picker.active || this.editMode) {
2838      return;
2839    }
2840    this.editMode = true;
2841    inputField.classList.add('in-edit');
2842  }
2843
2844  /**
2845   * Exit from edit mode
2846   * Not available on inline picker
2847   * @param  {Object} [options] - function options
2848   * - update: {boolean} - whether to call update() after exiting
2849   *     If false, input field is revert to the existing selection
2850   *     default: false
2851   */
2852  exitEditMode(options = undefined) {
2853    if (!this.inputField || !this.editMode) {
2854      return;
2855    }
2856    const opts = Object.assign({update: false}, options);
2857    delete this.editMode;
2858    this.inputField.classList.remove('in-edit');
2859    if (opts.update) {
2860      this.update(opts);
2861    }
2862  }
2863}
2864
2865/**
2866 * German translation for bootstrap-datepicker
2867 * Sam Zurcher <[email protected]>
2868 */
2869var de = {
2870  de: {
2871    days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
2872    daysShort: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
2873    daysMin: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
2874    months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
2875    monthsShort: ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"],
2876    today: "Heute",
2877    monthsTitle: "Monate",
2878    clear: "Löschen",
2879    weekStart: 1,
2880    format: "dd.mm.yyyy"
2881  }
2882};
2883
2884//
2885Object.assign(Datepicker.locales, de);
2886function init() {
2887  var datepickers = document.querySelectorAll('.vjsdatepicker');
2888  datepickers.forEach(function (node) {
2889    var minDate = node.getAttribute('mindate');
2890    var maxDate = node.getAttribute('maxdate');
2891    new Datepicker(node, {
2892      minDate: minDate,
2893      maxDate: maxDate,
2894      maxNumberOfDates: 99,
2895      buttonClass: 'btn',
2896      language: 'de'
2897    });
2898  });
2899}
2900var _datepicker = {};
2901var Init = init;
2902
2903export { Init, _datepicker as default };
2904//# sourceMappingURL=_datepicker-7c518a59.js.map

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.