diff --git a/src/components/CvDatePicker/CvDatePicker.stories.js b/src/components/CvDatePicker/CvDatePicker.stories.js
index 2b5a4fa25..e09ff7c6d 100644
--- a/src/components/CvDatePicker/CvDatePicker.stories.js
+++ b/src/components/CvDatePicker/CvDatePicker.stories.js
@@ -9,7 +9,7 @@ import { ref } from 'vue';
const initArgs = {
dateLabel: 'Date label',
- invalidText: 'Invalid value',
+ invalidMessage: '',
};
export default {
@@ -25,7 +25,7 @@ export default {
},
argTypes: {
dateLabel: { type: String, description: 'Date picker label' },
- invalidText: {
+ invalidMessage: {
type: String,
description: 'Date picker text on invalid value',
},
@@ -58,7 +58,7 @@ Default.parameters = storyParametersObject(
);
const now = new Date();
-const modelValue = ref(now.toLocaleDateString());
+const modelValue = ref('');
const templateVModel = `
+
+
+ {{ invalidMessage }}
+
+
+
+
@@ -115,6 +125,11 @@ import { Calendar16 } from '@carbon/icons-vue';
import CvWrapper from '../CvWrapper/CvWrapper';
import { props as propsCvTheme, useIsLight } from '../../use/cvTheme';
import flatpickr from 'flatpickr';
+import l10n from 'flatpickr/dist/l10n/index';
+// import carbonFlatpickrAppendToPlugin from './plugins/appendToPlugin';
+import carbonFlatpickrFixEventsPlugin from './plugins/fixEventsPlugin';
+import carbonFlatpickrRangePlugin from './plugins/rangePlugin';
+import carbonFlatpickrMonthSelectPlugin from './plugins/monthSelectPlugin';
const dateWrapper = ref(null);
const date = ref(null);
@@ -130,7 +145,8 @@ const props = defineProps({
dateLabel: { type: String, default: undefined },
dateEndLabel: { type: String, default: 'End date' },
invalid: { type: Boolean, default: false },
- invalidText: { type: String },
+ disabled: { type: Boolean, default: false },
+ invalidMessage: { type: String },
pattern: { type: String, default: '\\d{1,2}/\\d{1,2}/\\d{4}' },
placeholder: { type: String, default: 'mm/dd/yyyy' },
calOptions: {
@@ -147,6 +163,7 @@ const props = defineProps({
default: 'simple',
validator: val => ['short', 'simple', 'single', 'range'].includes(val),
},
+ value: [String, Object, Array, Date],
...propsCvId,
...propsCvTheme,
});
@@ -165,7 +182,7 @@ const getKind = computed({
const getDateLabel = computed({
get() {
- if (props.getKind === 'range' && !props.dateLabel) {
+ if (props.kind === 'range' && !props.dateLabel) {
return 'Start date';
}
@@ -179,47 +196,133 @@ const getDateLabel = computed({
const getStartDate = computed({
get() {
- if (props.modelValue) {
- return props.modelValue?.startDate || props.modelValue;
- }
+ return (
+ props.modelValue?.startDate ||
+ props.modelValue ||
+ props.value?.startDate ||
+ props.value
+ );
},
});
const getEndDate = computed({
get() {
- if (props.modelValue) {
- return props.modelValue?.endDate || props.modelValue;
- }
+ return props.modelValue?.endDate || props.value?.endDate;
},
});
+const isRange = computed(() => {
+ return props.kind === 'range';
+});
+
+const isSingle = computed(() => {
+ return props.kind === 'single';
+});
+
+const isInvalid = computed(() => {
+ return !!props.invalidMessage;
+});
+
const getFlatpickrOptions = () => {
const options = { ...props.calOptions };
+ options.plugins = [
+ props.kind === 'range'
+ ? carbonFlatpickrRangePlugin({
+ input: todate.value,
+ })
+ : () => {},
+ carbonFlatpickrMonthSelectPlugin({
+ selectorFlatpickrMonthYearContainer: '.flatpickr-current-month',
+ selectorFlatpickrYearContainer: '.numInputWrapper',
+ selectorFlatpickrCurrentMonth: '.cur-month',
+ classFlatpickrCurrentMonth: 'cur-month',
+ }),
+ carbonFlatpickrFixEventsPlugin({
+ inputFrom: date.value,
+ inputTo: todate.value,
+ }),
+ ];
+
+ options.nextArrow = `
+
+ `;
+
+ options.prevArrow = `
+
+ `;
+
options.mode = props.kind;
+ // add events update based on parameters
options.onChange = handleDatePick;
+ // options.onOpen = onOpen;
+ options.onReady = onCalReady;
return options;
};
+const onCalReady = (selectedDates, dateStr, instance) => {
+ const calendarContainer = instance.calendarContainer;
+ const options = {
+ classCalendarContainer: `${carbonPrefix}--date-picker__calendar`,
+ classMonth: `${carbonPrefix}--date-picker__month`,
+ classWeekdays: `${carbonPrefix}--date-picker__weekdays`,
+ classDays: `${carbonPrefix}--date-picker__days`,
+ classWeekday: `${carbonPrefix}--date-picker__weekday`,
+ classDay: `${carbonPrefix}--date-picker__day`,
+ classFocused: `${carbonPrefix}--focused`,
+ classVisuallyHidden: `${carbonPrefix}--visually-hidden`,
+ };
+
+ if (calendarContainer) {
+ calendarContainer.classList.add(options.classCalendarContainer);
+ calendarContainer
+ .querySelector('.flatpickr-month')
+ .classList.add(options.classMonth);
+ calendarContainer
+ .querySelector('.flatpickr-weekdays')
+ .classList.add(options.classWeekdays);
+ calendarContainer
+ .querySelector('.flatpickr-days')
+ .classList.add(options.classDays);
+ for (const item of calendarContainer.querySelectorAll(
+ '.flatpickr-weekday'
+ )) {
+ const currentItem = item;
+ currentItem.innerHTML = currentItem.innerHTML.replace(/\s+/g, '');
+ currentItem.classList.add(options.classWeekday);
+ }
+ for (const item of calendarContainer.querySelectorAll('.flatpickr-day')) {
+ item.classList.add(options.classDay);
+ if (item.classList.contains('today') && selectedDates.length > 0) {
+ item.classList.add('no-border');
+ } else if (
+ item.classList.contains('today') &&
+ selectedDates.length === 0
+ ) {
+ item.classList.remove('no-border');
+ }
+ }
+ }
+};
+
const initFlatpickr = () => {
return flatpickr(date.value, getFlatpickrOptions());
};
let dateToString = val => {
- if (typeof val === 'number') {
- return this.cal.formatDate(val, this.calOptions.dateFormat);
- } else {
- return val || '';
- }
+ return calendar.formatDate(val, props.calOptions.dateFormat);
};
const handleDatePick = (selectedDates, dateStr, instance) => {
if (selectedDates.length === 1) {
- const temp = calendar.formatDate(
- selectedDates[0],
- props.calOptions.dateFormat
- );
+ const temp = dateToString(selectedDates[0]);
nextTick(() => {
date.value.value = temp;
@@ -227,14 +330,8 @@ const handleDatePick = (selectedDates, dateStr, instance) => {
emit('update:modelValue', temp);
} else {
- const startDate = calendar.formatDate(
- selectedDates[0],
- props.calOptions.dateFormat
- );
- const endDate = calendar.formatDate(
- selectedDates[1],
- props.calOptions.dateFormat
- );
+ const startDate = dateToString(selectedDates[0]);
+ const endDate = dateToString(selectedDates[1]);
nextTick(() => {
date.value.value = startDate;
@@ -261,4 +358,9 @@ onMounted(() => {
});
-
+
diff --git a/src/components/CvDatePicker/plugins/appendToPlugin.js b/src/components/CvDatePicker/plugins/appendToPlugin.js
new file mode 100644
index 000000000..4257a15e1
--- /dev/null
+++ b/src/components/CvDatePicker/plugins/appendToPlugin.js
@@ -0,0 +1,64 @@
+/**
+ * @license
+ *
+ * Copyright IBM Corp. 2019
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/*
+ * COPIED FROM carbon-components-react
+ */
+
+/**
+ * @param {object} config Plugin configuration.
+ * @returns {Plugin} A Flatpickr plugin to put adjust the position of calendar dropdown.
+ */
+export default config => fp => {
+ /**
+ * Adjusts the floating meun position after Flatpicker sets it.
+ */
+ const handlePreCalendarPosition = () => {
+ Promise.resolve().then(() => {
+ const {
+ calendarContainer,
+ config: fpConfig,
+ _positionElement: positionElement,
+ } = fp;
+ const { appendTo } = fpConfig;
+ const { left: containerLeft, top: containerTop } =
+ appendTo.getBoundingClientRect();
+ const { left: refLeft, bottom: refBottom } =
+ positionElement.getBoundingClientRect();
+ if (
+ (appendTo !== appendTo.ownerDocument.body ||
+ containerLeft !== 0 ||
+ containerTop !== 0) &&
+ appendTo.ownerDocument.defaultView
+ .getComputedStyle(appendTo)
+ .getPropertyValue('position') === 'static'
+ ) {
+ throw new Error(
+ 'Floating menu container must not have `position:static`.'
+ );
+ }
+ // `2` for negative mergin on calendar dropdown
+ calendarContainer.style.top = `${refBottom - containerTop + 2}px`;
+ calendarContainer.style.left = `${refLeft - containerLeft}px`;
+ });
+ };
+
+ /**
+ * Registers this Flatpickr plugin.
+ */
+ const register = () => {
+ fp.loadedPlugins.push('carbonFlatpickrAppendToPlugin');
+ };
+
+ return {
+ appendTo: config.appendTo,
+ onReady: register,
+ onPreCalendarPosition: handlePreCalendarPosition,
+ };
+};
diff --git a/src/components/CvDatePicker/plugins/fixEventsPlugin.js b/src/components/CvDatePicker/plugins/fixEventsPlugin.js
new file mode 100644
index 000000000..21523e707
--- /dev/null
+++ b/src/components/CvDatePicker/plugins/fixEventsPlugin.js
@@ -0,0 +1,79 @@
+/**
+ * Copyright IBM Corp. 2016, 2018
+ *
+ * This source code is licensed under the Apache-2.0 license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+/*
+ * COPIED FROM carbon-components-react - amended to remove keyboard code dependency
+ */
+
+/**
+ * @param {object} config Plugin configuration.
+ * @returns {Plugin} A Flatpickr plugin to fix Flatpickr's behavior of certain events.
+ */
+export default config => fp => {
+ /**
+ * Handles `keydown` event.
+ */
+ const handleKeydown = event => {
+ const { inputFrom, inputTo } = config;
+ const { target } = event;
+ if (inputFrom === target || inputTo === target) {
+ if (event.key === 'Enter') {
+ // Makes sure the hitting enter key picks up pending values of both ``
+ // Workaround for: https://github.com/flatpickr/flatpickr/issues/1942
+ fp.setDate(
+ [inputFrom.value, inputTo && inputTo.value],
+ true,
+ fp.config.dateFormat
+ );
+ event.stopPropagation();
+ } else if (event.key === 'Left' || event.key === 'Right') {
+ // Prevents Flatpickr code from canceling the event if left/right arrow keys are hit on ``,
+ // so user can move the keyboard cursor for editing dates
+ // Workaround for: https://github.com/flatpickr/flatpickr/issues/1943
+ event.stopPropagation();
+ } else if (event.key === 'Down') {
+ event.preventDefault();
+ fp.open();
+ }
+ }
+ };
+
+ /**
+ * Releases event listeners used in this Flatpickr plugin.
+ */
+ const release = () => {
+ const { inputFrom, inputTo } = config;
+ if (inputTo) {
+ inputTo.removeEventListener('keydown', handleKeydown, true);
+ }
+ inputFrom.removeEventListener('keydown', handleKeydown, true);
+ };
+
+ /**
+ * Sets up event listeners used for this Flatpickr plugin.
+ */
+ const init = () => {
+ release();
+ const { inputFrom, inputTo } = config;
+ inputFrom.addEventListener('keydown', handleKeydown, true);
+ if (inputTo) {
+ inputTo.addEventListener('keydown', handleKeydown, true);
+ }
+ };
+
+ /**
+ * Registers this Flatpickr plugin.
+ */
+ const register = () => {
+ fp.loadedPlugins.push('carbonFlatpickrFixEventsPlugin');
+ };
+
+ return {
+ onReady: [register, init],
+ onDestroy: [release],
+ };
+};
diff --git a/src/components/CvDatePicker/plugins/monthSelectPlugin.js b/src/components/CvDatePicker/plugins/monthSelectPlugin.js
new file mode 100644
index 000000000..7e4af465a
--- /dev/null
+++ b/src/components/CvDatePicker/plugins/monthSelectPlugin.js
@@ -0,0 +1,86 @@
+/*
+ * COPIED FROM carbon-components-react
+ */
+
+/**
+ * @param {number} monthNumber The month number.
+ * @param {boolean} shorthand `true` to use shorthand month.
+ * @param {Locale} locale The Flatpickr locale data.
+ * @returns {string} The month string.
+ */
+const monthToStr = (monthNumber, shorthand, locale) =>
+ locale.months[shorthand ? 'shorthand' : 'longhand'][monthNumber];
+
+/**
+ * @param {object} config Plugin configuration.
+ * @param {boolean} [config.shorthand] `true` to use shorthand month.
+ * @param {string} config.selectorFlatpickrMonthYearContainer The CSS selector for the container of month/year selection UI.
+ * @param {string} config.selectorFlatpickrYearContainer The CSS selector for the container of year selection UI.
+ * @param {string} config.selectorFlatpickrCurrentMonth The CSS selector for the text-based month selection UI.
+ * @param {string} config.classFlatpickrCurrentMonth The CSS class for the text-based month selection UI.
+ * @returns {Plugin} A Flatpickr plugin to use text instead of `