daisy-ui-kit 5.0.0-pre.9 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +277 -48
  2. package/app/components/Accordion.vue +8 -5
  3. package/app/components/Alert.vue +2 -1
  4. package/app/components/Avatar.vue +10 -7
  5. package/app/components/AvatarGroup.vue +6 -2
  6. package/app/components/Badge.vue +19 -1
  7. package/app/components/Button.vue +66 -46
  8. package/app/components/Calendar.vue +151 -42
  9. package/app/components/CalendarInput.vue +229 -130
  10. package/app/components/CalendarSkeleton.vue +51 -10
  11. package/app/components/Card.vue +20 -2
  12. package/app/components/CardActions.vue +1 -1
  13. package/app/components/CardBody.vue +1 -1
  14. package/app/components/CardTitle.vue +1 -1
  15. package/app/components/Carousel.vue +2 -1
  16. package/app/components/Chat.vue +6 -1
  17. package/app/components/Checkbox.vue +1 -1
  18. package/app/components/Collapse.vue +38 -5
  19. package/app/components/CollapseTitle.vue +11 -1
  20. package/app/components/Countdown.vue +3 -3
  21. package/app/components/CountdownTimers.vue +4 -7
  22. package/app/components/Counter.vue +14 -3
  23. package/app/components/DaisyLink.vue +33 -15
  24. package/app/components/Dock.vue +5 -6
  25. package/app/components/DockItem.vue +5 -3
  26. package/app/components/Drawer.vue +15 -12
  27. package/app/components/DrawerContent.vue +9 -6
  28. package/app/components/DrawerSide.vue +9 -6
  29. package/app/components/Dropdown.vue +61 -50
  30. package/app/components/DropdownButton.vue +11 -4
  31. package/app/components/DropdownContent.vue +90 -20
  32. package/app/components/DropdownTarget.vue +10 -3
  33. package/app/components/Fab.vue +16 -0
  34. package/app/components/FabClose.vue +18 -0
  35. package/app/components/FabMainAction.vue +5 -0
  36. package/app/components/FabTrigger.vue +117 -0
  37. package/app/components/Fieldset.vue +5 -4
  38. package/app/components/FileInput.vue +1 -1
  39. package/app/components/Filter.vue +45 -38
  40. package/app/components/Flex.vue +8 -1
  41. package/app/components/FlexItem.vue +30 -27
  42. package/app/components/Footer.vue +16 -12
  43. package/app/components/FooterTitle.vue +8 -5
  44. package/app/components/Hero.vue +9 -6
  45. package/app/components/HeroContent.vue +9 -6
  46. package/app/components/Hover3D.vue +22 -0
  47. package/app/components/HoverGallery.vue +11 -0
  48. package/app/components/Indicator.vue +12 -5
  49. package/app/components/IndicatorItem.vue +21 -14
  50. package/app/components/Input.vue +44 -47
  51. package/app/components/Kbd.vue +2 -1
  52. package/app/components/Label.vue +32 -29
  53. package/app/components/MenuExpand.vue +5 -13
  54. package/app/components/MenuExpandToggle.vue +7 -1
  55. package/app/components/MenuItem.vue +6 -4
  56. package/app/components/Modal.vue +23 -17
  57. package/app/components/Progress.vue +13 -1
  58. package/app/components/Prose.vue +7 -2
  59. package/app/components/RadialProgress.vue +8 -8
  60. package/app/components/Radio.vue +1 -1
  61. package/app/components/RadioGroup.vue +2 -2
  62. package/app/components/Range.vue +186 -46
  63. package/app/components/RangeMeasure.vue +33 -30
  64. package/app/components/RangeMeasureTick.vue +4 -5
  65. package/app/components/Rating.vue +70 -53
  66. package/app/components/Select.vue +44 -47
  67. package/app/components/SkeletonText.vue +11 -0
  68. package/app/components/Stack.vue +5 -0
  69. package/app/components/Steps.vue +7 -2
  70. package/app/components/Swap.vue +4 -10
  71. package/app/components/Tab.vue +23 -5
  72. package/app/components/Text.vue +47 -23
  73. package/app/components/TextArea.vue +75 -30
  74. package/app/components/TextRotate.vue +24 -0
  75. package/app/components/ThemeController.vue +3 -4
  76. package/app/components/ThemeProvider.vue +47 -32
  77. package/app/components/TimelineLine.vue +1 -1
  78. package/app/components/TimelineStart.vue +2 -1
  79. package/app/components/Toast.vue +3 -8
  80. package/app/components/Toggle.vue +2 -2
  81. package/app/components/Tooltip.vue +111 -21
  82. package/app/components/TooltipContent.vue +279 -1
  83. package/app/components/TooltipTarget.vue +20 -0
  84. package/app/composables/__tests__/use-calendar.test.ts +239 -0
  85. package/app/composables/use-calendar.ts +288 -0
  86. package/app/composables/use-daisy-theme.ts +140 -0
  87. package/app/composables/use-toast.ts +345 -0
  88. package/app/composables/useSearch.ts +22 -0
  89. package/app/utils/drawer-utils.ts +15 -13
  90. package/app/utils/position-area.ts +40 -0
  91. package/nuxt.d.ts +13 -0
  92. package/nuxt.js +12 -9
  93. package/package.json +34 -17
  94. package/app/utils/random-string.ts +0 -19
@@ -1,66 +1,175 @@
1
1
  <script setup lang="ts">
2
- import type { PikadayOptions } from 'pikaday'
3
- import { onMounted, ref } from 'vue'
4
- import { usePikaday } from '~/composables/use-pikaday'
5
- import CalendarSkeleton from './CalendarSkeleton.vue'
2
+ import type { CalendarOptions } from '../composables/use-calendar'
3
+ import { computed, watch } from 'vue'
4
+ import { useCalendar } from '../composables/use-calendar'
6
5
 
7
6
  const props = defineProps<{
8
7
  /** Bound value: Date object or ISO string or null */
9
- modelValue: Date | string | null
10
- /** All Pikaday options (except `container` and `bound`) */
11
- options?: Omit<PikadayOptions, 'container' | 'bound'>
8
+ modelValue?: Date | string | null
9
+ /** Calendar options */
10
+ options?: CalendarOptions
12
11
  /** If true, default to today when no value provided */
13
12
  autoDefault?: boolean
14
13
  }>()
14
+
15
15
  const emit = defineEmits<{
16
16
  (e: 'update:modelValue', v: Date | null): void
17
17
  }>()
18
18
 
19
- const containerRef = ref<HTMLElement | null>(null)
20
- const loading = ref(true)
21
-
22
- const defaultOptions: Partial<PikadayOptions> = {
23
- format: 'D MMM YYYY',
19
+ // Parse initial date from modelValue
20
+ function parseDate(value: Date | string | null | undefined): Date | null {
21
+ if (!value) return null
22
+ if (value instanceof Date) return value
23
+ const d = new Date(value)
24
+ return Number.isNaN(d.getTime()) ? null : d
24
25
  }
25
26
 
26
- function handleSelect(date: Date) {
27
+ const initialDate = computed(() => {
28
+ const parsed = parseDate(props.modelValue)
29
+ if (parsed) return parsed
30
+ if (props.autoDefault) return new Date()
31
+ return null
32
+ })
33
+
34
+ const {
35
+ selectedDate,
36
+ viewMonth,
37
+ viewYear,
38
+ monthName,
39
+ weekdayHeaders,
40
+ weekdayHeadersFull,
41
+ calendarDays,
42
+ prevMonth,
43
+ nextMonth,
44
+ selectDate,
45
+ goToDate,
46
+ } = useCalendar(initialDate.value, props.options)
47
+
48
+ // Sync selectedDate back to modelValue
49
+ watch(selectedDate, date => {
27
50
  emit('update:modelValue', date)
28
- }
51
+ })
29
52
 
30
- const { createPicker } = usePikaday(
31
- {
32
- ...defaultOptions,
33
- ...props.options,
34
- bound: false,
53
+ // Watch for external modelValue changes
54
+ watch(
55
+ () => props.modelValue,
56
+ newValue => {
57
+ const parsed = parseDate(newValue)
58
+ if (parsed && (!selectedDate.value || parsed.getTime() !== selectedDate.value.getTime())) {
59
+ selectDate(parsed)
60
+ goToDate(parsed)
61
+ }
35
62
  },
36
- handleSelect,
37
63
  )
38
64
 
39
- onMounted(async () => {
40
- if (containerRef.value) {
41
- const picker = await createPicker(containerRef.value)
42
- if (picker && picker.el && !containerRef.value.contains(picker.el)) {
43
- containerRef.value.appendChild(picker.el)
44
- }
45
- loading.value = false
46
- }
47
- })
65
+ function handleDayClick(day: (typeof calendarDays.value)[0]) {
66
+ if (day.isDisabled) return
67
+ selectDate(day.date)
68
+ }
48
69
  </script>
49
70
 
50
71
  <template>
51
- <div class="relative w-[270px] h-[270px]">
52
- <div class="absolute inset-0">
53
- <CalendarSkeleton :number-of-months="options?.numberOfMonths ?? 1" :class="loading ? '' : 'opacity-0 pointer-events-none transition-opacity duration-300'" />
72
+ <div class="pika-single">
73
+ <div class="pika-lendar">
74
+ <!-- Header with navigation -->
75
+ <div class="pika-title">
76
+ <div class="pika-label">
77
+ {{ monthName }}
78
+ <select
79
+ class="pika-select pika-select-month"
80
+ :value="viewMonth"
81
+ @change="
82
+ e => {
83
+ const target = e.target as HTMLSelectElement
84
+ const newMonth = parseInt(target.value, 10)
85
+ const d = new Date(viewYear, newMonth, 1)
86
+ goToDate(d)
87
+ }
88
+ "
89
+ >
90
+ <option
91
+ v-for="(m, i) in [
92
+ 'January',
93
+ 'February',
94
+ 'March',
95
+ 'April',
96
+ 'May',
97
+ 'June',
98
+ 'July',
99
+ 'August',
100
+ 'September',
101
+ 'October',
102
+ 'November',
103
+ 'December',
104
+ ]"
105
+ :key="i"
106
+ :value="i"
107
+ >
108
+ {{ m }}
109
+ </option>
110
+ </select>
111
+ </div>
112
+ <div class="pika-label">
113
+ {{ viewYear }}
114
+ <select
115
+ class="pika-select pika-select-year"
116
+ :value="viewYear"
117
+ @change="
118
+ e => {
119
+ const target = e.target as HTMLSelectElement
120
+ const newYear = parseInt(target.value, 10)
121
+ const d = new Date(newYear, viewMonth, 1)
122
+ goToDate(d)
123
+ }
124
+ "
125
+ >
126
+ <option v-for="y in Array.from({ length: 21 }, (_, i) => viewYear - 10 + i)" :key="y" :value="y">
127
+ {{ y }}
128
+ </option>
129
+ </select>
130
+ </div>
131
+ <button type="button" class="pika-prev" @click="prevMonth">Previous Month</button>
132
+ <button type="button" class="pika-next" @click="nextMonth">Next Month</button>
133
+ </div>
134
+
135
+ <!-- Calendar grid -->
136
+ <table class="pika-table" role="grid">
137
+ <thead>
138
+ <tr>
139
+ <th v-for="(day, i) in weekdayHeaders" :key="i" scope="col">
140
+ <abbr :title="weekdayHeadersFull[i]">{{ day }}</abbr>
141
+ </th>
142
+ </tr>
143
+ </thead>
144
+ <tbody>
145
+ <tr v-for="week in 6" :key="week" class="pika-row">
146
+ <td
147
+ v-for="dayIndex in 7"
148
+ :key="dayIndex"
149
+ :class="{
150
+ 'is-today': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isToday,
151
+ 'is-selected': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isSelected,
152
+ 'is-disabled': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isDisabled,
153
+ 'is-outside-current-month': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isOutsideMonth,
154
+ }"
155
+ :aria-selected="calendarDays[(week - 1) * 7 + dayIndex - 1]?.isSelected"
156
+ :data-day="calendarDays[(week - 1) * 7 + dayIndex - 1]?.day"
157
+ >
158
+ <button
159
+ type="button"
160
+ class="pika-button pika-day"
161
+ :disabled="calendarDays[(week - 1) * 7 + dayIndex - 1]?.isDisabled"
162
+ :data-pika-year="calendarDays[(week - 1) * 7 + dayIndex - 1]?.year"
163
+ :data-pika-month="calendarDays[(week - 1) * 7 + dayIndex - 1]?.month"
164
+ :data-pika-day="calendarDays[(week - 1) * 7 + dayIndex - 1]?.day"
165
+ @click="handleDayClick(calendarDays[(week - 1) * 7 + dayIndex - 1]!)"
166
+ >
167
+ {{ calendarDays[(week - 1) * 7 + dayIndex - 1]?.day }}
168
+ </button>
169
+ </td>
170
+ </tr>
171
+ </tbody>
172
+ </table>
54
173
  </div>
55
- <div ref="containerRef" class="absolute inset-0 inline-block w-full h-full" />
56
- <span class="pika-single hidden" />
57
174
  </div>
58
175
  </template>
59
-
60
- <style>
61
- .has-event {
62
- .pika-button {
63
- color: var(--color-primary);
64
- }
65
- }
66
- </style>
@@ -1,15 +1,13 @@
1
1
  <script setup lang="ts">
2
- import type { PikadayOptions } from 'pikaday'
3
- import type { ComponentPublicInstance } from 'vue'
4
- import { onMounted, ref, watch } from 'vue'
5
-
6
- import { usePikaday } from '~/composables/use-pikaday'
2
+ import type { CalendarOptions } from '../composables/use-calendar'
3
+ import { computed, ref, useId, watch } from 'vue'
4
+ import { useCalendar } from '../composables/use-calendar'
7
5
 
8
6
  const props = defineProps<{
9
7
  /** Bound value: Date object or ISO string or null */
10
- modelValue: Date | string | number | null
11
- /** All Pikaday options (except `field`) */
12
- options?: Omit<PikadayOptions, 'field'>
8
+ modelValue?: Date | string | number | null
9
+ /** Calendar options */
10
+ options?: CalendarOptions
13
11
  /** If true, default to today when no value provided */
14
12
  autoDefault?: boolean
15
13
  placeholder?: string
@@ -32,145 +30,246 @@ const props = defineProps<{
32
30
  sm?: boolean
33
31
  xs?: boolean
34
32
  }>()
33
+
35
34
  const emit = defineEmits<{
36
35
  (e: 'update:modelValue', v: Date | null): void
37
36
  (e: 'update:inputValue', v: string | null): void
38
37
  }>()
39
- const inputRef = ref<ComponentPublicInstance | null>(null)
40
- const inputValue = ref<string | null>(null)
41
- const visible = ref(false)
42
38
 
43
- const defaultOptions: Partial<PikadayOptions> = {
44
- format: 'D MMM YYYY',
45
- }
46
-
47
- const { picker, createPicker } = usePikaday(
48
- {
49
- ...defaultOptions,
50
- ...props.options,
51
- bound: true,
52
- field: undefined, // will be set below
53
- },
54
- handleSelect,
55
- handleOpen,
56
- handleClose,
57
- )
39
+ const uniqueId = useId()
40
+ const popoverId = `calendar-popover-${uniqueId}`
41
+ const anchorName = `--calendar-anchor-${uniqueId}`
42
+ const inputRef = ref<HTMLInputElement | null>(null)
43
+ const popoverRef = ref<HTMLElement | null>(null)
58
44
 
59
- function handleSelect(date: Date) {
60
- emit('update:modelValue', date)
61
- inputValue.value = picker.value?.toString() ?? ''
62
- emit('update:inputValue', inputValue.value)
63
- }
64
- function handleOpen() {
65
- visible.value = true
66
- }
67
- function handleClose() {
68
- visible.value = false
45
+ // Parse date from various input types
46
+ function parseDate(value: Date | string | number | null | undefined): Date | null {
47
+ if (!value) return null
48
+ if (value instanceof Date) return value
49
+ const d = new Date(value)
50
+ return Number.isNaN(d.getTime()) ? null : d
69
51
  }
70
52
 
71
- onMounted(async () => {
72
- const inputEl = inputRef.value as HTMLInputElement | null
73
- // Format and set the initial input value BEFORE initializing Pikaday
74
- if (props.modelValue) {
75
- let d: Date | null = null
76
- if (props.modelValue instanceof Date) {
77
- d = props.modelValue
78
- }
79
- else if (typeof props.modelValue === 'string') {
80
- const tmp = new Date(props.modelValue)
81
- if (!Number.isNaN(tmp.getTime())) {
82
- d = tmp
83
- }
84
- }
85
- if (d) {
86
- const day = d.getDate()
87
- const month = d.toLocaleString('en-US', { month: 'short' })
88
- const year = d.getFullYear()
89
- inputValue.value = `${day} ${month} ${year}`
90
- }
91
- }
92
- if (inputEl) {
93
- picker.value = await createPicker(inputEl)
94
- // Do not call setDate here
95
- if (picker.value && (picker.value as any).config?.bound === false) {
96
- picker.value.hide()
97
- }
98
- }
53
+ const initialDate = computed(() => {
54
+ const parsed = parseDate(props.modelValue)
55
+ if (parsed) return parsed
56
+ if (props.autoDefault) return new Date()
57
+ return null
99
58
  })
100
59
 
101
- function onFocus() {
102
- if (picker.value) {
103
- let d: Date | null = null
104
- if (props.modelValue instanceof Date) {
105
- d = props.modelValue
106
- }
107
- else if (typeof props.modelValue === 'string') {
108
- const tmp = new Date(props.modelValue)
109
- if (!Number.isNaN(tmp.getTime())) {
110
- d = tmp
111
- }
112
- }
113
- picker.value.setDate(d, true)
114
- }
115
- }
60
+ const {
61
+ selectedDate,
62
+ viewMonth,
63
+ viewYear,
64
+ monthName,
65
+ weekdayHeaders,
66
+ weekdayHeadersFull,
67
+ calendarDays,
68
+ prevMonth,
69
+ nextMonth,
70
+ selectDate,
71
+ goToDate,
72
+ formatDate,
73
+ } = useCalendar(initialDate.value, props.options)
116
74
 
75
+ // Input display value
76
+ const inputValue = computed(() => formatDate('D MMM YYYY'))
77
+
78
+ // Sync selectedDate back to modelValue
79
+ watch(selectedDate, date => {
80
+ emit('update:modelValue', date)
81
+ emit('update:inputValue', formatDate('D MMM YYYY'))
82
+ })
83
+
84
+ // Watch for external modelValue changes
117
85
  watch(
118
86
  () => props.modelValue,
119
- (val) => {
120
- if (!picker.value) {
121
- return
122
- }
123
- if (!visible.value) {
124
- if (val instanceof Date) {
125
- picker.value.setDate(val, true)
126
- inputValue.value = picker.value.toString()
127
- }
128
- else if (typeof val === 'string') {
129
- const d = new Date(val)
130
- if (!Number.isNaN(d.getTime())) {
131
- picker.value.setDate(d, true)
132
- inputValue.value = picker.value.toString()
133
- }
134
- }
135
- else {
136
- picker.value.setDate(null, true)
137
- inputValue.value = ''
138
- }
87
+ newValue => {
88
+ const parsed = parseDate(newValue)
89
+ if (parsed && (!selectedDate.value || parsed.getTime() !== selectedDate.value.getTime())) {
90
+ selectDate(parsed)
91
+ goToDate(parsed)
92
+ } else if (!newValue && selectedDate.value) {
93
+ selectedDate.value = null
139
94
  }
140
95
  },
141
96
  )
97
+
98
+ function handleDayClick(day: (typeof calendarDays.value)[0]) {
99
+ if (day.isDisabled) return
100
+ selectDate(day.date)
101
+ popoverRef.value?.hidePopover()
102
+ }
103
+
104
+ function handleClick() {
105
+ // Sync view to selected date when opening
106
+ if (selectedDate.value) {
107
+ goToDate(selectedDate.value)
108
+ }
109
+ popoverRef.value?.togglePopover()
110
+ }
142
111
  </script>
143
112
 
144
113
  <template>
145
- <input
146
- ref="inputRef"
147
- type="text"
148
- :value="inputValue"
149
- :placeholder="props.placeholder"
150
- :disabled="props.disabled"
151
- class="input"
152
- :class="[
153
- { validator: props.validator },
154
- { 'input-primary': props.primary || props.color === 'primary' },
155
- { 'input-secondary': props.secondary || props.color === 'secondary' },
156
- { 'input-accent': props.accent || props.color === 'accent' },
157
- { 'input-info': props.info || props.color === 'info' },
158
- { 'input-success': props.success || props.color === 'success' },
159
- { 'input-warning': props.warning || props.color === 'warning' },
160
- { 'input-error': props.error || props.color === 'error' },
161
- { 'input-ghost': props.ghost },
162
- { 'input-xl': props.xl || props.size === 'xl' },
163
- { 'input-lg': props.lg || props.size === 'lg' },
164
- { 'input-md': props.md || props.size === 'md' },
165
- { 'input-sm': props.sm || props.size === 'sm' },
166
- { 'input-xs': props.xs || props.size === 'xs' },
167
- { 'join-item': props.join },
168
- ]"
169
- v-bind="$attrs"
170
- @focus="onFocus"
171
- @input="e => {
172
- const val = (e.target as HTMLInputElement).value
173
- emit('update:inputValue', val)
174
- }"
175
- >
114
+ <div class="relative inline-block">
115
+ <input
116
+ ref="inputRef"
117
+ type="text"
118
+ readonly
119
+ :value="inputValue"
120
+ :placeholder="props.placeholder"
121
+ :disabled="props.disabled"
122
+ class="input cursor-pointer"
123
+ :class="[
124
+ { validator: props.validator },
125
+ { 'input-primary': props.primary || props.color === 'primary' },
126
+ { 'input-secondary': props.secondary || props.color === 'secondary' },
127
+ { 'input-accent': props.accent || props.color === 'accent' },
128
+ { 'input-info': props.info || props.color === 'info' },
129
+ { 'input-success': props.success || props.color === 'success' },
130
+ { 'input-warning': props.warning || props.color === 'warning' },
131
+ { 'input-error': props.error || props.color === 'error' },
132
+ { 'input-ghost': props.ghost },
133
+ { 'input-xl': props.xl || props.size === 'xl' },
134
+ { 'input-lg': props.lg || props.size === 'lg' },
135
+ { 'input-md': props.md || props.size === 'md' },
136
+ { 'input-sm': props.sm || props.size === 'sm' },
137
+ { 'input-xs': props.xs || props.size === 'xs' },
138
+ { 'join-item': props.join },
139
+ ]"
140
+ :style="{ 'anchor-name': anchorName } as any"
141
+ v-bind="$attrs"
142
+ @click="handleClick"
143
+ />
144
+
145
+ <!-- Dropdown calendar using Popover API -->
146
+ <div
147
+ :id="popoverId"
148
+ ref="popoverRef"
149
+ popover="auto"
150
+ class="pika-single calendar-popover"
151
+ :style="{ 'position-anchor': anchorName } as any"
152
+ >
153
+ <div class="pika-lendar">
154
+ <!-- Header with navigation -->
155
+ <div class="pika-title">
156
+ <div class="pika-label">
157
+ {{ monthName }}
158
+ <select
159
+ class="pika-select pika-select-month"
160
+ :value="viewMonth"
161
+ @change="
162
+ e => {
163
+ const target = e.target as HTMLSelectElement
164
+ const newMonth = parseInt(target.value, 10)
165
+ const d = new Date(viewYear, newMonth, 1)
166
+ goToDate(d)
167
+ }
168
+ "
169
+ >
170
+ <option
171
+ v-for="(m, i) in [
172
+ 'January',
173
+ 'February',
174
+ 'March',
175
+ 'April',
176
+ 'May',
177
+ 'June',
178
+ 'July',
179
+ 'August',
180
+ 'September',
181
+ 'October',
182
+ 'November',
183
+ 'December',
184
+ ]"
185
+ :key="i"
186
+ :value="i"
187
+ >
188
+ {{ m }}
189
+ </option>
190
+ </select>
191
+ </div>
192
+ <div class="pika-label">
193
+ {{ viewYear }}
194
+ <select
195
+ class="pika-select pika-select-year"
196
+ :value="viewYear"
197
+ @change="
198
+ e => {
199
+ const target = e.target as HTMLSelectElement
200
+ const newYear = parseInt(target.value, 10)
201
+ const d = new Date(newYear, viewMonth, 1)
202
+ goToDate(d)
203
+ }
204
+ "
205
+ >
206
+ <option v-for="y in Array.from({ length: 21 }, (_, i) => viewYear - 10 + i)" :key="y" :value="y">
207
+ {{ y }}
208
+ </option>
209
+ </select>
210
+ </div>
211
+ <button type="button" class="pika-prev" @click="prevMonth">Previous Month</button>
212
+ <button type="button" class="pika-next" @click="nextMonth">Next Month</button>
213
+ </div>
214
+
215
+ <!-- Calendar grid -->
216
+ <table class="pika-table" role="grid">
217
+ <thead>
218
+ <tr>
219
+ <th v-for="(day, i) in weekdayHeaders" :key="i" scope="col">
220
+ <abbr :title="weekdayHeadersFull[i]">{{ day }}</abbr>
221
+ </th>
222
+ </tr>
223
+ </thead>
224
+ <tbody>
225
+ <tr v-for="week in 6" :key="week" class="pika-row">
226
+ <td
227
+ v-for="dayIndex in 7"
228
+ :key="dayIndex"
229
+ :class="{
230
+ 'is-today': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isToday,
231
+ 'is-selected': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isSelected,
232
+ 'is-disabled': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isDisabled,
233
+ 'is-outside-current-month': calendarDays[(week - 1) * 7 + dayIndex - 1]?.isOutsideMonth,
234
+ }"
235
+ :aria-selected="calendarDays[(week - 1) * 7 + dayIndex - 1]?.isSelected"
236
+ >
237
+ <button
238
+ type="button"
239
+ class="pika-button pika-day"
240
+ :disabled="calendarDays[(week - 1) * 7 + dayIndex - 1]?.isDisabled"
241
+ @click="handleDayClick(calendarDays[(week - 1) * 7 + dayIndex - 1]!)"
242
+ >
243
+ {{ calendarDays[(week - 1) * 7 + dayIndex - 1]?.day }}
244
+ </button>
245
+ </td>
246
+ </tr>
247
+ </tbody>
248
+ </table>
249
+ </div>
250
+ </div>
251
+ </div>
176
252
  </template>
253
+
254
+ <style>
255
+ .calendar-popover[popover] {
256
+ position-area: block-end span-inline-end;
257
+ position-try-fallbacks:
258
+ flip-block,
259
+ flip-inline,
260
+ flip-block flip-inline;
261
+ margin: 0;
262
+ margin-top: 0.25rem;
263
+ /* Reset default popover styles */
264
+ border: none;
265
+ inset: auto;
266
+ }
267
+
268
+ .calendar-popover[popover]:popover-open {
269
+ position: fixed;
270
+ }
271
+
272
+ .calendar-popover[popover]:not(:popover-open) {
273
+ display: none;
274
+ }
275
+ </style>