daisy-ui-kit 5.0.0-pre.21 → 5.0.0-pre.25
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.
- package/app/components/Calendar.vue +149 -63
- package/app/components/CalendarInput.vue +229 -128
- package/app/components/Toast.vue +2 -2
- package/app/composables/__tests__/use-calendar.test.ts +239 -0
- package/app/composables/use-calendar.ts +288 -0
- package/app/composables/use-daisy-theme.ts +131 -0
- package/app/composables/use-toast.ts +345 -0
- package/app/composables/useSearch.ts +22 -0
- package/package.json +8 -4
|
@@ -1,89 +1,175 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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
|
|
10
|
-
/**
|
|
11
|
-
options?:
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
if (props.
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
// Try to parse modelValue if it's a string
|
|
33
|
-
if (typeof props.modelValue === 'string') {
|
|
34
|
-
return new Date(props.modelValue)
|
|
35
|
-
}
|
|
36
|
-
// Use defaultDate from options if provided
|
|
37
|
-
if (props.options?.defaultDate instanceof Date) {
|
|
38
|
-
return props.options.defaultDate
|
|
39
|
-
}
|
|
40
|
-
// Fall back to today
|
|
41
|
-
return new 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
|
|
42
32
|
})
|
|
43
33
|
|
|
44
|
-
|
|
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 => {
|
|
45
50
|
emit('update:modelValue', date)
|
|
46
|
-
}
|
|
51
|
+
})
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
}
|
|
53
62
|
},
|
|
54
|
-
handleSelect,
|
|
55
63
|
)
|
|
56
64
|
|
|
57
|
-
|
|
58
|
-
if (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
containerRef.value.appendChild(picker.el)
|
|
62
|
-
}
|
|
63
|
-
loading.value = false
|
|
64
|
-
}
|
|
65
|
-
})
|
|
65
|
+
function handleDayClick(day: (typeof calendarDays.value)[0]) {
|
|
66
|
+
if (day.isDisabled) return
|
|
67
|
+
selectDate(day.date)
|
|
68
|
+
}
|
|
66
69
|
</script>
|
|
67
70
|
|
|
68
71
|
<template>
|
|
69
|
-
<div class="
|
|
70
|
-
<div class="
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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>
|
|
77
173
|
</div>
|
|
78
|
-
<div ref="containerRef" class="absolute inset-0 inline-block w-full h-full" />
|
|
79
|
-
<span class="pika-single hidden" />
|
|
80
174
|
</div>
|
|
81
175
|
</template>
|
|
82
|
-
|
|
83
|
-
<style>
|
|
84
|
-
.has-event {
|
|
85
|
-
.pika-button {
|
|
86
|
-
color: var(--color-primary);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
</style>
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { usePikaday } from '~/composables/use-pikaday'
|
|
2
|
+
import type { CalendarOptions } from '../composables/use-calendar'
|
|
3
|
+
import { computed, ref, watch } from 'vue'
|
|
4
|
+
import { useCalendar } from '../composables/use-calendar'
|
|
5
|
+
import { randomString } from '../utils/random-string'
|
|
7
6
|
|
|
8
7
|
const props = defineProps<{
|
|
9
8
|
/** Bound value: Date object or ISO string or null */
|
|
10
|
-
modelValue
|
|
11
|
-
/**
|
|
12
|
-
options?:
|
|
9
|
+
modelValue?: Date | string | number | null
|
|
10
|
+
/** Calendar options */
|
|
11
|
+
options?: CalendarOptions
|
|
13
12
|
/** If true, default to today when no value provided */
|
|
14
13
|
autoDefault?: boolean
|
|
15
14
|
placeholder?: string
|
|
@@ -32,143 +31,245 @@ const props = defineProps<{
|
|
|
32
31
|
sm?: boolean
|
|
33
32
|
xs?: boolean
|
|
34
33
|
}>()
|
|
34
|
+
|
|
35
35
|
const emit = defineEmits<{
|
|
36
36
|
(e: 'update:modelValue', v: Date | null): void
|
|
37
37
|
(e: 'update:inputValue', v: string | null): void
|
|
38
38
|
}>()
|
|
39
|
-
const inputRef = ref<ComponentPublicInstance | null>(null)
|
|
40
|
-
const inputValue = ref<string | null>(null)
|
|
41
|
-
const visible = ref(false)
|
|
42
39
|
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
const popoverId = `calendar-popover-${randomString()}`
|
|
41
|
+
const anchorName = `--calendar-anchor-${randomString()}`
|
|
42
|
+
const inputRef = ref<HTMLInputElement | null>(null)
|
|
43
|
+
const popoverRef = ref<HTMLElement | null>(null)
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
},
|
|
54
|
-
handleSelect,
|
|
55
|
-
handleOpen,
|
|
56
|
-
handleClose,
|
|
57
|
-
)
|
|
58
|
-
|
|
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
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
if (props.
|
|
75
|
-
|
|
76
|
-
if (props.modelValue instanceof Date) {
|
|
77
|
-
d = props.modelValue
|
|
78
|
-
} else if (typeof props.modelValue === 'string') {
|
|
79
|
-
const tmp = new Date(props.modelValue)
|
|
80
|
-
if (!Number.isNaN(tmp.getTime())) {
|
|
81
|
-
d = tmp
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (d) {
|
|
85
|
-
const day = d.getDate()
|
|
86
|
-
const month = d.toLocaleString('en-US', { month: 'short' })
|
|
87
|
-
const year = d.getFullYear()
|
|
88
|
-
inputValue.value = `${day} ${month} ${year}`
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (inputEl) {
|
|
92
|
-
picker.value = await createPicker(inputEl)
|
|
93
|
-
// Do not call setDate here
|
|
94
|
-
if (picker.value && (picker.value as any).config?.bound === false) {
|
|
95
|
-
picker.value.hide()
|
|
96
|
-
}
|
|
97
|
-
}
|
|
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
|
|
98
58
|
})
|
|
99
59
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
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)
|
|
74
|
+
|
|
75
|
+
// Input display value
|
|
76
|
+
const inputValue = computed(() => formatDate('D MMM YYYY'))
|
|
114
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
|
|
115
85
|
watch(
|
|
116
86
|
() => props.modelValue,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
inputValue.value = picker.value.toString()
|
|
125
|
-
} else if (typeof val === 'string') {
|
|
126
|
-
const d = new Date(val)
|
|
127
|
-
if (!Number.isNaN(d.getTime())) {
|
|
128
|
-
picker.value.setDate(d, true)
|
|
129
|
-
inputValue.value = picker.value.toString()
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
picker.value.setDate(null, true)
|
|
133
|
-
inputValue.value = ''
|
|
134
|
-
}
|
|
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
|
|
135
94
|
}
|
|
136
95
|
},
|
|
137
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
|
+
}
|
|
138
111
|
</script>
|
|
139
112
|
|
|
140
113
|
<template>
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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>
|
|
174
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>
|
package/app/components/Toast.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { Toast } from '
|
|
2
|
+
import type { Toast } from '../composables/use-toast'
|
|
3
3
|
import { computed } from 'vue'
|
|
4
|
-
import { useToast } from '
|
|
4
|
+
import { useToast } from '../composables/use-toast'
|
|
5
5
|
|
|
6
6
|
// Explicit slot typing (Vue 3.4+ / Volar)
|
|
7
7
|
interface ToastSlotProps {
|