#12518 feat(VCalendar): add support for object categories

Merged nquinn721
Coverage Reach
components/VCalendar/mixins/calendar-with-events.ts components/VCalendar/mixins/calendar-with-intervals.ts components/VCalendar/mixins/calendar-base.ts components/VCalendar/mixins/times.ts components/VCalendar/mixins/mouse.ts components/VCalendar/util/timestamp.ts components/VCalendar/util/props.ts components/VCalendar/util/events.ts components/VCalendar/util/parser.ts components/VCalendar/modes/stack.ts components/VCalendar/modes/common.ts components/VCalendar/modes/column.ts components/VCalendar/modes/index.ts components/VCalendar/VCalendar.ts components/VCalendar/VCalendarDaily.ts components/VCalendar/VCalendarWeekly.ts components/VCalendar/VCalendarCategory.ts components/VCalendar/VCalendarMonthly.ts components/VDatePicker/VDatePicker.ts components/VDatePicker/mixins/date-picker-table.ts components/VDatePicker/VDatePickerDateTable.ts components/VDatePicker/util/pad.ts components/VDatePicker/util/createNativeLocaleFormatter.ts components/VDatePicker/util/eventHelpers.ts components/VDatePicker/util/monthChange.ts components/VDatePicker/util/isDateAllowed.ts components/VDatePicker/VDatePickerHeader.ts components/VDatePicker/VDatePickerYears.ts components/VDatePicker/VDatePickerTitle.ts components/VDatePicker/VDatePickerMonthTable.ts components/VSelect/VSelect.ts components/VSelect/VSelectList.ts components/VDataTable/VDataTable.ts components/VDataTable/VDataTableHeaderDesktop.ts components/VDataTable/VVirtualTable.ts components/VDataTable/VEditDialog.ts components/VDataTable/VDataTableHeaderMobile.ts components/VDataTable/MobileRow.ts components/VDataTable/Row.ts components/VDataTable/RowGroup.ts components/VDataTable/mixins/header.ts components/VDataTable/VSimpleTable.ts components/VDataTable/VDataTableHeader.ts components/VDataTable/index.ts components/VTreeview/VTreeview.ts components/VTreeview/VTreeviewNode.ts components/VTreeview/util/filterTreeItems.ts components/VTimePicker/VTimePicker.ts components/VTimePicker/VTimePickerClock.ts components/VTimePicker/VTimePickerTitle.ts components/VColorPicker/util/index.ts components/VColorPicker/VColorPickerCanvas.ts components/VColorPicker/VColorPickerEdit.ts components/VColorPicker/VColorPicker.ts components/VColorPicker/VColorPickerSwatches.ts components/VColorPicker/VColorPickerPreview.ts components/VSlider/VSlider.ts components/VAutocomplete/VAutocomplete.ts components/VTextField/VTextField.ts components/VMenu/VMenu.ts components/VNavigationDrawer/VNavigationDrawer.ts components/VSparkline/VSparkline.ts components/VSparkline/helpers/core.ts components/VSparkline/helpers/path.ts components/VSparkline/helpers/math.ts components/VDataIterator/VDataIterator.ts components/VDataIterator/VDataFooter.ts components/VTabs/VTabs.ts components/VTabs/VTab.ts components/VTabs/VTabsBar.ts components/VTabs/VTabsItems.ts components/VTabs/VTabItem.ts components/VTabs/VTabsSlider.ts components/VData/VData.ts components/VWindow/VWindow.ts components/VWindow/VWindowItem.ts components/VList/VListGroup.ts components/VList/VListItem.ts components/VList/VList.ts components/VList/VListItemAvatar.ts components/VList/VListItemAction.ts components/VList/VListItemGroup.ts components/VList/index.ts components/VList/VListItemIcon.ts components/VAppBar/VAppBar.ts components/VAppBar/VAppBarTitle.ts components/VAppBar/VAppBarNavIcon.ts components/VSlideGroup/VSlideGroup.ts components/VStepper/VStepperContent.ts components/VStepper/VStepperStep.ts components/VStepper/VStepper.ts components/VStepper/index.ts components/VItemGroup/VItemGroup.ts components/VItemGroup/VItem.ts components/VGrid/VCol.ts components/VGrid/VRow.ts components/VGrid/grid.ts components/VGrid/VContainer.ts components/VCombobox/VCombobox.ts components/VFileInput/VFileInput.ts components/VInput/VInput.ts components/VDialog/VDialog.ts components/VRangeSlider/VRangeSlider.ts components/VImg/VImg.ts components/VIcon/VIcon.ts components/VTooltip/VTooltip.ts components/transitions/expand-transition.ts components/transitions/createTransition.ts components/transitions/index.ts components/VPagination/VPagination.ts components/VExpansionPanel/VExpansionPanel.ts components/VExpansionPanel/VExpansionPanelHeader.ts components/VExpansionPanel/VExpansionPanels.ts components/VExpansionPanel/VExpansionPanelContent.ts components/VAlert/VAlert.ts components/VRadioGroup/VRadio.ts components/VRadioGroup/VRadioGroup.ts components/VProgressLinear/VProgressLinear.ts components/VRating/VRating.ts components/VCarousel/VCarousel.ts components/VCarousel/VCarouselItem.ts components/VBadge/VBadge.ts components/VBtn/VBtn.ts components/VSkeletonLoader/VSkeletonLoader.ts components/VSnackbar/VSnackbar.ts components/VForm/VForm.ts components/VToolbar/VToolbar.ts components/VToolbar/index.ts components/VOverflowBtn/VOverflowBtn.ts components/VCheckbox/VCheckbox.ts components/VCheckbox/VSimpleCheckbox.ts components/VChip/VChip.ts components/VBanner/VBanner.ts components/VFooter/VFooter.ts components/VProgressCircular/VProgressCircular.ts components/VTextarea/VTextarea.ts components/VTimeline/VTimelineItem.ts components/VTimeline/VTimeline.ts components/VSwitch/VSwitch.ts components/VBottomNavigation/VBottomNavigation.ts components/VVirtualScroll/VVirtualScroll.ts components/VBreadcrumbs/VBreadcrumbs.ts components/VBreadcrumbs/VBreadcrumbsItem.ts components/VParallax/VParallax.ts components/VCard/VCard.ts components/VCard/index.ts components/VSpeedDial/VSpeedDial.ts components/VHover/VHover.ts components/VPicker/VPicker.ts components/VSystemBar/VSystemBar.ts components/VOverlay/VOverlay.ts components/VLazy/VLazy.ts components/VResponsive/VResponsive.ts components/VChipGroup/VChipGroup.ts components/VApp/VApp.ts components/VMessages/VMessages.ts components/VContent/VContent.ts components/VAvatar/VAvatar.ts components/VSheet/VSheet.ts components/VCounter/VCounter.ts components/VMain/VMain.ts components/VBtnToggle/VBtnToggle.ts components/VThemeProvider/VThemeProvider.ts components/VLabel/VLabel.ts components/VDivider/VDivider.ts components/VBottomSheet/VBottomSheet.ts components/VSubheader/VSubheader.ts mixins/menuable/index.ts mixins/validatable/index.ts mixins/overlayable/index.ts mixins/activatable/index.ts mixins/selectable/index.ts mixins/detachable/index.ts mixins/routable/index.ts mixins/themeable/index.ts mixins/dependent/index.ts mixins/applicationable/index.ts mixins/scrollable/index.ts mixins/translatable/index.ts mixins/colorable/index.ts mixins/stackable/index.ts mixins/picker/index.ts mixins/groupable/index.ts mixins/intersectable/index.ts mixins/measurable/index.ts mixins/roundable/index.ts mixins/mobile/index.ts mixins/proxyable/index.ts mixins/binds-attrs/index.ts mixins/registrable/index.ts mixins/picker-button/index.ts mixins/toggleable/index.ts mixins/delayable/index.ts mixins/returnable/index.ts mixins/sizeable/index.ts mixins/bootable/index.ts mixins/elevatable/index.ts mixins/rippleable/index.ts mixins/loadable/index.ts mixins/ssr-bootable/index.ts mixins/positionable/index.ts mixins/button-group/index.ts mixins/localable/index.ts util/helpers.ts util/colorUtils.ts util/mergeData.ts util/console.ts util/color/transformSRGB.ts util/color/transformCIELAB.ts util/dateTimeUtils.ts util/colors.ts util/dedupeModelListeners.ts util/rebuildFunctionalSlots.ts util/mixins.ts util/component.ts services/theme/index.ts services/theme/utils.ts services/breakpoint/index.ts services/goto/util.ts services/goto/index.ts services/goto/easing-patterns.ts services/lang/index.ts services/application/index.ts services/icons/presets/fa-svg.ts services/icons/presets/fa.ts services/icons/presets/mdi-svg.ts services/icons/presets/mdi.ts services/icons/presets/md.ts services/icons/presets/fa4.ts services/icons/index.ts services/presets/index.ts services/service/index.ts directives/ripple/index.ts directives/touch/index.ts directives/color/index.ts directives/intersect/index.ts directives/mutate/index.ts directives/click-outside/index.ts directives/scroll/index.ts directives/resize/index.ts install.ts framework.ts index.ts presets/default/index.ts

No flags found

Use flags to group coverage reports by test type, project and/or folders.
Then setup custom commit statuses and notifications for each flag.

e.g., #unittest #integration

#production #enterprise

#frontend #backend

Learn more about Codecov Flags here.


@@ -39,6 +39,7 @@
Loading
39 39
  CalendarEventOverlapMode,
40 40
  CalendarEvent,
41 41
  CalendarEventCategoryFunction,
42 +
  CalendarCategory,
42 43
} from 'vuetify/types'
43 44
44 45
// Types
@@ -80,7 +81,11 @@
Loading
80 81
    ripple,
81 82
  },
82 83
83 -
  props: props.events,
84 +
  props: {
85 +
    ...props.events,
86 +
    ...props.calendar,
87 +
    ...props.category,
88 +
  },
84 89
85 90
  computed: {
86 91
    noEvents (): boolean {
@@ -92,11 +97,6 @@
Loading
92 97
    parsedEventOverlapThreshold (): number {
93 98
      return parseInt(this.eventOverlapThreshold)
94 99
    },
95 -
    eventColorFunction (): CalendarEventColorFunction {
96 -
      return typeof this.eventColor === 'function'
97 -
        ? this.eventColor
98 -
        : () => (this.eventColor as string)
99 -
    },
100 100
    eventTimedFunction (): CalendarEventTimedFunction {
101 101
      return typeof this.eventTimed === 'function'
102 102
        ? this.eventTimed
@@ -126,11 +126,16 @@
Loading
126 126
      return this.parsedWeekdays
127 127
    },
128 128
    categoryMode (): boolean {
129 -
      return false
129 +
      return this.type === 'category'
130 130
    },
131 131
  },
132 132
133 133
  methods: {
134 +
    eventColorFunction (e: CalendarEvent): string {
135 +
      return typeof this.eventColor === 'function'
136 +
        ? this.eventColor(e)
137 +
        : e.color || this.eventColor
138 +
    },
134 139
    parseEvent (input: CalendarEvent, index = 0): CalendarEventParsed {
135 140
      return parseEvent(
136 141
        input,
@@ -396,9 +401,10 @@
Loading
396 401
        event => isEventOverlapping(event, start, end)
397 402
      )
398 403
    },
399 -
    isEventForCategory (event: CalendarEventParsed, category: string | undefined | null): boolean {
404 +
    isEventForCategory (event: CalendarEventParsed, category: CalendarCategory): boolean {
400 405
      return !this.categoryMode ||
401 -
        category === event.category ||
406 +
        (typeof category === 'object' && category.calendarName &&
407 +
        category.categoryName === event.category) ||
402 408
        (typeof event.category !== 'string' && category === null)
403 409
    },
404 410
    getEventsForDay (day: CalendarDaySlotScope): CalendarEventParsed[] {

@@ -1,7 +1,7 @@
Loading
1 1
2 2
import { validateTimestamp, parseDate, DAYS_IN_WEEK, validateTime } from './timestamp'
3 3
import { PropType } from 'vue'
4 -
import { CalendarEvent, CalendarFormatter, CalendarTimestamp, CalendarEventOverlapMode, CalendarEventNameFunction, CalendarEventColorFunction, CalendarEventCategoryFunction, CalendarEventTimedFunction } from 'vuetify/types'
4 +
import { CalendarEvent, CalendarFormatter, CalendarTimestamp, CalendarEventOverlapMode, CalendarEventNameFunction, CalendarEventColorFunction, CalendarEventCategoryFunction, CalendarEventTimedFunction, CalendarCategoryTextFunction, CalendarCategory } from 'vuetify/types'
5 5
import { CalendarEventOverlapModes } from '../modes'
6 6
import { PropValidator } from 'vue/types/options'
7 7
@@ -123,9 +123,12 @@
Loading
123 123
  },
124 124
  category: {
125 125
    categories: {
126 -
      type: [Array, String],
126 +
      type: [Array, String] as PropType<CalendarCategory[] | string>,
127 127
      default: '',
128 128
    },
129 +
    categoryText: {
130 +
      type: [String, Function] as PropType<string | CalendarCategoryTextFunction>,
131 +
    },
129 132
    categoryHideDynamic: {
130 133
      type: Boolean,
131 134
    },

@@ -34,7 +34,8 @@
Loading
34 34
import VCalendarDaily from './VCalendarDaily'
35 35
import VCalendarWeekly from './VCalendarWeekly'
36 36
import VCalendarCategory from './VCalendarCategory'
37 -
import { CalendarTimestamp, CalendarFormatter } from 'vuetify/types'
37 +
import { CalendarTimestamp, CalendarFormatter, CalendarCategory } from 'vuetify/types'
38 +
import { getParsedCategories } from './util/parser'
38 39
39 40
// Types
40 41
interface VCalendarRenderProps {
@@ -43,7 +44,7 @@
Loading
43 44
  component: string | Component
44 45
  maxDays: number
45 46
  weekdays: number[]
46 -
  categories: string[]
47 +
  categories: CalendarCategory[]
47 48
}
48 49
49 50
/* @vue/component */
@@ -170,12 +171,8 @@
Loading
170 171
        timeZone: 'UTC', month: 'short',
171 172
      })
172 173
    },
173 -
    parsedCategories (): string[] {
174 -
      return typeof this.categories === 'string' && this.categories
175 -
        ? this.categories.split(/\s*,\s*/)
176 -
        : Array.isArray(this.categories)
177 -
          ? this.categories as string[]
178 -
          : []
174 +
    parsedCategories (): CalendarCategory[] {
175 +
      return getParsedCategories(this.categories, this.categoryText)
179 176
    },
180 177
  },
181 178
@@ -294,10 +291,10 @@
Loading
294 291
    timestampToDate (timestamp: CalendarTimestamp): Date {
295 292
      return timestampToDate(timestamp)
296 293
    },
297 -
    getCategoryList (categories: string[]): string[] {
294 +
    getCategoryList (categories: CalendarCategory[]): CalendarCategory[] {
298 295
      if (!this.noEvents) {
299 296
        const categoryMap = categories.reduce((map, category, index) => {
300 -
          map[category] = { index, count: 0 }
297 +
          if (typeof category === 'object' && category.categoryName) map[category.categoryName] = { index, count: 0 }
301 298
302 299
          return map
303 300
        }, Object.create(null))
@@ -335,9 +332,13 @@
Loading
335 332
          }
336 333
        }
337 334
338 -
        categories = Object.keys(categoryMap)
335 +
        categories = categories.filter((category: CalendarCategory) => {
336 +
          if (typeof category === 'object' && category.categoryName) {
337 +
            return categoryMap.hasOwnProperty(category.categoryName)
338 +
          }
339 +
          return false
340 +
        })
339 341
      }
340 -
341 342
      return categories
342 343
    },
343 344
  },

@@ -0,0 +1,28 @@
Loading
1 +
import { CalendarCategory, CalendarCategoryTextFunction } from 'types'
2 +
3 +
export function parsedCategoryText (
4 +
  category: CalendarCategory,
5 +
  categoryText: string | CalendarCategoryTextFunction
6 +
): string {
7 +
  return typeof categoryText === 'string' && typeof category === 'object' && category
8 +
    ? category[categoryText]
9 +
    : typeof categoryText === 'function'
10 +
      ? categoryText(category)
11 +
      : category
12 +
}
13 +
14 +
export function getParsedCategories (
15 +
  categories: CalendarCategory | CalendarCategory[],
16 +
  categoryText: string | CalendarCategoryTextFunction
17 +
): CalendarCategory[] {
18 +
  if (typeof categories === 'string') return categories.split(/\s*,\s/)
19 +
  if (Array.isArray(categories)) {
20 +
    return categories.map((v: CalendarCategory) => {
21 +
      const categoryName = typeof v === 'string' ? v
22 +
        : typeof v === 'object' && v && typeof v.categoryName === 'string' ? v.categoryName
23 +
          : parsedCategoryText(v, categoryText)
24 +
      return { categoryName }
25 +
    })
26 +
  }
27 +
  return []
28 +
}

@@ -8,9 +8,10 @@
Loading
8 8
import VCalendarDaily from './VCalendarDaily'
9 9
10 10
// Util
11 -
import { getSlot } from '../../util/helpers'
12 -
import { CalendarTimestamp } from 'types'
11 +
import { convertToUnit, getSlot } from '../../util/helpers'
12 +
import { CalendarCategory, CalendarTimestamp } from 'types'
13 13
import props from './util/props'
14 +
import { getParsedCategories } from './util/parser'
14 15
15 16
/* @vue/component */
16 17
export default VCalendarDaily.extend({
@@ -26,15 +27,10 @@
Loading
26 27
        ...this.themeClasses,
27 28
      }
28 29
    },
29 -
    parsedCategories (): string[] {
30 -
      return typeof this.categories === 'string' && this.categories
31 -
        ? this.categories.split(/\s*,\s*/)
32 -
        : Array.isArray(this.categories)
33 -
          ? this.categories as string[]
34 -
          : []
30 +
    parsedCategories (): CalendarCategory[] {
31 +
      return getParsedCategories(this.categories, this.categoryText)
35 32
    },
36 33
  },
37 -
38 34
  methods: {
39 35
    genDayHeader (day: CalendarTimestamp, index: number): VNode[] {
40 36
      const data = {
@@ -44,14 +40,18 @@
Loading
44 40
        week: this.days, ...day, index,
45 41
      }
46 42
47 -
      const children = this.parsedCategories.map(category => this.genDayHeaderCategory(day, this.getCategoryScope(scope, category)))
43 +
      const children = this.parsedCategories.map(category => {
44 +
        return this.genDayHeaderCategory(day, this.getCategoryScope(scope, category))
45 +
      })
48 46
49 47
      return [this.$createElement('div', data, children)]
50 48
    },
51 -
    getCategoryScope (scope: any, category: string) {
49 +
    getCategoryScope (scope: any, category: CalendarCategory) {
50 +
      const cat = typeof category === 'object' && category &&
51 +
          category.categoryName === this.categoryForInvalid ? null : category
52 52
      return {
53 53
        ...scope,
54 -
        category: category === this.categoryForInvalid ? null : category,
54 +
        category: cat,
55 55
      }
56 56
    },
57 57
    genDayHeaderCategory (day: CalendarTimestamp, scope: any): VNode {
@@ -61,25 +61,68 @@
Loading
61 61
          return this.getCategoryScope(this.getSlotScope(day), scope.category)
62 62
        }),
63 63
      }, [
64 -
        getSlot(this, 'category', scope) || this.genDayHeaderCategoryTitle(scope.category),
64 +
        getSlot(this, 'category', scope) || this.genDayHeaderCategoryTitle(scope.category && scope.category.categoryName),
65 65
        getSlot(this, 'day-header', scope),
66 66
      ])
67 67
    },
68 -
    genDayHeaderCategoryTitle (category: string) {
68 +
    genDayHeaderCategoryTitle (categoryName: string | null) {
69 69
      return this.$createElement('div', {
70 70
        staticClass: 'v-calendar-category__category',
71 -
      }, category === null ? this.categoryForInvalid : category)
71 +
      }, categoryName === null ? this.categoryForInvalid : categoryName)
72 +
    },
73 +
    genDays (): VNode[] {
74 +
      const d = this.days[0]
75 +
      let days = this.days.slice()
76 +
      days = new Array(this.parsedCategories.length)
77 +
      days.fill(d)
78 +
      return days.map((v, i) => this.genDay(v, 0, i))
79 +
    },
80 +
    genDay (day: CalendarTimestamp, index: number, categoryIndex: number): VNode {
81 +
      const category = this.parsedCategories[categoryIndex]
82 +
      return this.$createElement('div', {
83 +
        key: day.date + '-' + categoryIndex,
84 +
        staticClass: 'v-calendar-daily__day',
85 +
        class: this.getRelativeClasses(day),
86 +
        on: this.getDefaultMouseEventHandlers(':time', e => {
87 +
          return this.getSlotScope(this.getTimestampAtEvent(e, day))
88 +
        }),
89 +
      }, [
90 +
        ...this.genDayIntervals(index, category),
91 +
        ...this.genDayBody(day, category),
92 +
      ])
93 +
    },
94 +
    genDayIntervals (index: number, category: CalendarCategory): VNode[] {
95 +
      return this.intervals[index].map(v => this.genDayInterval(v, category))
96 +
    },
97 +
    genDayInterval (interval: CalendarTimestamp, category: CalendarCategory): VNode {
98 +
      const height: string | undefined = convertToUnit(this.intervalHeight)
99 +
      const styler = this.intervalStyle || this.intervalStyleDefault
100 +
101 +
      const data = {
102 +
        key: interval.time,
103 +
        staticClass: 'v-calendar-daily__day-interval',
104 +
        style: {
105 +
          height,
106 +
          ...styler({ ...interval, category }),
107 +
        },
108 +
      }
109 +
110 +
      const children = getSlot(this, 'interval', () =>
111 +
        this.getCategoryScope(this.getSlotScope(interval), category)
112 +
      )
113 +
114 +
      return this.$createElement('div', data, children)
72 115
    },
73 -
    genDayBody (day: CalendarTimestamp): VNode[] {
116 +
    genDayBody (day: CalendarTimestamp, category: CalendarCategory): VNode[] {
74 117
      const data = {
75 118
        staticClass: 'v-calendar-category__columns',
76 119
      }
77 120
78 -
      const children = this.parsedCategories.map(category => this.genDayBodyCategory(day, category))
121 +
      const children = [this.genDayBodyCategory(day, category)]
79 122
80 123
      return [this.$createElement('div', data, children)]
81 124
    },
82 -
    genDayBodyCategory (day: CalendarTimestamp, category: string): VNode {
125 +
    genDayBodyCategory (day: CalendarTimestamp, category: CalendarCategory): VNode {
83 126
      const data = {
84 127
        staticClass: 'v-calendar-category__column',
85 128
        on: this.getDefaultMouseEventHandlers(':time-category', e => {

Learn more Showing 5 files with coverage changes found.

Changes in packages/vuetify/src/components/VAppBar/VAppBar.ts
-1
Loading file...
Changes in packages/vuetify/src/components/VAutocomplete/VAutocomplete.ts
New
Loading file...
New file packages/vuetify/src/components/VCalendar/util/parser.ts
New
Loading file...
New file packages/vuetify/src/components/VAppBar/VAppBarTitle.ts
New
Loading file...
Changes in packages/vuetify/src/components/VCalendar/VCalendarCategory.ts
-6
+6
Loading file...
Files Coverage
packages/vuetify/src 0.10% 87.03%
Project Totals (244 files) 87.03%
Loading