|
import { addMeltEventListener, makeElement, createElHelpers, effect, executeCallbacks, generateIds, isBrowser, isHTMLElement, isValidIndex, kbd, omit, overridable, styleToString, toWritableStores, withGet, } from '../../internal/helpers/index.js'; |
|
import { createFormatter, createMonths, dateStore, getAnnouncer, getDefaultDate, getSelectableCells, isAfter, isBefore, isCalendarCell, parseStringToDateValue, setPlaceholderToNodeValue, toDate, } from '../../internal/helpers/date/index.js'; |
|
import { getLocalTimeZone, isSameDay, isSameMonth, isToday, } from '@internationalized/date'; |
|
import { tick } from 'svelte'; |
|
import { derived, writable } from 'svelte/store'; |
|
export const defaults = { |
|
isDateDisabled: undefined, |
|
isDateUnavailable: undefined, |
|
value: undefined, |
|
preventDeselect: false, |
|
numberOfMonths: 1, |
|
pagedNavigation: false, |
|
weekStartsOn: 0, |
|
fixedWeeks: false, |
|
calendarLabel: 'Event Date', |
|
locale: 'en', |
|
minValue: undefined, |
|
maxValue: undefined, |
|
disabled: false, |
|
readonly: false, |
|
weekdayFormat: 'narrow', |
|
}; |
|
const { name } = createElHelpers('calendar'); |
|
export const calendarIdParts = ['calendar', 'accessibleHeading']; |
|
export function createCalendar(props) { |
|
const withDefaults = { ...defaults, ...props }; |
|
const options = toWritableStores({ |
|
...omit(withDefaults, 'value', 'placeholder', 'multiple', 'ids'), |
|
multiple: withDefaults.multiple ?? false, |
|
}); |
|
const { preventDeselect, numberOfMonths, pagedNavigation, weekStartsOn, fixedWeeks, calendarLabel, locale, minValue, maxValue, multiple, isDateUnavailable, disabled, readonly, weekdayFormat, } = options; |
|
const ids = toWritableStores({ ...generateIds(calendarIdParts), ...withDefaults.ids }); |
|
const defaultDate = getDefaultDate({ |
|
defaultPlaceholder: withDefaults.defaultPlaceholder, |
|
defaultValue: withDefaults.defaultValue, |
|
}); |
|
const formatter = createFormatter(withDefaults.locale); |
|
const valueWritable = withDefaults.value ?? writable(withDefaults.defaultValue); |
|
const value = overridable(valueWritable, withDefaults.onValueChange); |
|
const placeholderWritable = withDefaults.placeholder ?? writable(withDefaults.defaultPlaceholder ?? defaultDate); |
|
const placeholder = dateStore(overridable(placeholderWritable, withDefaults.onPlaceholderChange), withDefaults.defaultPlaceholder ?? defaultDate); |
|
|
|
|
|
|
|
const months = withGet(writable(createMonths({ |
|
dateObj: placeholder.get(), |
|
weekStartsOn: withDefaults.weekStartsOn, |
|
locale: withDefaults.locale, |
|
fixedWeeks: withDefaults.fixedWeeks, |
|
numberOfMonths: withDefaults.numberOfMonths, |
|
}))); |
|
|
|
|
|
|
|
|
|
|
|
const visibleMonths = withGet.derived([months], ([$months]) => { |
|
return $months.map((month) => { |
|
return month.value; |
|
}); |
|
}); |
|
const isOutsideVisibleMonths = derived([visibleMonths], ([$visibleMonths]) => { |
|
return (date) => { |
|
return !$visibleMonths.some((month) => isSameMonth(date, month)); |
|
}; |
|
}); |
|
const isNextButtonDisabled = withGet.derived([months, maxValue, disabled], ([$months, $maxValue, $disabled]) => { |
|
if (!$maxValue || !$months.length) |
|
return false; |
|
if ($disabled) |
|
return true; |
|
const lastMonthInView = $months[$months.length - 1].value; |
|
const firstMonthOfNextPage = lastMonthInView.add({ months: 1 }).set({ day: 1 }); |
|
return isAfter(firstMonthOfNextPage, $maxValue); |
|
}); |
|
const isPrevButtonDisabled = withGet.derived([months, minValue, disabled], ([$months, $minValue, $disabled]) => { |
|
if (!$minValue || !$months.length) |
|
return false; |
|
if ($disabled) |
|
return true; |
|
const firstMonthInView = $months[0].value; |
|
const lastMonthOfPrevPage = firstMonthInView.subtract({ months: 1 }).set({ day: 35 }); |
|
return isBefore(lastMonthOfPrevPage, $minValue); |
|
}); |
|
|
|
|
|
|
|
|
|
const isDateDisabled = withGet.derived([options.isDateDisabled, minValue, maxValue, disabled], ([$isDateDisabled, $minValue, $maxValue, $disabled]) => { |
|
return (date) => { |
|
if ($isDateDisabled?.(date) || $disabled) |
|
return true; |
|
if ($minValue && isBefore(date, $minValue)) |
|
return true; |
|
if ($maxValue && isBefore($maxValue, date)) |
|
return true; |
|
return false; |
|
}; |
|
}); |
|
const isDateSelected = derived([value], ([$value]) => { |
|
return (date) => { |
|
if (Array.isArray($value)) { |
|
return $value.some((d) => isSameDay(d, date)); |
|
} |
|
else if (!$value) { |
|
return false; |
|
} |
|
else { |
|
return isSameDay($value, date); |
|
} |
|
}; |
|
}); |
|
|
|
|
|
|
|
const isInvalid = derived([value, isDateDisabled, options.isDateUnavailable], ([$value, $isDateDisabled, $isDateUnavailable]) => { |
|
if (Array.isArray($value)) { |
|
if (!$value.length) |
|
return false; |
|
for (const date of $value) { |
|
if ($isDateDisabled?.(date)) |
|
return true; |
|
if ($isDateUnavailable?.(date)) |
|
return true; |
|
} |
|
} |
|
else { |
|
if (!$value) |
|
return false; |
|
if ($isDateDisabled?.($value)) |
|
return true; |
|
if ($isDateUnavailable?.($value)) |
|
return true; |
|
} |
|
return false; |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let announcer = getAnnouncer(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const headingValue = withGet.derived([months, locale], ([$months, $locale]) => { |
|
if (!$months.length) |
|
return ''; |
|
if ($locale !== formatter.getLocale()) { |
|
formatter.setLocale($locale); |
|
} |
|
if ($months.length === 1) { |
|
const month = $months[0].value; |
|
return `${formatter.fullMonthAndYear(toDate(month))}`; |
|
} |
|
const startMonth = toDate($months[0].value); |
|
const endMonth = toDate($months[$months.length - 1].value); |
|
const startMonthName = formatter.fullMonth(startMonth); |
|
const endMonthName = formatter.fullMonth(endMonth); |
|
const startMonthYear = formatter.fullYear(startMonth); |
|
const endMonthYear = formatter.fullYear(endMonth); |
|
const content = startMonthYear === endMonthYear |
|
? `${startMonthName} - ${endMonthName} ${endMonthYear}` |
|
: `${startMonthName} ${startMonthYear} - ${endMonthName} ${endMonthYear}`; |
|
return content; |
|
}); |
|
|
|
|
|
|
|
|
|
const fullCalendarLabel = withGet.derived([headingValue, calendarLabel], ([$headingValue, $calendarLabel]) => { |
|
return `${$calendarLabel}, ${$headingValue}`; |
|
}); |
|
|
|
|
|
|
|
|
|
const calendar = makeElement(name(), { |
|
stores: [fullCalendarLabel, isInvalid, disabled, readonly, ids.calendar], |
|
returned: ([$fullCalendarLabel, $isInvalid, $disabled, $readonly, $calendarId]) => { |
|
return { |
|
id: $calendarId, |
|
role: 'application', |
|
'aria-label': $fullCalendarLabel, |
|
'data-invalid': $isInvalid ? '' : undefined, |
|
'data-disabled': $disabled ? '' : undefined, |
|
'data-readonly': $readonly ? '' : undefined, |
|
}; |
|
}, |
|
action: (node) => { |
|
|
|
|
|
|
|
|
|
|
|
createAccessibleHeading(node, fullCalendarLabel.get()); |
|
announcer = getAnnouncer(); |
|
const unsubKb = addMeltEventListener(node, 'keydown', handleCalendarKeydown); |
|
return { |
|
destroy() { |
|
unsubKb(); |
|
}, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const heading = makeElement(name('heading'), { |
|
stores: [disabled], |
|
returned: ([$disabled]) => { |
|
return { |
|
'aria-hidden': true, |
|
'data-disabled': $disabled ? '' : undefined, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const grid = makeElement(name('grid'), { |
|
stores: [readonly, disabled], |
|
returned: ([$readonly, $disabled]) => { |
|
return { |
|
tabindex: -1, |
|
role: 'grid', |
|
'aria-readonly': $readonly ? 'true' : undefined, |
|
'aria-disabled': $disabled ? 'true' : undefined, |
|
'data-readonly': $readonly ? '' : undefined, |
|
'data-disabled': $disabled ? '' : undefined, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
const prevButton = makeElement(name('prevButton'), { |
|
stores: [isPrevButtonDisabled], |
|
returned: ([$isPrevButtonDisabled]) => { |
|
const disabled = $isPrevButtonDisabled; |
|
return { |
|
role: 'button', |
|
type: 'button', |
|
'aria-label': 'Previous', |
|
'aria-disabled': disabled ? 'true' : undefined, |
|
'data-disabled': disabled ? '' : undefined, |
|
disabled: disabled ? true : undefined, |
|
}; |
|
}, |
|
action: (node) => { |
|
const unsub = executeCallbacks(addMeltEventListener(node, 'click', () => { |
|
if (isPrevButtonDisabled.get()) |
|
return; |
|
prevPage(); |
|
})); |
|
return { |
|
destroy: unsub, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
const nextButton = makeElement(name('nextButton'), { |
|
stores: [isNextButtonDisabled], |
|
returned: ([$isNextButtonDisabled]) => { |
|
const disabled = $isNextButtonDisabled; |
|
return { |
|
role: 'button', |
|
type: 'button', |
|
'aria-label': 'Next', |
|
'aria-disabled': disabled ? 'true' : undefined, |
|
'data-disabled': disabled ? '' : undefined, |
|
disabled: disabled ? true : undefined, |
|
}; |
|
}, |
|
action: (node) => { |
|
const unsub = executeCallbacks(addMeltEventListener(node, 'click', () => { |
|
if (isNextButtonDisabled.get()) |
|
return; |
|
nextPage(); |
|
})); |
|
return { |
|
destroy: unsub, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
const cell = makeElement(name('cell'), { |
|
stores: [ |
|
isDateSelected, |
|
isDateDisabled, |
|
isDateUnavailable, |
|
isOutsideVisibleMonths, |
|
placeholder, |
|
], |
|
returned: ([$isDateSelected, $isDateDisabled, $isDateUnavailable, $isOutsideVisibleMonths, $placeholder,]) => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (cellValue, monthValue) => { |
|
const cellDate = toDate(cellValue); |
|
const isDisabled = $isDateDisabled?.(cellValue); |
|
const isUnavailable = $isDateUnavailable?.(cellValue); |
|
const isDateToday = isToday(cellValue, getLocalTimeZone()); |
|
const isOutsideMonth = !isSameMonth(cellValue, monthValue); |
|
const isOutsideVisibleMonths = $isOutsideVisibleMonths(cellValue); |
|
const isFocusedDate = isSameDay(cellValue, $placeholder); |
|
const isSelectedDate = $isDateSelected(cellValue); |
|
const labelText = formatter.custom(cellDate, { |
|
weekday: 'long', |
|
month: 'long', |
|
day: 'numeric', |
|
year: 'numeric', |
|
}); |
|
return { |
|
role: 'button', |
|
'aria-label': labelText, |
|
'aria-selected': isSelectedDate ? true : undefined, |
|
'aria-disabled': isOutsideMonth || isDisabled || isUnavailable ? true : undefined, |
|
'data-selected': isSelectedDate ? true : undefined, |
|
'data-value': cellValue.toString(), |
|
'data-disabled': isDisabled || isOutsideMonth ? '' : undefined, |
|
'data-unavailable': isUnavailable ? '' : undefined, |
|
'data-today': isDateToday ? '' : undefined, |
|
'data-outside-month': isOutsideMonth ? '' : undefined, |
|
'data-outside-visible-months': isOutsideVisibleMonths ? '' : undefined, |
|
'data-focused': isFocusedDate ? '' : undefined, |
|
tabindex: isFocusedDate ? 0 : isOutsideMonth || isDisabled ? undefined : -1, |
|
}; |
|
}; |
|
}, |
|
action: (node) => { |
|
const getElArgs = () => { |
|
const value = node.getAttribute('data-value'); |
|
const label = node.getAttribute('data-label'); |
|
const disabled = node.hasAttribute('data-disabled'); |
|
return { |
|
value, |
|
label: label ?? node.textContent ?? null, |
|
disabled: disabled ? true : false, |
|
}; |
|
}; |
|
const unsub = executeCallbacks(addMeltEventListener(node, 'click', () => { |
|
const args = getElArgs(); |
|
if (args.disabled) |
|
return; |
|
if (!args.value) |
|
return; |
|
handleCellClick(parseStringToDateValue(args.value, placeholder.get())); |
|
})); |
|
return { |
|
destroy: unsub, |
|
}; |
|
}, |
|
}); |
|
|
|
|
|
|
|
|
|
effect([locale], ([$locale]) => { |
|
if (formatter.getLocale() === $locale) |
|
return; |
|
formatter.setLocale($locale); |
|
}); |
|
|
|
|
|
|
|
|
|
effect([placeholder], ([$placeholder]) => { |
|
if (!isBrowser || !$placeholder) |
|
return; |
|
const $visibleMonths = visibleMonths.get(); |
|
|
|
|
|
|
|
|
|
if ($visibleMonths.some((month) => isSameMonth(month, $placeholder))) { |
|
return; |
|
} |
|
const $weekStartsOn = weekStartsOn.get(); |
|
const $locale = locale.get(); |
|
const $fixedWeeks = fixedWeeks.get(); |
|
const $numberOfMonths = numberOfMonths.get(); |
|
const defaultMonthProps = { |
|
weekStartsOn: $weekStartsOn, |
|
locale: $locale, |
|
fixedWeeks: $fixedWeeks, |
|
numberOfMonths: $numberOfMonths, |
|
}; |
|
months.set(createMonths({ |
|
...defaultMonthProps, |
|
dateObj: $placeholder, |
|
})); |
|
}); |
|
|
|
|
|
|
|
|
|
effect([weekStartsOn, locale, fixedWeeks, numberOfMonths], ([$weekStartsOn, $locale, $fixedWeeks, $numberOfMonths]) => { |
|
const $placeholder = placeholder.get(); |
|
if (!isBrowser || !$placeholder) |
|
return; |
|
const defaultMonthProps = { |
|
weekStartsOn: $weekStartsOn, |
|
locale: $locale, |
|
fixedWeeks: $fixedWeeks, |
|
numberOfMonths: $numberOfMonths, |
|
}; |
|
months.set(createMonths({ |
|
...defaultMonthProps, |
|
dateObj: $placeholder, |
|
})); |
|
}); |
|
|
|
|
|
|
|
|
|
effect([fullCalendarLabel], ([$fullCalendarLabel]) => { |
|
if (!isBrowser) |
|
return; |
|
const node = document.getElementById(ids.accessibleHeading.get()); |
|
if (!isHTMLElement(node)) |
|
return; |
|
node.textContent = $fullCalendarLabel; |
|
}); |
|
|
|
|
|
|
|
effect([value], ([$value]) => { |
|
if (Array.isArray($value) && $value.length) { |
|
const lastValue = $value[$value.length - 1]; |
|
if (lastValue && placeholder.get() !== lastValue) { |
|
placeholder.set(lastValue); |
|
} |
|
} |
|
else if (!Array.isArray($value) && $value && placeholder.get() !== $value) { |
|
placeholder.set($value); |
|
} |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const weekdays = derived([months, weekdayFormat, locale], ([$months, $weekdayFormat, _]) => { |
|
if (!$months.length) |
|
return []; |
|
return $months[0].weeks[0].map((date) => { |
|
return formatter.dayOfWeek(toDate(date), $weekdayFormat); |
|
}); |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createAccessibleHeading(node, label) { |
|
if (!isBrowser) |
|
return; |
|
const div = document.createElement('div'); |
|
div.style.cssText = styleToString({ |
|
border: '0px', |
|
clip: 'rect(0px, 0px, 0px, 0px)', |
|
'clip-path': 'inset(50%)', |
|
height: '1px', |
|
margin: '-1px', |
|
overflow: 'hidden', |
|
padding: '0px', |
|
position: 'absolute', |
|
'white-space': 'nowrap', |
|
width: '1px', |
|
}); |
|
const h2 = document.createElement('div'); |
|
h2.textContent = label; |
|
h2.id = ids.accessibleHeading.get(); |
|
h2.role = 'heading'; |
|
h2.ariaLevel = '2'; |
|
node.insertBefore(div, node.firstChild); |
|
div.appendChild(h2); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function nextPage() { |
|
const $months = months.get(); |
|
const $numberOfMonths = numberOfMonths.get(); |
|
if (pagedNavigation.get()) { |
|
const firstMonth = $months[0].value; |
|
placeholder.set(firstMonth.add({ months: $numberOfMonths })); |
|
} |
|
else { |
|
const firstMonth = $months[0].value; |
|
const newMonths = createMonths({ |
|
dateObj: firstMonth.add({ months: 1 }), |
|
weekStartsOn: weekStartsOn.get(), |
|
locale: locale.get(), |
|
fixedWeeks: fixedWeeks.get(), |
|
numberOfMonths: $numberOfMonths, |
|
}); |
|
months.set(newMonths); |
|
placeholder.set(newMonths[0].value.set({ day: 1 })); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function prevPage() { |
|
const $months = months.get(); |
|
const $numberOfMonths = numberOfMonths.get(); |
|
if (pagedNavigation.get()) { |
|
const firstMonth = $months[0].value; |
|
placeholder.set(firstMonth.subtract({ months: $numberOfMonths })); |
|
} |
|
else { |
|
const firstMonth = $months[0].value; |
|
const newMonths = createMonths({ |
|
dateObj: firstMonth.subtract({ months: 1 }), |
|
weekStartsOn: weekStartsOn.get(), |
|
locale: locale.get(), |
|
fixedWeeks: fixedWeeks.get(), |
|
numberOfMonths: $numberOfMonths, |
|
}); |
|
months.set(newMonths); |
|
placeholder.set(newMonths[0].value.set({ day: 1 })); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function nextYear() { |
|
placeholder.add({ years: 1 }); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function prevYear() { |
|
placeholder.subtract({ years: 1 }); |
|
} |
|
const ARROW_KEYS = [kbd.ARROW_DOWN, kbd.ARROW_UP, kbd.ARROW_LEFT, kbd.ARROW_RIGHT]; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function setYear(year) { |
|
placeholder.setDate({ year: year }); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function setMonth(month) { |
|
placeholder.setDate({ month: month }); |
|
} |
|
function handleCellClick(date) { |
|
const $readonly = readonly.get(); |
|
if ($readonly) |
|
return; |
|
const $isDateDisabled = isDateDisabled.get(); |
|
const $isUnavailable = options.isDateUnavailable.get(); |
|
if ($isDateDisabled?.(date) || $isUnavailable?.(date)) |
|
return; |
|
value.update((prev) => { |
|
const $multiple = multiple.get(); |
|
if ($multiple) { |
|
return handleMultipleUpdate(prev, date); |
|
} |
|
else { |
|
const next = handleSingleUpdate(prev, date); |
|
if (!next) { |
|
announcer.announce('Selected date is now empty.', 'polite', 5000); |
|
} |
|
else { |
|
announcer.announce(`Selected Date: ${formatter.selectedDate(next, false)}`, 'polite'); |
|
} |
|
return next; |
|
} |
|
}); |
|
} |
|
function handleSingleUpdate(prev, date) { |
|
if (Array.isArray(prev)) |
|
throw new Error('Invalid value for multiple prop.'); |
|
if (!prev) |
|
return date; |
|
const $preventDeselect = preventDeselect.get(); |
|
if (!$preventDeselect && isSameDay(prev, date)) { |
|
placeholder.set(date); |
|
return undefined; |
|
} |
|
return date; |
|
} |
|
function handleMultipleUpdate(prev, date) { |
|
if (!prev) |
|
return [date]; |
|
if (!Array.isArray(prev)) |
|
throw new Error('Invalid value for multiple prop.'); |
|
const index = prev.findIndex((d) => isSameDay(d, date)); |
|
const $preventDeselect = preventDeselect.get(); |
|
if (index === -1) { |
|
return [...prev, date]; |
|
} |
|
else if ($preventDeselect) { |
|
return prev; |
|
} |
|
else { |
|
const next = prev.filter((d) => !isSameDay(d, date)); |
|
if (!next.length) { |
|
placeholder.set(date); |
|
return undefined; |
|
} |
|
return next; |
|
} |
|
} |
|
const SELECT_KEYS = [kbd.ENTER, kbd.SPACE]; |
|
function handleCalendarKeydown(e) { |
|
const currentCell = e.target; |
|
if (!isCalendarCell(currentCell)) |
|
return; |
|
if (!ARROW_KEYS.includes(e.key) && !SELECT_KEYS.includes(e.key)) |
|
return; |
|
e.preventDefault(); |
|
|
|
if (e.key === kbd.ARROW_DOWN) { |
|
shiftFocus(currentCell, 7); |
|
} |
|
if (e.key === kbd.ARROW_UP) { |
|
shiftFocus(currentCell, -7); |
|
} |
|
if (e.key === kbd.ARROW_LEFT) { |
|
shiftFocus(currentCell, -1); |
|
} |
|
if (e.key === kbd.ARROW_RIGHT) { |
|
shiftFocus(currentCell, 1); |
|
} |
|
if (e.key === kbd.SPACE || e.key === kbd.ENTER) { |
|
const cellValue = currentCell.getAttribute('data-value'); |
|
if (!cellValue) |
|
return; |
|
handleCellClick(parseStringToDateValue(cellValue, placeholder.get())); |
|
} |
|
} |
|
function shiftFocus(node, add) { |
|
const candidateCells = getSelectableCells(ids.calendar.get()); |
|
if (!candidateCells.length) |
|
return; |
|
const index = candidateCells.indexOf(node); |
|
const nextIndex = index + add; |
|
|
|
|
|
|
|
|
|
if (isValidIndex(nextIndex, candidateCells)) { |
|
const nextCell = candidateCells[nextIndex]; |
|
setPlaceholderToNodeValue(nextCell, placeholder); |
|
return nextCell.focus(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (nextIndex < 0) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isPrevButtonDisabled.get()) |
|
return; |
|
const $months = months.get(); |
|
const firstMonth = $months[0].value; |
|
const $numberOfMonths = numberOfMonths.get(); |
|
placeholder.set(firstMonth.subtract({ months: $numberOfMonths })); |
|
|
|
|
|
tick().then(() => { |
|
const newCandidateCells = getSelectableCells(ids.calendar.get()); |
|
if (!newCandidateCells.length) { |
|
return; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
const newIndex = newCandidateCells.length - Math.abs(nextIndex); |
|
if (isValidIndex(newIndex, newCandidateCells)) { |
|
const newCell = newCandidateCells[newIndex]; |
|
setPlaceholderToNodeValue(newCell, placeholder); |
|
return newCell.focus(); |
|
} |
|
}); |
|
} |
|
if (nextIndex >= candidateCells.length) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (isNextButtonDisabled.get()) |
|
return; |
|
const $months = months.get(); |
|
const firstMonth = $months[0].value; |
|
const $numberOfMonths = numberOfMonths.get(); |
|
placeholder.set(firstMonth.add({ months: $numberOfMonths })); |
|
tick().then(() => { |
|
const newCandidateCells = getSelectableCells(ids.calendar.get()); |
|
if (!newCandidateCells.length) { |
|
return; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
const newIndex = nextIndex - candidateCells.length; |
|
if (isValidIndex(newIndex, newCandidateCells)) { |
|
const nextCell = newCandidateCells[newIndex]; |
|
return nextCell.focus(); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const _isDateDisabled = derived([isDateDisabled, placeholder, minValue, maxValue, disabled], ([$isDateDisabled, $placeholder, $minValue, $maxValue, $disabled]) => { |
|
return (date) => { |
|
if ($isDateDisabled?.(date) || $disabled) |
|
return true; |
|
if ($minValue && isBefore(date, $minValue)) |
|
return true; |
|
if ($maxValue && isAfter(date, $maxValue)) |
|
return true; |
|
if (!isSameMonth(date, $placeholder)) |
|
return true; |
|
return false; |
|
}; |
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const _isDateUnavailable = derived(isDateUnavailable, ($isDateUnavailable) => { |
|
return (date) => $isDateUnavailable?.(date); |
|
}); |
|
return { |
|
elements: { |
|
calendar, |
|
heading, |
|
grid, |
|
cell, |
|
nextButton, |
|
prevButton, |
|
}, |
|
states: { |
|
placeholder: placeholder.toWritable(), |
|
months, |
|
value, |
|
weekdays, |
|
headingValue, |
|
}, |
|
helpers: { |
|
nextPage, |
|
prevPage, |
|
nextYear, |
|
prevYear, |
|
setYear, |
|
setMonth, |
|
isDateDisabled: _isDateDisabled, |
|
isDateSelected, |
|
isDateUnavailable: _isDateUnavailable, |
|
}, |
|
options, |
|
ids, |
|
}; |
|
} |
|
|