@quasar/quasar-ui-qcalendar
Version:
QCalendar - Day/Month/Week Calendars, Popups, Date Pickers, Schedules, Agendas, Planners and Tasks for your Vue Apps
1,289 lines (1,161 loc) • 36.8 kB
text/typescript
import {
h,
computed,
defineComponent,
getCurrentInstance,
onBeforeUpdate,
onMounted,
// nextTick,
reactive,
ref,
Transition,
watch,
withDirectives,
CSSProperties,
SetupContext,
VNode,
} from 'vue'
// Utility
import { getDayIdentifier, parsed, parseTimestamp, type Timestamp, today } from '../utils/Timestamp'
import { convertToUnit, minCharWidth } from '../utils/helpers'
import useMouse, { getRawMouseEvents } from '../composables/useMouse'
import useCalendar from '../composables/useCalendar'
import useCommon, { useCommonProps } from '../composables/useCommon'
import useTask, { useTaskProps, type Task } from '../composables/useTask'
import useTimes, { useTimesProps } from '../composables/useTimes'
import useRenderValues from '../composables/useRenderValues'
import useMove, { useMoveEmits } from '../composables/useMove'
import useEmitListeners from '../composables/useEmitListeners'
import useButton from '../composables/useButton'
import useFocusHelper from '../composables/useFocusHelper'
import useCheckChange, { useCheckChangeEmits } from '../composables/useCheckChange'
import useEvents from '../composables/useEvents'
import useKeyboard, { useNavigationProps } from '../composables/useKeyboard'
import { /*useCellWidth,*/ useCellWidthProps } from '../composables/useCellWidth'
// Directives
import ResizeObserver from '../directives/ResizeObserver'
interface Size {
width: number
height: number
}
const { renderButton } = useButton()
export default defineComponent({
name: 'QCalendarTask',
directives: { ResizeObserver },
props: {
...useTimesProps,
...useNavigationProps,
...useCellWidthProps,
...useCommonProps,
...useTaskProps, // last for any overrides
},
emits: [
'update:model-value',
'update:model-tasks',
'update:model-title',
'update:model-footer',
'task-expanded',
...useCheckChangeEmits,
...useMoveEmits,
...getRawMouseEvents('-date'),
...getRawMouseEvents('-day'),
...getRawMouseEvents('-head-day'),
],
setup(props, { slots, emit, expose }: SetupContext) {
const scrollArea = ref(null),
pane = ref(null),
direction = ref<'next' | 'prev'>('next'),
startDate = ref(props.modelValue || today()),
endDate = ref('0000-00-00'),
maxDaysRendered = ref(0), // always 0
emittedValue = ref(props.modelValue),
focusRef = ref<string>(props.modelValue || today()),
focusValue = ref<Timestamp>(parsed(props.modelValue || today()) as Timestamp),
datesRef = ref<Record<string, HTMLElement>>({}),
// taskRef = ref(null),
// weekEventRef = ref([]),
// weekRef = ref([]),
// headerColumnRef = ref(null),
size = reactive<Size>({ width: 0, height: 0 }),
dragOverHeadDayRef = ref(''),
dragOverResource = ref(''),
// keep track of last seen start and end dates
lastStart = ref(null),
lastEnd = ref(null)
watch(
() => props.view,
() => {
// reset maxDaysRendered
maxDaysRendered.value = 0
},
)
const parsedView = computed(() => {
if (props.view === 'month') {
return 'month-interval'
}
return props.view as string
})
const vm = getCurrentInstance()
if (vm === null) {
throw new Error('current instance is null')
}
// initialize emit listeners
const { emitListeners } = useEmitListeners(vm)
const { times, setCurrent, updateCurrent } = useTimes(props)
// update dates
updateCurrent()
setCurrent()
const {
// computed
parsedStart,
// parsedEnd,
dayFormatter,
weekdayFormatter,
ariaDateFormatter,
// methods
dayStyleDefault,
getRelativeClasses,
} = useCommon(props, { startDate, endDate, times })
// const { isSticky } = useCellWidth(props)
const parsedValue = computed(() => {
return parseTimestamp(props.modelValue, times.now) || parsedStart.value || times.today
})
focusValue.value = parsedValue.value
focusRef.value = parsedValue.value.date
// const computedStyles = computed(() => {
// const style: CSSProperties = {}
// style.minWidth = computedWidth.value
// style.maxWidth = computedWidth.value
// style.width = computedWidth.value
// return style
// })
/// @ts-expect-error fix later
const { renderValues } = useRenderValues(props, {
parsedView,
times,
parsedValue,
})
const { rootRef, __initCalendar, __renderCalendar } = useCalendar(props, __renderTask, {
scrollArea,
pane,
})
const {
// computed
days,
parsedStartDate,
parsedEndDate,
// methods
/// @ts-expect-error fix later
} = useTask(props, emit, {
times,
})
const { move } = useMove(props, {
parsedView,
parsedValue,
direction,
maxDays: maxDaysRendered,
times,
emittedValue,
emit,
})
const { getDefaultMouseEventHandlers } = useMouse(emit, emitListeners)
const { checkChange } = useCheckChange(emit, { days, lastStart, lastEnd })
const { isKeyCode } = useEvents()
/// @ts-expect-error fix later
const { tryFocus } = useKeyboard(props, {
rootRef,
focusRef,
focusValue,
datesRef,
days,
parsedView,
parsedValue,
emittedValue,
direction,
times,
})
// const parsedColumnCount = computed(() => {
// return days.value.length
// })
// const borderWidth = computed(() => {
// if (rootRef.value) {
// const calendarBorderWidth = getComputedStyle(rootRef.value).getPropertyValue('--calendar-border')
// const parts = calendarBorderWidth.split(' ')
// const part = parts.filter(part => part.indexOf('px') > -1)
// return parseInt(part[ 0 ], 0)
// }
// return 0
// })
const isSticky = ref(true)
const parsedCellWidth = computed(() => {
if (props.cellWidth !== undefined) {
return parseInt(String(props.cellWidth), 10)
}
return 150 // default when not specified
})
// const computedWidth = computed(() => {
// if (rootRef.value) {
// const width = size.width || rootRef.value.getBoundingClientRect().width
// if (width && parsedColumnCount.value) {
// return (width / parsedColumnCount.value) + 'px'
// }
// }
// return (100 / parsedColumnCount.value) + '%'
// })
const isDayFocusable = computed(() => {
return props.focusable === true && props.focusType.includes('day')
})
const isDateFocusable = computed(() => {
return (
props.focusable === true &&
props.focusType.includes('date') &&
isDayFocusable.value !== true
)
})
const isWeekdayFocusable = computed(() => {
return props.focusable === true && props.focusType.includes('weekday')
})
const parsedHeight = computed(() => {
return parseInt(String(props.dayHeight), 10)
})
const parsedMinHeight = computed(() => {
return parseInt(String(props.dayMinHeight), 10)
})
watch([days], checkChange, { deep: true, immediate: true })
watch(
() => props.modelValue,
(val, oldVal) => {
if (emittedValue.value !== val) {
if (props.animated === true) {
const v1 = getDayIdentifier(parsed(val) as Timestamp)
const v2 = getDayIdentifier(parsed(oldVal) as Timestamp)
direction.value = v1 >= v2 ? 'next' : 'prev'
}
emittedValue.value = val
}
focusRef.value = val
},
)
watch(emittedValue, (val, oldVal) => {
if (emittedValue.value !== props.modelValue) {
if (props.animated === true) {
const v1 = getDayIdentifier(parsed(val) as Timestamp)
const v2 = getDayIdentifier(parsed(oldVal) as Timestamp)
direction.value = v1 >= v2 ? 'next' : 'prev'
}
emit('update:model-value', val)
}
})
watch(focusRef, (val) => {
if (val) {
focusValue.value = parseTimestamp(val) as Timestamp
}
})
watch(focusValue, () => {
if (datesRef.value[focusRef.value]) {
datesRef.value[focusRef.value].focus()
} else {
// if focusRef is not in the list of current dates of dateRef,
// then assume month is changing
tryFocus()
}
})
onBeforeUpdate(() => {
datesRef.value = {}
// weekEventRef.value = []
// weekRef.value = []
})
onMounted(() => {
__initCalendar()
})
// public functions
function moveToToday(): void {
emittedValue.value = today()
}
function next(amount = 1): void {
move(amount)
}
function prev(amount = 1): void {
move(-amount)
}
// private functions
function __onResize({ width, height }: Size): void {
size.width = width
size.height = height
}
function __isActiveDate(day: Timestamp): boolean {
return day.date === emittedValue.value
}
/**
* Renders the given day with the associated task
* @param {Timestamp} day Timestamp representing the day
* @param {any} task The Task
* @param {number} taskIndex The task index
* @returns VNode
*/
function __renderTaskDay(day: Timestamp, task: Task, taskIndex: number): VNode {
const slot = slots.day
const styler = props.dayStyle || dayStyleDefault
const activeDate = props.noActiveDate !== true && parsedValue.value.date === day.date
const dragValue = task[props.taskKey]
const scope = {
timestamp: day,
task,
taskIndex,
activeDate,
droppable: dragOverResource.value === dragValue,
}
const width = convertToUnit(parsedCellWidth.value)
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
...styler({ scope }),
}
const dayClass = typeof props.dayClass === 'function' ? props.dayClass({ scope }) : {}
// const key = day.date + '-' + task.id
return h(
'div',
{
// key,
// ref: (el) => {
// if (isDayFocusable.value === true) {
// datesRef.value[ key ] = el
// }
// },
tabindex: isDayFocusable.value === true ? 0 : -1,
class: {
'q-calendar-task__task--day': true,
...dayClass,
...getRelativeClasses(day),
'q-active-date': activeDate === true,
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isDayFocusable.value === true,
},
style,
onFocus: () => {
if (isDayFocusable.value === true) {
// focusRef.value = key
}
},
...getDefaultMouseEventHandlers('-day', (event) => {
return { scope, event }
}),
onDragenter: (e: DragEvent) => {
if (props.dragEnterFunc !== undefined && typeof props.dragEnterFunc === 'function') {
props.dragEnterFunc(e, 'day', { scope }) === true
? (dragOverResource.value = dragValue)
: (dragOverResource.value = '')
}
},
onDragover: (e: DragEvent) => {
if (props.dragOverFunc !== undefined && typeof props.dragOverFunc === 'function') {
props.dragOverFunc(e, 'day', { scope }) === true
? (dragOverResource.value = dragValue)
: (dragOverResource.value = '')
}
},
onDragleave: (e: DragEvent) => {
if (props.dragLeaveFunc !== undefined && typeof props.dragLeaveFunc === 'function') {
props.dragLeaveFunc(e, 'day', { scope }) === true
? (dragOverResource.value = dragValue)
: (dragOverResource.value = '')
}
},
onDrop: (e: DragEvent) => {
if (props.dropFunc !== undefined && typeof props.dropFunc === 'function') {
props.dropFunc(e, 'day', { scope }) === true
? (dragOverResource.value = dragValue)
: (dragOverResource.value = '')
}
},
},
[slot && slot({ scope }), useFocusHelper()],
)
}
function __renderTaskDays(task: Task, taskIndex: number): VNode[] {
return days.value.map((day) => __renderTaskDay(day, task, taskIndex))
}
function __renderTaskDaysRow(task: Task, taskIndex: number): VNode {
const slot = slots.days
const scope = {
timestamps: days.value,
days: days.value, // deprecated
task,
taskIndex,
cellWidth: parsedCellWidth.value,
}
return h(
'div',
{
class: 'q-calendar-task__task--days-row',
},
[__renderTaskDays(task, taskIndex), slot && slot({ scope })],
)
}
function __renderTaskItem(
task: Task,
taskIndex: number,
indentLevel = 0,
expanded = true,
): VNode {
const slot = indentLevel === 0 ? slots.task : slots.subtask
const scope = {
start: parsedStartDate.value,
end: parsedEndDate.value,
task,
taskIndex,
expanded,
}
const width = convertToUnit(props.taskWidth)
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
}
const isFocusable = props.focusable === true && props.focusType.includes('task')
return h(
'div',
{
class: {
'q-calendar-task__task--item': true,
'q-calendar__sticky': isSticky.value === true,
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isFocusable === true,
},
style,
},
[
h(
'div',
{
style: {
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: 10 + 10 * indentLevel + 'px',
},
},
[
h('div', {
class: {
'q-calendar__parent': task.children !== undefined,
'q-calendar__parent--expanded':
task.children !== undefined && task.expanded === true,
'q-calendar__parent--collapsed':
task.children !== undefined && task.expanded !== true,
},
onClick: (e) => {
e.stopPropagation()
task.expanded = !task.expanded
// emit('update:model-tasks', props.modelTasks)
emit('task-expanded', { expanded: task.expanded, scope })
},
}),
],
),
slot && slot({ scope }),
useFocusHelper(),
],
)
}
function __renderTaskRow(
task: Task,
taskIndex: number,
indentLevel = 0,
expanded = true,
): VNode[] {
const height =
task.height !== void 0
? convertToUnit(parseInt(task.height, 10))
: parsedHeight.value > 0
? convertToUnit(parsedHeight.value)
: 'auto'
const minHeight = parsedMinHeight.value > 0 ? convertToUnit(parsedMinHeight.value) : void 0
const style: CSSProperties = {
height,
}
if (minHeight !== void 0) {
style.minHeight = minHeight
}
const taskRow = h(
'div',
{
key: task[props.taskKey] + '-' + taskIndex,
class: {
'q-calendar-task__task': indentLevel === 0,
'q-calendar-task__task--section': indentLevel !== 0,
},
style,
},
[
__renderTaskItem(task, taskIndex, indentLevel, expanded),
__renderTaskDaysRow(task, taskIndex),
],
)
if (task.children !== undefined) {
return [
taskRow,
h(
'div',
{
class: {
'q-calendar__child': true,
'q-calendar__child--expanded': expanded === true,
'q-calendar__child--collapsed': expanded !== true,
},
},
[
__renderTasks(
task.children,
indentLevel + 1,
expanded === false ? expanded : task.expanded,
),
],
),
]
}
return [taskRow]
}
function __renderTasks(
tasks: Task[] | void = undefined,
indentLevel = 0,
expanded = true,
): VNode[] {
if (tasks === undefined) {
tasks = props.modelTasks
}
return (tasks as Array<Task>)
.map((task, taskIndex) => {
return __renderTaskRow(
task,
taskIndex,
indentLevel,
task.children !== undefined ? task.expanded : expanded,
)
})
.flat()
}
function __renderTasksContainer(): VNode {
return h(
'div',
{
class: {
'q-calendar-task__task--container': true,
'q-calendar__sticky': isSticky.value === true,
},
},
[__renderTasks()],
)
}
function __renderFooterTask(task: Task, index: number): VNode {
const slot = slots['footer-task']
const scope = {
start: parsedStartDate.value,
end: parsedEndDate.value,
footer: task,
index,
}
const width = convertToUnit(props.taskWidth)
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
}
return h(
'div',
{
class: {
'q-calendar-task__footer--task': true,
'q-calendar__sticky': isSticky.value === true,
},
style,
},
[slot && slot({ scope })],
)
}
function __renderFooterDay(day: Timestamp, task: Task, index: number): VNode {
const slot = slots['footer-day']
const scope = {
timestamp: day,
footer: task,
index,
}
const width = convertToUnit(parsedCellWidth.value)
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
}
const footerDayClass =
typeof props.footerDayClass === 'function' ? props.footerDayClass({ scope }) : {}
return h(
'div',
{
class: {
'q-calendar-task__footer--day': true,
...footerDayClass,
...getRelativeClasses(day),
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isDayFocusable.value === true,
},
style,
// onFocus: (e) => {
// if (isDayFocusable.value === true) {
// focusRef.value = day.date
// }
// }
},
[slot && slot({ scope })],
)
}
function __renderFooterDays(task: Task, index: number): VNode {
return h(
'div',
{
class: 'q-calendar-task__footer--day-wrapper',
},
[days.value.map((day) => __renderFooterDay(day, task, index))],
)
}
function __renderFooterRows(): VNode[] {
const isFocusable = props.focusable === true && props.focusType.includes('task')
return props.modelFooter.map((task, index) => {
return h(
'div',
{
class: {
'q-calendar-task__footer--wrapper': true,
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isFocusable === true,
},
},
{
default: () => [__renderFooterTask(task, index), __renderFooterDays(task, index)],
},
)
})
}
function __renderFooter(): VNode {
return h(
'div',
{
class: {
'q-calendar-task__footer': true,
'q-calendar__sticky': isSticky.value === true,
},
},
__renderFooterRows(),
)
}
function __renderContainer(): VNode {
return h(
'div',
{
class: {
'q-calendar-task__container': true,
},
},
[props.noHeader !== true && __renderHead(), __renderTasksContainer(), __renderFooter()],
)
}
function __renderHeadTask(): VNode {
const slot = slots['head-tasks']
const scope = {
start: parsedStartDate.value,
end: parsedEndDate.value,
}
const width = convertToUnit(parseInt(String(props.taskWidth), 10))
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
}
return h(
'div',
{
class: {
'q-calendar-task__head--tasks': true,
'q-calendar__sticky': isSticky.value === true,
},
style,
},
[slot && slot({ scope })],
)
}
function __renderTitleTask(title: string, index: number): VNode {
const slot = slots['title-task']
const width = convertToUnit(parseInt(String(props.taskWidth), 10))
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
}
const scope = {
start: parsedStartDate.value,
end: parsedEndDate.value,
cellWidth: width,
title,
index,
}
return h(
'div',
{
class: {
'q-calendar-task__title--task': true,
'q-calendar__sticky': isSticky.value === true,
},
style,
},
[slot && slot({ scope })],
)
}
function __renderHeadWeekday(day: Timestamp): VNode {
const slot = slots['head-weekday-label']
const activeDate = props.noActiveDate !== true && __isActiveDate(day)
const scope = {
activeDate,
timestamp: day,
disabled: props.disabledWeekdays
? props.disabledWeekdays.includes(Number(day.weekday))
: false,
}
const data: Record<string, any> = {
class: {
'q-calendar-task__head--weekday': true,
['q-calendar__' + props.weekdayAlign]: true,
'q-calendar__ellipsis': true,
},
}
return h(
'div',
data,
(slot && slot({ scope })) || __renderHeadWeekdayLabel(day, props.shortWeekdayLabel),
)
}
function __renderHeadWeekdayLabel(day: Timestamp, shortWeekdayLabel: boolean): VNode {
const weekdayLabel = weekdayFormatter.value(
day,
shortWeekdayLabel ||
(props.weekdayBreakpoints[0] > 0 && parsedCellWidth.value <= props.weekdayBreakpoints[0]),
)
return h(
'span',
{
class: 'q-calendar__ellipsis',
},
props.weekdayBreakpoints[1] > 0 && parsedCellWidth.value <= props.weekdayBreakpoints[1]
? minCharWidth(weekdayLabel, Number(props.minWeekdayLabel))
: weekdayLabel,
)
}
function __renderHeadDayDate(day: Timestamp): VNode {
const data: Record<string, any> = {
class: {
'q-calendar-task__head--date': true,
['q-calendar__' + props.dateAlign]: true,
},
}
return h('div', data, __renderHeadDayBtn(day))
}
function __renderHeadDayBtn(day: Timestamp): VNode | VNode[] {
const activeDate = props.noActiveDate !== true && __isActiveDate(day)
const dayLabel = dayFormatter.value(day, false)
const headDayLabelSlot = slots['head-day-label']
const headDayButtonSlot = slots['head-day-button']
const scope = { dayLabel, timestamp: day, activeDate }
const key = day.date
const data: Record<string, any> = {
key,
tabindex: isDateFocusable.value === true ? 0 : -1,
class: {
'q-calendar-task__head--day__label': true,
'q-calendar__button': true,
'q-calendar__button--round': props.dateType === 'round',
'q-calendar__button--rounded': props.dateType === 'rounded',
'q-calendar__button--bordered': day.current === true,
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isDateFocusable.value === true,
},
disabled: day.disabled,
onKeydown: (e: KeyboardEvent) => {
if (day.disabled !== true && isKeyCode(e, [13, 32])) {
e.stopPropagation()
e.preventDefault()
}
},
onKeyup: (e: KeyboardEvent) => {
// allow selection of date via Enter or Space keys
if (day.disabled !== true && isKeyCode(e, [13, 32])) {
emittedValue.value = day.date
if (emitListeners.value.onClickDate !== undefined) {
emit('click-date', { scope })
}
}
},
...getDefaultMouseEventHandlers('-date', (event, eventName) => {
if (eventName === 'click-date' || eventName === 'contextmenu-date') {
emittedValue.value = day.date
if (eventName === 'click-date') {
event.preventDefault()
}
}
return { scope, event }
}),
}
if (props.noAria !== true) {
data.ariaLabel = ariaDateFormatter.value(day, false)
}
return headDayButtonSlot
? headDayButtonSlot({ scope })
: renderButton(props, data, headDayLabelSlot ? headDayLabelSlot({ scope }) : dayLabel)
}
function __renderDateHeader(day: Timestamp): VNode | VNode[] | void {
if (props.dateHeader === 'stacked') {
return [
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
].filter((v): v is VNode => v !== false)
} else if (props.dateHeader === 'inline') {
if (props.weekdayAlign === 'left' && props.dateAlign === 'right') {
return h(
'div',
{
class: 'q-calendar__header--inline',
},
[
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
],
)
} else if (props.weekdayAlign === 'right' && props.dateAlign === 'left') {
return h(
'div',
{
class: 'q-calendar__header--inline',
},
[
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
],
)
} else {
return h(
'div',
{
class: 'q-calendar__header--inline',
},
[
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
],
)
}
} else if (props.dateHeader === 'inverted') {
if (props.weekdayAlign === 'left' && props.dateAlign === 'right') {
return h(
'div',
{
class: 'q-calendar__header--inline',
},
[
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
],
)
} else if (props.weekdayAlign === 'right' && props.dateAlign === 'left') {
return h(
'div',
{
class: 'q-calendar__header--inline',
},
[
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
],
)
} else {
return h(
'div',
{
class: 'q-calendar__header--inline',
},
[
props.noDefaultHeaderBtn !== true && __renderHeadDayDate(day),
props.noDefaultHeaderText !== true && __renderHeadWeekday(day),
],
)
}
}
}
/**
* Renders the given day with the associated task
* @param {Timestamp} day Timestamp representing the day
* @param {sting} title The Title
* @param {number} index The task index
* @returns VNode
*/
function __renderTitleDay(day: Timestamp, title: string, index: number): VNode {
const slot = slots['title-day']
const width = convertToUnit(parsedCellWidth.value)
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
}
const scope = {
timestamp: day,
title,
index,
cellWidth: parsedCellWidth.value,
}
const dayClass = typeof props.dayClass === 'function' ? props.dayClass({ scope }) : {}
const isFocusable = props.focusable === true && props.focusType.includes('day')
return h(
'div',
{
class: {
'q-calendar-task__title--day': true,
...dayClass,
...getRelativeClasses(day),
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isFocusable === true,
},
style,
},
[slot && slot({ scope }), useFocusHelper()],
)
}
function __renderHeadDay(day: Timestamp): VNode {
const headDaySlot = slots['head-day']
const headDateSlot = slots['head-date']
const activeDate = props.noActiveDate !== true && __isActiveDate(day)
const scope = {
timestamp: day,
activeDate,
droppable: (dragOverHeadDayRef.value = day.date),
disabled: props.disabledWeekdays
? props.disabledWeekdays.includes(Number(day.weekday))
: false,
}
const styler = props.weekdayStyle || dayStyleDefault
const weekdayClass =
typeof props.weekdayClass === 'function' ? props.weekdayClass({ scope }) : {}
const width = convertToUnit(parsedCellWidth.value)
const style: CSSProperties = {
width,
minWidth: width,
maxWidth: width,
...styler({ scope }),
}
const key = day.date
const data: Record<string, any> = {
key,
ref: (el: HTMLElement) => {
datesRef.value[key] = el
},
tabindex: isWeekdayFocusable.value === true ? 0 : -1,
class: {
'q-calendar-task__head--day': true,
...weekdayClass,
...getRelativeClasses(day),
'q-active-date': activeDate,
'q-calendar__hoverable': props.hoverable === true,
'q-calendar__focusable': isWeekdayFocusable.value === true,
},
style,
onFocus: () => {
if (isWeekdayFocusable.value === true) {
focusRef.value = key
}
},
onKeydown: (e: KeyboardEvent) => {
if (day.disabled !== true && isKeyCode(e, [13, 32])) {
e.stopPropagation()
e.preventDefault()
}
},
onKeyup: (e: KeyboardEvent) => {
// allow selection of date via Enter or Space keys
if (day.disabled !== true && isKeyCode(e, [13, 32])) {
emittedValue.value = day.date
}
},
...getDefaultMouseEventHandlers('-head-day', (event) => {
return { scope, event }
}),
onDragenter: (e: DragEvent) => {
if (props.dragEnterFunc !== undefined && typeof props.dragEnterFunc === 'function') {
props.dragEnterFunc(e, 'head-day', { scope }) === true
? (dragOverHeadDayRef.value = day.date)
: (dragOverHeadDayRef.value = '')
}
},
onDragover: (e: DragEvent) => {
if (props.dragOverFunc !== undefined && typeof props.dragOverFunc === 'function') {
props.dragOverFunc(e, 'head-day', { scope }) === true
? (dragOverHeadDayRef.value = day.date)
: (dragOverHeadDayRef.value = '')
}
},
onDragleave: (e: DragEvent) => {
if (props.dragLeaveFunc !== undefined && typeof props.dragLeaveFunc === 'function') {
props.dragLeaveFunc(e, 'head-day', { scope }) === true
? (dragOverHeadDayRef.value = day.date)
: (dragOverHeadDayRef.value = '')
}
},
onDrop: (e: DragEvent) => {
if (props.dropFunc !== undefined && typeof props.dropFunc === 'function') {
props.dropFunc(e, 'head-day', { scope }) === true
? (dragOverHeadDayRef.value = day.date)
: (dragOverHeadDayRef.value = '')
}
},
}
return h('div', data, [
// head-day slot replaces everything below it
headDaySlot !== undefined && headDaySlot({ scope }),
headDaySlot === undefined && __renderDateHeader(day),
headDaySlot === undefined && headDateSlot && headDateSlot({ scope }),
useFocusHelper(),
])
}
function __renderHeadDays(): VNode[] {
return days.value.map((day) => __renderHeadDay(day))
}
function __renderTitleDays(title: string, index: number): VNode[] {
return days.value.map((day) => __renderTitleDay(day, title, index))
}
function __renderHeadDaysRow(): VNode {
return h(
'div',
{
class: {
'q-calendar-task__head--days': true,
},
},
[...__renderHeadDays()],
)
}
function __renderTitleDaysRow(title: string, index: number): VNode {
return h(
'div',
{
class: {
'q-calendar-task__title--days': true,
},
},
[...__renderTitleDays(title, index)],
)
}
// ----
function __renderHead(): VNode {
return h(
'div',
{
roll: 'presentation',
class: {
'q-calendar-task__head': true,
'q-calendar__sticky': isSticky.value === true,
},
style: {},
},
[
h(
'div',
{
style: {
position: 'relative',
display: 'flex',
},
},
[__renderHeadTask(), __renderHeadDaysRow()],
),
props.modelTitle.map((title, index) =>
h(
'div',
{
class: 'q-calendar-task__title',
style: {
position: 'relative',
display: 'flex',
},
},
[__renderTitleTask(title, index), __renderTitleDaysRow(title, index)],
),
),
],
)
}
function __renderBody(): VNode {
return h(
'div',
{
class: 'q-calendar-task__body',
},
[__renderScrollArea()],
)
}
function __renderScrollArea(): VNode {
return h(
'div',
{
ref: scrollArea,
class: {
'q-calendar-task__scroll-area': true,
'q-calendar__scroll': true,
},
},
[__renderContainer()],
)
}
function __renderTask(): VNode {
const { start, end } = renderValues.value
startDate.value = start.date
endDate.value = end.date
const hasWidth = size.width > 0
const weekly = withDirectives(
h(
'div',
{
key: startDate.value,
class: 'q-calendar-task',
},
[hasWidth === true && __renderBody()],
),
[[ResizeObserver, __onResize]],
)
if (props.animated === true) {
const transition =
'q-calendar--' +
(direction.value === 'prev' ? props.transitionPrev : props.transitionNext)
return h(
Transition,
{
name: transition,
appear: true,
},
() => weekly,
)
}
return weekly
}
// expose public methods
expose({
prev,
next,
move,
moveToToday,
updateCurrent,
})
// Object.assign(vm.proxy, {
// prev,
// next,
// move,
// moveToToday,
// updateCurrent
// })
return (): VNode => __renderCalendar()
},
})