@stonecrop/aform 0.31.0 → 0.33.0
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/dist/aform.js +3024 -2256
- package/dist/aform.js.map +1 -1
- package/dist/assets/index.css +1170 -1
- package/dist/src/components/AForm.vue.d.ts +20 -0
- package/dist/src/components/AForm.vue.d.ts.map +1 -0
- package/dist/src/components/AFormLoading.vue.d.ts +14 -0
- package/dist/src/components/AFormLoading.vue.d.ts.map +1 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts +7 -0
- package/dist/src/components/base/CollapseButton.vue.d.ts.map +1 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts +7 -0
- package/dist/src/components/base/ExpandButton.vue.d.ts.map +1 -0
- package/dist/src/components/form/ABadge.vue.d.ts +14 -0
- package/dist/src/components/form/ABadge.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts +15 -0
- package/dist/src/components/form/ACheckbox.vue.d.ts.map +1 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts +20 -0
- package/dist/src/components/form/ACurrencyInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADate.vue.d.ts +14 -0
- package/dist/src/components/form/ADate.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts +28 -0
- package/dist/src/components/form/ADatePicker.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateRange.vue.d.ts +18 -0
- package/dist/src/components/form/ADateRange.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts +52 -0
- package/dist/src/components/form/ADateSelection.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTime.vue.d.ts +17 -0
- package/dist/src/components/form/ADateTime.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts +30 -0
- package/dist/src/components/form/ADateTimeInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADropdown.vue.d.ts +20 -0
- package/dist/src/components/form/ADropdown.vue.d.ts.map +1 -0
- package/dist/src/components/form/ADuration.vue.d.ts +18 -0
- package/dist/src/components/form/ADuration.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFieldset.vue.d.ts +28 -0
- package/dist/src/components/form/AFieldset.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts +5 -0
- package/dist/src/components/form/AFileAttach.vue.d.ts.map +1 -0
- package/dist/src/components/form/AFormLink.vue.d.ts +40 -0
- package/dist/src/components/form/AFormLink.vue.d.ts.map +1 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts +14 -0
- package/dist/src/components/form/ANumericInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts +20 -0
- package/dist/src/components/form/AQuantityInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts +23 -0
- package/dist/src/components/form/ASegmentedControl.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextInput.vue.d.ts +14 -0
- package/dist/src/components/form/ATextInput.vue.d.ts.map +1 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts +21 -0
- package/dist/src/components/form/ATextboxInput.vue.d.ts.map +1 -0
- package/dist/src/components/utilities/Login.vue.d.ts +14 -0
- package/dist/src/components/utilities/Login.vue.d.ts.map +1 -0
- package/dist/{tsdoc-metadata.json → src/tsdoc-metadata.json} +1 -1
- package/package.json +35 -30
- package/dist/aform.d.ts +0 -441
- package/dist/aform.tsbuildinfo +0 -1
- package/dist/src/directives/mask.js +0 -107
- package/dist/src/index.js +0 -58
- package/dist/src/types/index.js +0 -0
- package/dist/src/utils/badge.js +0 -71
- package/dist/src/utils/columns.js +0 -33
- package/dist/src/utils/deserialize.js +0 -20
- package/src/components/AForm.vue +0 -314
- package/src/components/AFormLoading.vue +0 -76
- package/src/components/base/CollapseButton.vue +0 -36
- package/src/components/base/ExpandButton.vue +0 -34
- package/src/components/form/ABadge.vue +0 -135
- package/src/components/form/ACheckbox.vue +0 -62
- package/src/components/form/ACurrencyInput.vue +0 -357
- package/src/components/form/ADate.vue +0 -93
- package/src/components/form/ADatePicker.vue +0 -470
- package/src/components/form/ADateRange.vue +0 -133
- package/src/components/form/ADateSelection.vue +0 -174
- package/src/components/form/ADateTime.vue +0 -149
- package/src/components/form/ADateTimeInput.vue +0 -299
- package/src/components/form/ADropdown.vue +0 -222
- package/src/components/form/ADuration.vue +0 -124
- package/src/components/form/AFieldset.vue +0 -72
- package/src/components/form/AFileAttach.vue +0 -97
- package/src/components/form/AFormLink.vue +0 -368
- package/src/components/form/ANumericInput.vue +0 -32
- package/src/components/form/AQuantityInput.vue +0 -349
- package/src/components/form/ASegmentedControl.vue +0 -342
- package/src/components/form/ATextInput.vue +0 -40
- package/src/components/form/ATextboxInput.vue +0 -66
- package/src/components/utilities/Login.vue +0 -189
- package/src/directives/mask.ts +0 -122
- package/src/index.ts +0 -92
- package/src/shims-vue.d.ts +0 -5
- package/src/types/index.ts +0 -333
- package/src/utils/badge.ts +0 -94
- package/src/utils/columns.ts +0 -38
- package/src/utils/deserialize.ts +0 -20
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<template v-if="mode === 'display' || mode === 'read'">
|
|
3
|
-
<span class="aform_display-value">{{ date ? new Date(date).toLocaleDateString() : '' }}</span>
|
|
4
|
-
<label v-if="label">{{ label }}</label>
|
|
5
|
-
<p v-show="errorText" class="aform_error" v-html="errorText"></p>
|
|
6
|
-
</template>
|
|
7
|
-
<template v-else>
|
|
8
|
-
<div ref="datepicker" class="adatepicker" tabindex="0">
|
|
9
|
-
<table @mousedown="preventCellSelection">
|
|
10
|
-
<tbody>
|
|
11
|
-
<tr>
|
|
12
|
-
<td id="previous-month-btn" :tabindex="-1" @click="previousMonth"><</td>
|
|
13
|
-
<th colspan="5" :tabindex="-1">{{ monthAndYear }}</th>
|
|
14
|
-
<td id="next-month-btn" :tabindex="-1" @click="nextMonth">></td>
|
|
15
|
-
</tr>
|
|
16
|
-
<tr v-if="selectRange">
|
|
17
|
-
<td colspan="7">
|
|
18
|
-
<div class="date-input">
|
|
19
|
-
<input
|
|
20
|
-
ref="start-date-input"
|
|
21
|
-
:value="getStartDate"
|
|
22
|
-
class="date-input-start aform_input-field"
|
|
23
|
-
type="text"
|
|
24
|
-
placeholder="start date"
|
|
25
|
-
@blur="enterInputDate()"
|
|
26
|
-
@keydown="enterDate" />
|
|
27
|
-
<div>-</div>
|
|
28
|
-
<input
|
|
29
|
-
ref="end-date-input"
|
|
30
|
-
:value="getEndDate"
|
|
31
|
-
class="date-input-end aform_input-field"
|
|
32
|
-
type="text"
|
|
33
|
-
placeholder="end date"
|
|
34
|
-
@blur="enterInputDate()"
|
|
35
|
-
@keydown="enterDate" />
|
|
36
|
-
</div>
|
|
37
|
-
<!-- {{ formattedDateRange }} -->
|
|
38
|
-
</td>
|
|
39
|
-
</tr>
|
|
40
|
-
<tr class="days-header">
|
|
41
|
-
<td>M</td>
|
|
42
|
-
<td>T</td>
|
|
43
|
-
<td>W</td>
|
|
44
|
-
<td>T</td>
|
|
45
|
-
<td>F</td>
|
|
46
|
-
<td>S</td>
|
|
47
|
-
<td>S</td>
|
|
48
|
-
</tr>
|
|
49
|
-
<tr v-for="rowNo in numberOfRows" :key="rowNo">
|
|
50
|
-
<!-- the 'ref' key is currently only used for test references -->
|
|
51
|
-
<td
|
|
52
|
-
v-for="colNo in numberOfColumns"
|
|
53
|
-
ref="celldate"
|
|
54
|
-
:key="getCurrentCell(rowNo, colNo)"
|
|
55
|
-
class="date-cell"
|
|
56
|
-
:contenteditable="false"
|
|
57
|
-
:spellcheck="false"
|
|
58
|
-
:tabindex="0"
|
|
59
|
-
:class="{
|
|
60
|
-
todaysDate: isTodaysDate(getCurrentDate(rowNo, colNo)),
|
|
61
|
-
selectedDate: isSelectedDate(getCurrentDate(rowNo, colNo)),
|
|
62
|
-
withinRange: selectRange ? isInDateRange(getCurrentDate(rowNo, colNo)) : false,
|
|
63
|
-
startDate: selectRange ? isStartDate(getCurrentDate(rowNo, colNo)) : false,
|
|
64
|
-
endDate: selectRange ? isEndDate(getCurrentDate(rowNo, colNo)) : false,
|
|
65
|
-
}"
|
|
66
|
-
@click.prevent.stop="selectDate(getCurrentCell(rowNo, colNo))"
|
|
67
|
-
@keydown.enter="selectDate(getCurrentCell(rowNo, colNo))"
|
|
68
|
-
@mouseover="hoverDate(getCurrentCell(rowNo, colNo))">
|
|
69
|
-
{{ new Date(getCurrentDate(rowNo, colNo)).getDate() }}
|
|
70
|
-
</td>
|
|
71
|
-
</tr>
|
|
72
|
-
</tbody>
|
|
73
|
-
</table>
|
|
74
|
-
</div>
|
|
75
|
-
<p v-show="errorText" class="aform_error" v-html="errorText"></p>
|
|
76
|
-
</template>
|
|
77
|
-
</template>
|
|
78
|
-
|
|
79
|
-
<script setup lang="ts">
|
|
80
|
-
/* removed keyboard nav temportarily since it interfered with user experience navigating input fields */
|
|
81
|
-
// import { defaultKeypressHandlers, useKeyboardNav } from '@stonecrop/utilities'
|
|
82
|
-
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue'
|
|
83
|
-
|
|
84
|
-
import type { ComponentProps } from '../../types'
|
|
85
|
-
|
|
86
|
-
const numberOfRows = 6
|
|
87
|
-
const numberOfColumns = 7
|
|
88
|
-
|
|
89
|
-
const { mode, label, selectRange = false, errors, validation = { errorMessage: '' } } = defineProps<ComponentProps>()
|
|
90
|
-
|
|
91
|
-
// Dynamic trigger errors take precedence over a static schema errorMessage; empty means the slot hides.
|
|
92
|
-
const errorText = computed(() => (errors?.length ? errors.join('; ') : (validation.errorMessage ?? '')))
|
|
93
|
-
|
|
94
|
-
const date = defineModel<number | Date>({ default: () => new Date() })
|
|
95
|
-
const selectedDate = ref(new Date(date.value))
|
|
96
|
-
const currentMonth = ref<number>(selectedDate.value.getMonth())
|
|
97
|
-
const currentYear = ref<number>(selectedDate.value.getFullYear())
|
|
98
|
-
const currentDates = ref<number[]>([])
|
|
99
|
-
|
|
100
|
-
/* needed for keyboard navigation. uncomment if implementing */
|
|
101
|
-
// const datepickerRef = useTemplateRef<HTMLDivElement>('datepicker')
|
|
102
|
-
|
|
103
|
-
const hoveredDate = ref(new Date(date.value))
|
|
104
|
-
const start_date = ref<Date | null>(null)
|
|
105
|
-
const end_date = ref<Date | null>(null)
|
|
106
|
-
const startDateInput = useTemplateRef<HTMLInputElement>('start-date-input')
|
|
107
|
-
const endDateInput = useTemplateRef<HTMLInputElement>('end-date-input')
|
|
108
|
-
|
|
109
|
-
/*******************
|
|
110
|
-
Emits
|
|
111
|
-
*******************/
|
|
112
|
-
|
|
113
|
-
const emit = defineEmits<{
|
|
114
|
-
'get-date': [{ start: Date | null; end: Date | null; selected: Date }]
|
|
115
|
-
}>()
|
|
116
|
-
|
|
117
|
-
/*******************
|
|
118
|
-
Computed
|
|
119
|
-
*******************/
|
|
120
|
-
|
|
121
|
-
const monthAndYear = computed(() => {
|
|
122
|
-
return new Date(currentYear.value, currentMonth.value, 1).toLocaleDateString(undefined, {
|
|
123
|
-
year: 'numeric',
|
|
124
|
-
month: 'long',
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
const getStartDate = computed(() => {
|
|
129
|
-
return start_date.value != null ? parseDateToString(start_date.value) : ''
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
const getEndDate = computed(() => {
|
|
133
|
-
return end_date.value != null ? parseDateToString(end_date.value) : ''
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
/*******************
|
|
137
|
-
Functions
|
|
138
|
-
*******************/
|
|
139
|
-
|
|
140
|
-
const parseDateToString = (dateValue: Date | null) => {
|
|
141
|
-
if (!validateDate(dateValue)) return ''
|
|
142
|
-
return dateValue.getMonth() + 1 + '/' + dateValue.getDate() + '/' + dateValue.getFullYear()
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const isTodaysDate = (day: string | number | Date): boolean => {
|
|
146
|
-
const todaysDate = new Date()
|
|
147
|
-
if (currentMonth.value !== todaysDate.getMonth()) return false
|
|
148
|
-
return todaysDate.toDateString() === new Date(day).toDateString()
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const isSelectedDate = (day: string | number | Date) => {
|
|
152
|
-
return new Date(day).toDateString() === new Date(selectedDate.value).toDateString()
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const isStartDate = (day: string | number | Date) => {
|
|
156
|
-
const start = start_date.value
|
|
157
|
-
if (!validateDate(start)) return false
|
|
158
|
-
return new Date(day).toDateString() === start.toDateString()
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const isEndDate = (day: string | number | Date) => {
|
|
162
|
-
const end = end_date.value
|
|
163
|
-
if (!validateDate(end)) return false
|
|
164
|
-
return new Date(day).toDateString() === end.toDateString()
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const getCurrentCell = (rowNo: number, colNo: number) => {
|
|
168
|
-
return (rowNo - 1) * numberOfColumns + colNo
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const isInDateRange = (day: string | number | Date) => {
|
|
172
|
-
const start = start_date.value
|
|
173
|
-
if (!validateDate(start)) return false
|
|
174
|
-
const this_date = new Date(day)
|
|
175
|
-
|
|
176
|
-
//the end is either the selected end date or wherever the user is hovering
|
|
177
|
-
const end = end_date.value
|
|
178
|
-
const temp_end_date = validateDate(end) ? end : new Date(hoveredDate.value)
|
|
179
|
-
|
|
180
|
-
return this_date.getTime() > start.getTime() && this_date.getTime() < temp_end_date.getTime()
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const getCurrentDate = (rowNo: number, colNo: number) => {
|
|
184
|
-
return currentDates.value[getCurrentCell(rowNo, colNo)]
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const hoverDate = (currentIndex: number) => {
|
|
188
|
-
hoveredDate.value = new Date(currentDates.value[currentIndex])
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// browsers (notably Firefox) allow drag-selecting text across table cells even with
|
|
192
|
-
// `user-select: none` on the cells; blocking mousedown is the reliable cross-browser fix.
|
|
193
|
-
// the start/end-date inputs must keep native mousedown behavior so they stay focusable/typable.
|
|
194
|
-
const preventCellSelection = (event: MouseEvent) => {
|
|
195
|
-
if ((event.target as HTMLElement)?.tagName !== 'INPUT') {
|
|
196
|
-
event.preventDefault()
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const populateMonth = () => {
|
|
201
|
-
currentDates.value = []
|
|
202
|
-
const firstOfMonth = new Date(currentYear.value, currentMonth.value, 1)
|
|
203
|
-
const monthStartWeekday = firstOfMonth.getDay()
|
|
204
|
-
const calendarStartDay = firstOfMonth.setDate(firstOfMonth.getDate() - monthStartWeekday)
|
|
205
|
-
|
|
206
|
-
// assume midnight for all dates while building the calendar
|
|
207
|
-
for (const dayIndex of Array(43).keys()) {
|
|
208
|
-
currentDates.value.push(calendarStartDay + dayIndex * 86400000)
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
const previousYear = () => (currentYear.value -= 1)
|
|
212
|
-
const nextYear = () => (currentYear.value += 1)
|
|
213
|
-
|
|
214
|
-
const previousMonth = () => {
|
|
215
|
-
if (currentMonth.value == 0) {
|
|
216
|
-
currentMonth.value = 11
|
|
217
|
-
previousYear()
|
|
218
|
-
} else {
|
|
219
|
-
currentMonth.value -= 1
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const nextMonth = () => {
|
|
224
|
-
if (currentMonth.value == 11) {
|
|
225
|
-
currentMonth.value = 0
|
|
226
|
-
nextYear()
|
|
227
|
-
} else {
|
|
228
|
-
currentMonth.value += 1
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const enterDate = (event: KeyboardEvent) => {
|
|
233
|
-
if (event.key === 'Enter') enterInputDate()
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// useKeyboardNav([
|
|
237
|
-
// {
|
|
238
|
-
// parent: datepickerRef,
|
|
239
|
-
// selectors: 'td',
|
|
240
|
-
// handlers: {
|
|
241
|
-
// ...defaultKeypressHandlers,
|
|
242
|
-
// ...{
|
|
243
|
-
// 'keydown.pageup': previousMonth,
|
|
244
|
-
// 'keydown.shift.pageup': previousYear,
|
|
245
|
-
// 'keydown.pagedown': nextMonth,
|
|
246
|
-
// 'keydown.shift.pagedown': nextYear,
|
|
247
|
-
// // TODO: this is a hack to override the stonecrop enter handler;
|
|
248
|
-
// // store context inside the component so that handlers can be setup consistently
|
|
249
|
-
// // eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
250
|
-
// 'keydown.enter': () => {}, // select this date
|
|
251
|
-
// },
|
|
252
|
-
// },
|
|
253
|
-
// },
|
|
254
|
-
// ])
|
|
255
|
-
|
|
256
|
-
const selectDate = (currentIndex: number) => {
|
|
257
|
-
date.value = selectedDate.value = new Date(currentDates.value[currentIndex])
|
|
258
|
-
|
|
259
|
-
if (selectRange) {
|
|
260
|
-
const start = start_date.value
|
|
261
|
-
if (start == null || end_date.value != null) {
|
|
262
|
-
start_date.value = date.value
|
|
263
|
-
end_date.value = null
|
|
264
|
-
} else if (validateDate(start) && selectedDate.value.getTime() < start.getTime()) {
|
|
265
|
-
end_date.value = null
|
|
266
|
-
start_date.value = date.value
|
|
267
|
-
} else {
|
|
268
|
-
end_date.value = date.value
|
|
269
|
-
}
|
|
270
|
-
if (startDateInput.value) startDateInput.value.value = parseDateToString(start_date.value) ?? ''
|
|
271
|
-
if (endDateInput.value) endDateInput.value.value = parseDateToString(end_date.value) ?? ''
|
|
272
|
-
}
|
|
273
|
-
emitData()
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
const testDateOrder = () => {
|
|
277
|
-
const start = start_date.value
|
|
278
|
-
const end = end_date.value
|
|
279
|
-
if (validateDate(end) && validateDate(start) && end.getTime() < start.getTime())
|
|
280
|
-
[start_date.value, end_date.value] = [end, start]
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const validateDate = (dateValue: unknown): dateValue is Date => {
|
|
284
|
-
return dateValue instanceof Date && !isNaN(dateValue.getTime())
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const enterInputDate = () => {
|
|
288
|
-
if (startDateInput.value?.value == '') {
|
|
289
|
-
start_date.value = null
|
|
290
|
-
} else if (startDateInput.value) {
|
|
291
|
-
const start = new Date(startDateInput.value.value)
|
|
292
|
-
start_date.value = validateDate(start) ? start : null
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (endDateInput.value?.value == '') {
|
|
296
|
-
end_date.value = null
|
|
297
|
-
} else if (endDateInput.value) {
|
|
298
|
-
const end = new Date(endDateInput.value.value)
|
|
299
|
-
end_date.value = validateDate(end) ? end : null
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
if (validateDate(start_date.value)) {
|
|
303
|
-
if (validateDate(end_date.value)) testDateOrder()
|
|
304
|
-
selectedDate.value = start_date.value
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
emitData()
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const emitData = () => {
|
|
311
|
-
emit('get-date', {
|
|
312
|
-
start: selectRange ? start_date.value : null,
|
|
313
|
-
end: selectRange ? end_date.value : null,
|
|
314
|
-
selected: selectedDate.value,
|
|
315
|
-
})
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/*******************
|
|
319
|
-
Hooks
|
|
320
|
-
*******************/
|
|
321
|
-
|
|
322
|
-
onMounted(async () => {
|
|
323
|
-
populateMonth()
|
|
324
|
-
// required to allow the elements to be focused in the next step
|
|
325
|
-
await nextTick()
|
|
326
|
-
const $selectedDate = document.getElementsByClassName('selectedDate')
|
|
327
|
-
if ($selectedDate.length > 0) {
|
|
328
|
-
;($selectedDate[0] as HTMLElement).focus()
|
|
329
|
-
} else {
|
|
330
|
-
const $todaysDate = document.getElementsByClassName('todaysDate')
|
|
331
|
-
if ($todaysDate.length > 0) {
|
|
332
|
-
;($todaysDate[0] as HTMLElement).focus()
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
})
|
|
336
|
-
|
|
337
|
-
// setup keyboard navigation
|
|
338
|
-
// useKeyboardNav([
|
|
339
|
-
// {
|
|
340
|
-
// parent: datepickerRef,
|
|
341
|
-
// selectors: 'td',
|
|
342
|
-
// handlers: {
|
|
343
|
-
// ...defaultKeypressHandlers,
|
|
344
|
-
// 'keydown.pageup': previousMonth,
|
|
345
|
-
// 'keydown.shift.pageup': previousYear,
|
|
346
|
-
// 'keydown.pagedown': nextMonth,
|
|
347
|
-
// 'keydown.shift.pagedown': nextYear,
|
|
348
|
-
// // TODO: this is a hack to override the stonecrop enter handler;
|
|
349
|
-
// // store context inside the component so that handlers can be setup consistently
|
|
350
|
-
|
|
351
|
-
// 'keydown.enter': () => {}, // select this date
|
|
352
|
-
// },
|
|
353
|
-
// },
|
|
354
|
-
// ])
|
|
355
|
-
|
|
356
|
-
/*******************
|
|
357
|
-
Watchers
|
|
358
|
-
*******************/
|
|
359
|
-
|
|
360
|
-
watch([currentMonth, currentYear], populateMonth)
|
|
361
|
-
|
|
362
|
-
/*******************
|
|
363
|
-
Expose
|
|
364
|
-
*******************/
|
|
365
|
-
|
|
366
|
-
defineExpose({ currentMonth, currentYear, selectedDate })
|
|
367
|
-
</script>
|
|
368
|
-
|
|
369
|
-
<style scoped>
|
|
370
|
-
.adatepicker,
|
|
371
|
-
.adatepicker table,
|
|
372
|
-
.adatepicker tr,
|
|
373
|
-
.adatepicker td,
|
|
374
|
-
.adatepicker th {
|
|
375
|
-
-webkit-user-select: none;
|
|
376
|
-
-moz-user-select: none;
|
|
377
|
-
user-select: none;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
.adatepicker {
|
|
381
|
-
font-size: var(--sc-table-font-size);
|
|
382
|
-
display: inline-table;
|
|
383
|
-
color: var(--sc-cell-text-color);
|
|
384
|
-
outline: none;
|
|
385
|
-
border-collapse: collapse;
|
|
386
|
-
margin-bottom: 10px;
|
|
387
|
-
/* width: calc(100% - 4px); */
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
.adatepicker tr {
|
|
391
|
-
height: 1.15rem;
|
|
392
|
-
height: 1.15rem;
|
|
393
|
-
text-align: center;
|
|
394
|
-
vertical-align: middle;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
.adatepicker td {
|
|
398
|
-
border: 2px solid transparent;
|
|
399
|
-
outline: 2px solid transparent;
|
|
400
|
-
min-width: 3ch;
|
|
401
|
-
max-width: 3ch;
|
|
402
|
-
cursor: pointer;
|
|
403
|
-
}
|
|
404
|
-
.adatepicker td.date-cell:hover {
|
|
405
|
-
background: var(--sc-gray-10);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
.adatepicker td:focus,
|
|
409
|
-
.adatepicker td:focus-within {
|
|
410
|
-
/* outline: 1px dashed black; */
|
|
411
|
-
box-shadow: none;
|
|
412
|
-
overflow: hidden;
|
|
413
|
-
min-height: 1.15em;
|
|
414
|
-
max-height: 1.15em;
|
|
415
|
-
overflow: hidden;
|
|
416
|
-
}
|
|
417
|
-
.adatepicker .selectedDate,
|
|
418
|
-
.adatepicker .startDate,
|
|
419
|
-
.adatepicker .endDate {
|
|
420
|
-
/* outline: 1px solid black; */
|
|
421
|
-
background: var(--sc-gray-20);
|
|
422
|
-
font-weight: bolder;
|
|
423
|
-
}
|
|
424
|
-
.adatepicker .startDate {
|
|
425
|
-
/* border-radius: 5px 0px 0px 5px; */
|
|
426
|
-
border-left: 1px solid var(--sc-gray-50);
|
|
427
|
-
background: var(--sc-gray-20) !important;
|
|
428
|
-
}
|
|
429
|
-
.adatepicker .endDate {
|
|
430
|
-
border-right: 1px solid var(--sc-gray-50);
|
|
431
|
-
/* border-radius: 0px 5px 5px 0px; */
|
|
432
|
-
background: var(--sc-gray-20) !important;
|
|
433
|
-
}
|
|
434
|
-
.adatepicker .withinRange {
|
|
435
|
-
background: var(--sc-gray-5);
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
.adatepicker .todaysDate {
|
|
439
|
-
font-weight: bolder;
|
|
440
|
-
/* text-decoration: underline; */
|
|
441
|
-
color: black;
|
|
442
|
-
}
|
|
443
|
-
.days-header > td {
|
|
444
|
-
font-weight: bold;
|
|
445
|
-
}
|
|
446
|
-
.prev-date {
|
|
447
|
-
color: var(--sc-gray-20);
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
.adatepicker .date-input {
|
|
451
|
-
display: flex;
|
|
452
|
-
width: 100%;
|
|
453
|
-
gap: 5px;
|
|
454
|
-
align-items: center;
|
|
455
|
-
}
|
|
456
|
-
.adatepicker .date-input > input {
|
|
457
|
-
width: 50%;
|
|
458
|
-
padding: 2px;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/* Keep the field error in-flow below the calendar. The shared .aform_error is absolutely
|
|
462
|
-
positioned against a .aform_form-element anchor, which this grid component does not use. */
|
|
463
|
-
p.aform_error {
|
|
464
|
-
position: static;
|
|
465
|
-
display: block;
|
|
466
|
-
color: var(--sc-brand-danger);
|
|
467
|
-
font-size: 0.7rem;
|
|
468
|
-
margin: 0.25rem 0 0;
|
|
469
|
-
}
|
|
470
|
-
</style>
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="aform_form-element">
|
|
3
|
-
<template v-if="mode === 'display'">
|
|
4
|
-
<span class="aform_display-value">{{ displayValue }}</span>
|
|
5
|
-
<label class="aform_field-label">{{ label }}</label>
|
|
6
|
-
</template>
|
|
7
|
-
|
|
8
|
-
<template v-else>
|
|
9
|
-
<input
|
|
10
|
-
:id="uuid"
|
|
11
|
-
class="aform_input-field"
|
|
12
|
-
type="text"
|
|
13
|
-
:value="rangeDisplay"
|
|
14
|
-
placeholder="Select date range"
|
|
15
|
-
:disabled="mode === 'read'"
|
|
16
|
-
readonly
|
|
17
|
-
@click="openPicker" />
|
|
18
|
-
<label class="aform_field-label" :for="uuid">{{ label }}</label>
|
|
19
|
-
|
|
20
|
-
<p v-show="errorText" class="aform_error" v-html="errorText"></p>
|
|
21
|
-
|
|
22
|
-
<ADateSelection
|
|
23
|
-
v-if="showPicker"
|
|
24
|
-
ref="pickerRef"
|
|
25
|
-
class="adaterange-picker"
|
|
26
|
-
:select-range="true"
|
|
27
|
-
:show-time="false"
|
|
28
|
-
@get-date="handlePickerDate" />
|
|
29
|
-
</template>
|
|
30
|
-
</div>
|
|
31
|
-
</template>
|
|
32
|
-
|
|
33
|
-
<script setup lang="ts">
|
|
34
|
-
import { ref, computed, watch } from 'vue'
|
|
35
|
-
import { onClickOutside } from '@vueuse/core'
|
|
36
|
-
import ADateSelection from './ADateSelection.vue'
|
|
37
|
-
import type { ComponentProps } from '../../types'
|
|
38
|
-
|
|
39
|
-
const fmt = (d: string) => new Date(d).toLocaleDateString()
|
|
40
|
-
|
|
41
|
-
const { label = 'Date Range', mode, uuid, errors, validation = { errorMessage: '' } } = defineProps<ComponentProps>()
|
|
42
|
-
|
|
43
|
-
// Dynamic trigger errors take precedence over a static schema errorMessage; empty means the slot hides.
|
|
44
|
-
const errorText = computed(() => (errors?.length ? errors.join('; ') : (validation.errorMessage ?? '')))
|
|
45
|
-
|
|
46
|
-
export interface DateRangeValue {
|
|
47
|
-
start_date: string | null
|
|
48
|
-
end_date: string | null
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const modelValue = defineModel<DateRangeValue>({
|
|
52
|
-
default: () => ({ start_date: null, end_date: null }),
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
const startDate = ref<Date | null>(modelValue.value.start_date ? new Date(modelValue.value.start_date) : null)
|
|
56
|
-
const endDate = ref<Date | null>(modelValue.value.end_date ? new Date(modelValue.value.end_date) : null)
|
|
57
|
-
|
|
58
|
-
const showPicker = ref(false)
|
|
59
|
-
const pickerRef = ref(null)
|
|
60
|
-
onClickOutside(pickerRef, () => (showPicker.value = false))
|
|
61
|
-
|
|
62
|
-
const openPicker = () => {
|
|
63
|
-
if (mode !== 'read') showPicker.value = true
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const formatDate = (d: Date | null): string => {
|
|
67
|
-
if (!d) return ''
|
|
68
|
-
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const rangeDisplay = computed(() => {
|
|
72
|
-
const s = formatDate(startDate.value)
|
|
73
|
-
const e = formatDate(endDate.value)
|
|
74
|
-
if (s && e) return `${s} — ${e}`
|
|
75
|
-
if (s) return `${s} — ...`
|
|
76
|
-
return ''
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const displayValue = computed(() => {
|
|
80
|
-
const s = modelValue.value.start_date
|
|
81
|
-
const e = modelValue.value.end_date
|
|
82
|
-
if (!s && !e) return ''
|
|
83
|
-
if (s && e) return `${fmt(s)} — ${fmt(e)}`
|
|
84
|
-
if (s) return `From ${fmt(s)}`
|
|
85
|
-
return `Until ${fmt(e!)}`
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
const ensureOrder = () => {
|
|
89
|
-
const s = startDate.value
|
|
90
|
-
const e = endDate.value
|
|
91
|
-
if (s && e && e.getTime() < s.getTime()) {
|
|
92
|
-
;[startDate.value, endDate.value] = [e, s]
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const toISODate = (d: Date | null): string | null => (d ? d.toISOString().split('T')[0] : null)
|
|
97
|
-
|
|
98
|
-
const emitModel = () => {
|
|
99
|
-
modelValue.value = {
|
|
100
|
-
start_date: toISODate(startDate.value),
|
|
101
|
-
end_date: toISODate(endDate.value),
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const handlePickerDate = (data: { selected: Date; start?: Date | null; end?: Date | null }) => {
|
|
106
|
-
if (data.start) startDate.value = data.start
|
|
107
|
-
if (data.end) {
|
|
108
|
-
endDate.value = data.end
|
|
109
|
-
ensureOrder()
|
|
110
|
-
showPicker.value = false
|
|
111
|
-
}
|
|
112
|
-
emitModel()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
watch(
|
|
116
|
-
() => modelValue.value,
|
|
117
|
-
newVal => {
|
|
118
|
-
startDate.value = newVal.start_date ? new Date(newVal.start_date) : null
|
|
119
|
-
endDate.value = newVal.end_date ? new Date(newVal.end_date) : null
|
|
120
|
-
},
|
|
121
|
-
{ deep: true }
|
|
122
|
-
)
|
|
123
|
-
</script>
|
|
124
|
-
|
|
125
|
-
<style scoped>
|
|
126
|
-
.adaterange-picker {
|
|
127
|
-
position: absolute;
|
|
128
|
-
top: 100%;
|
|
129
|
-
left: 0;
|
|
130
|
-
z-index: 1000;
|
|
131
|
-
margin-top: 0.25rem;
|
|
132
|
-
}
|
|
133
|
-
</style>
|