webcake-ui-kit 1.1.28 → 1.1.30
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/package.json +7 -2
- package/src/components/alert/Alert.vue +3 -2
- package/src/components/alert/alert.css +29 -3
- package/src/components/date-picker/CalendarPanel.vue +154 -0
- package/src/components/date-picker/CalendarView.vue +104 -0
- package/src/components/date-picker/DatePicker.vue +587 -0
- package/src/components/date-picker/PanelFooter.vue +45 -0
- package/src/components/date-picker/PanelHeader.vue +102 -0
- package/src/components/date-picker/TimePanel.vue +98 -0
- package/src/components/date-picker/calendar-panel.css +36 -0
- package/src/components/date-picker/calendar-view.css +134 -0
- package/src/components/date-picker/calendar.js +116 -0
- package/src/components/date-picker/date-picker.css +95 -0
- package/src/components/date-picker/dayjs.js +34 -0
- package/src/components/date-picker/panel-footer.css +42 -0
- package/src/components/date-picker/panel-header.css +71 -0
- package/src/components/date-picker/time-panel.css +63 -0
- package/src/components/dropdown/Dropdown.vue +14 -2
- package/src/components/dropdown/DropdownMenuItems.vue +7 -3
- package/src/components/dropdown/dropdown-menu-items.css +4 -11
- package/src/components/input/input.css +7 -2
- package/src/components/menu/Menu.vue +7 -2
- package/src/components/menu/menu.css +0 -1
- package/src/components/message/Message.vue +72 -0
- package/src/components/message/MessageContainer.vue +200 -0
- package/src/components/message/message-container.css +108 -0
- package/src/components/message/message.css +69 -0
- package/src/components/message/message.js +133 -0
- package/src/components/progress/Progress.vue +42 -0
- package/src/components/progress/progress.css +15 -0
- package/src/components/radio/Radio.vue +1 -1
- package/src/components/radio-group/RadioGroup.vue +2 -2
- package/src/components/select/Select.vue +5 -1
- package/src/components/select/select.css +13 -7
- package/src/components/select-option/SelectOption.vue +6 -0
- package/src/components/select-option/select_option.css +20 -0
- package/src/components/table/Table.vue +434 -92
- package/src/components/table/table.css +178 -5
- package/src/components/tooltip/Tooltip.vue +6 -3
- package/src/floating/click-outside-mixin.js +21 -1
- package/src/index.js +5 -0
- package/src/styles/color_general.css +20 -12
- package/src/styles/raw_colors.css +4 -4
- package/src/styles/shadow.css +3 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Menu
|
|
3
|
+
class="ui-date-picker"
|
|
4
|
+
:open="isOpen"
|
|
5
|
+
:placement="placement"
|
|
6
|
+
:offset="6"
|
|
7
|
+
:close-on-select="false"
|
|
8
|
+
:disabled="disabled"
|
|
9
|
+
@change="onMenuChange"
|
|
10
|
+
@open="onMenuOpen"
|
|
11
|
+
@close="onMenuClose"
|
|
12
|
+
:overlayStyle="{ maxHeight: 'max-content' }"
|
|
13
|
+
>
|
|
14
|
+
<template #trigger="{ triggerRef, attrs }">
|
|
15
|
+
<div
|
|
16
|
+
:ref="triggerRef"
|
|
17
|
+
class="ui-date-picker__trigger"
|
|
18
|
+
:class="[`ui-date-picker__trigger--${size}`, disabled && 'is-disabled', isOpen && 'is-focus']"
|
|
19
|
+
v-bind="attrs"
|
|
20
|
+
:tabindex="disabled ? -1 : 0"
|
|
21
|
+
role="combobox"
|
|
22
|
+
:aria-expanded="isOpen ? 'true' : 'false'"
|
|
23
|
+
@click="onTriggerClick"
|
|
24
|
+
@keydown="onTriggerKeydown"
|
|
25
|
+
@focus="$emit('focus', $event)"
|
|
26
|
+
@blur="$emit('blur', $event)"
|
|
27
|
+
>
|
|
28
|
+
<span class="ui-date-picker__icon">
|
|
29
|
+
<WkiCalendar :size="16" />
|
|
30
|
+
</span>
|
|
31
|
+
<span class="ui-date-picker__text" :class="{ 'is-placeholder': !hasValue }">
|
|
32
|
+
{{ hasValue ? displayText : placeholderText }}
|
|
33
|
+
</span>
|
|
34
|
+
<span
|
|
35
|
+
v-if="clearable && hasValue && !disabled"
|
|
36
|
+
role="button"
|
|
37
|
+
tabindex="0"
|
|
38
|
+
class="ui-date-picker__clear"
|
|
39
|
+
aria-label="Clear"
|
|
40
|
+
@click.stop="onClear"
|
|
41
|
+
@keydown.enter.stop.prevent="onClear"
|
|
42
|
+
@keydown.space.stop.prevent="onClear"
|
|
43
|
+
>
|
|
44
|
+
<WkiX :size="14" />
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<CalendarPanel
|
|
50
|
+
:views="views"
|
|
51
|
+
:month-options="monthOptions"
|
|
52
|
+
:year-options="yearOptions"
|
|
53
|
+
:show-select-month="showSelectMonth"
|
|
54
|
+
:show-select-year="showSelectYear"
|
|
55
|
+
:weekdays="weekdays"
|
|
56
|
+
:mode="mode"
|
|
57
|
+
:selected="viewSelected"
|
|
58
|
+
:range-start="resolvedRange.start"
|
|
59
|
+
:range-end="resolvedRange.end"
|
|
60
|
+
:focus-iso="focusISO"
|
|
61
|
+
:can-prev="canPrev"
|
|
62
|
+
:can-next="canNext"
|
|
63
|
+
:show-time="showTime"
|
|
64
|
+
:time-value="timeValue"
|
|
65
|
+
:show-seconds="showSeconds"
|
|
66
|
+
:hour-step="hourStep"
|
|
67
|
+
:minute-step="minuteStep"
|
|
68
|
+
:second-step="secondStep"
|
|
69
|
+
:disabled-time="disabledTime"
|
|
70
|
+
:show-footer="showFooter"
|
|
71
|
+
:show-today="showToday && mode === 'single'"
|
|
72
|
+
:show-clear="clearable"
|
|
73
|
+
:show-confirm="confirmVisible"
|
|
74
|
+
:today-disabled="todayDisabled"
|
|
75
|
+
:confirm-disabled="confirmDisabled"
|
|
76
|
+
:today-text="todayText"
|
|
77
|
+
:clear-text="clearText"
|
|
78
|
+
:confirm-text="confirmText"
|
|
79
|
+
@prev="shiftMonth(-1)"
|
|
80
|
+
@next="shiftMonth(1)"
|
|
81
|
+
@prev-year="shiftYear(-1)"
|
|
82
|
+
@next-year="shiftYear(1)"
|
|
83
|
+
@set-month="onSetMonth"
|
|
84
|
+
@set-year="onSetYear"
|
|
85
|
+
@select="onSelectDate"
|
|
86
|
+
@hover="onHover"
|
|
87
|
+
@grid-keydown="onGridKeydown"
|
|
88
|
+
@time-change="onTimeChange"
|
|
89
|
+
@today="onToday"
|
|
90
|
+
@clear="onClear"
|
|
91
|
+
@confirm="onConfirm"
|
|
92
|
+
>
|
|
93
|
+
<template #cell="slotProps">
|
|
94
|
+
<slot name="cell" v-bind="slotProps">{{ slotProps.cell.day }}</slot>
|
|
95
|
+
</template>
|
|
96
|
+
</CalendarPanel>
|
|
97
|
+
</Menu>
|
|
98
|
+
</template>
|
|
99
|
+
|
|
100
|
+
<script>
|
|
101
|
+
/**
|
|
102
|
+
* @typedef {'date'|'week'|'month'|'quarter'|'year'} PickerType
|
|
103
|
+
* @typedef {'single'|'range'|'multiple'} SelectionMode
|
|
104
|
+
* @typedef {Date|string|number} DateInput
|
|
105
|
+
*
|
|
106
|
+
* @typedef {Object} DatePickerProps
|
|
107
|
+
* @property {DateInput|DateInput[]} [modelValue] Vue 3 v-model. single→DateInput, range/multiple→DateInput[].
|
|
108
|
+
* @property {DateInput|DateInput[]} [value] Vue 2 v-model (via model:{prop:'value',event:'change'}).
|
|
109
|
+
* @property {PickerType} [picker='date'] Granularity. Phase 1 implements 'date'.
|
|
110
|
+
* @property {SelectionMode} [mode='single'] Selection behaviour.
|
|
111
|
+
* @property {boolean} [showTime=false] Append a time column; gates emit behind Confirm.
|
|
112
|
+
* @property {string} [format] Display format (dayjs tokens). Defaults by showTime.
|
|
113
|
+
* @property {string|null} [valueFormat=null] When set, emit formatted strings; else emit Date objects.
|
|
114
|
+
* @property {string|string[]} [placeholder] Trigger placeholder (array for range [start,end]).
|
|
115
|
+
* @property {boolean} [disabled=false]
|
|
116
|
+
* @property {boolean} [clearable=true]
|
|
117
|
+
* @property {DateInput} [minDate]
|
|
118
|
+
* @property {DateInput} [maxDate]
|
|
119
|
+
* @property {(date: Date) => boolean} [disabledDate]
|
|
120
|
+
* @property {() => Object} [disabledTime] Returns { disabledHours, disabledMinutes, disabledSeconds }.
|
|
121
|
+
* @property {string} [locale='en']
|
|
122
|
+
* @property {number} [weekStart=0] 0 = Sunday.
|
|
123
|
+
* @property {number} [months=1] Visible month panels (1–3).
|
|
124
|
+
* @property {number} [maxCount=0] Cap for mode='multiple' (0 = unlimited).
|
|
125
|
+
*
|
|
126
|
+
* Emits: update:modelValue, change, open, close, calendar-change, clear, confirm, today, focus, blur.
|
|
127
|
+
*/
|
|
128
|
+
import Menu from '../menu/Menu.vue'
|
|
129
|
+
import CalendarPanel from './CalendarPanel.vue'
|
|
130
|
+
import dayjs from './dayjs.js'
|
|
131
|
+
import { ISO, toDayjs, toISO, isDateDisabled, weekdayLabels, buildDayMatrix, orderISO } from './calendar.js'
|
|
132
|
+
import { WkiCalendar, WkiX } from '../../icons'
|
|
133
|
+
|
|
134
|
+
export default {
|
|
135
|
+
name: 'DatePicker',
|
|
136
|
+
components: { Menu, CalendarPanel, WkiCalendar, WkiX },
|
|
137
|
+
// Vue 2 v-model binds `value`/`change`; Vue 3 v-model binds `modelValue`/`update:modelValue`.
|
|
138
|
+
model: { prop: 'value', event: 'change' },
|
|
139
|
+
props: {
|
|
140
|
+
value: { type: [Date, String, Number, Array], default: undefined },
|
|
141
|
+
modelValue: { type: [Date, String, Number, Array], default: undefined },
|
|
142
|
+
picker: {
|
|
143
|
+
type: String,
|
|
144
|
+
default: 'date',
|
|
145
|
+
validator: v => ['date', 'week', 'month', 'quarter', 'year'].includes(v)
|
|
146
|
+
},
|
|
147
|
+
mode: {
|
|
148
|
+
type: String,
|
|
149
|
+
default: 'single',
|
|
150
|
+
validator: v => ['single', 'range', 'multiple'].includes(v)
|
|
151
|
+
},
|
|
152
|
+
showTime: { type: Boolean, default: false },
|
|
153
|
+
format: { type: String, default: null },
|
|
154
|
+
valueFormat: { type: String, default: null },
|
|
155
|
+
placeholder: { type: [String, Array], default: null },
|
|
156
|
+
disabled: { type: Boolean, default: false },
|
|
157
|
+
clearable: { type: Boolean, default: true },
|
|
158
|
+
minDate: { type: [Date, String, Number], default: null },
|
|
159
|
+
maxDate: { type: [Date, String, Number], default: null },
|
|
160
|
+
disabledDate: { type: Function, default: null },
|
|
161
|
+
disabledTime: { type: Function, default: null },
|
|
162
|
+
locale: { type: String, default: 'en' },
|
|
163
|
+
weekStart: { type: Number, default: 0 },
|
|
164
|
+
months: { type: Number, default: 1 },
|
|
165
|
+
maxCount: { type: Number, default: 0 },
|
|
166
|
+
showToday: { type: Boolean, default: true },
|
|
167
|
+
showFooter: { type: Boolean, default: true },
|
|
168
|
+
// Swap the header month / year label for a WkSelect dropdown.
|
|
169
|
+
showSelectMonth: { type: Boolean, default: false },
|
|
170
|
+
showSelectYear: { type: Boolean, default: false },
|
|
171
|
+
showConfirm: { type: Boolean, default: undefined },
|
|
172
|
+
showSeconds: { type: Boolean, default: false },
|
|
173
|
+
hourStep: { type: Number, default: 1 },
|
|
174
|
+
minuteStep: { type: Number, default: 1 },
|
|
175
|
+
secondStep: { type: Number, default: 1 },
|
|
176
|
+
placement: { type: String, default: 'bottom-start' },
|
|
177
|
+
size: {
|
|
178
|
+
type: String,
|
|
179
|
+
default: 'md',
|
|
180
|
+
validator: v => ['sm', 'md', 'lg'].includes(v)
|
|
181
|
+
},
|
|
182
|
+
todayText: { type: String, default: 'Today' },
|
|
183
|
+
clearText: { type: String, default: 'Clear' },
|
|
184
|
+
confirmText: { type: String, default: 'OK' }
|
|
185
|
+
},
|
|
186
|
+
emits: [
|
|
187
|
+
'change',
|
|
188
|
+
'update:modelValue',
|
|
189
|
+
'open',
|
|
190
|
+
'close',
|
|
191
|
+
'calendar-change',
|
|
192
|
+
'clear',
|
|
193
|
+
'confirm',
|
|
194
|
+
'today',
|
|
195
|
+
'focus',
|
|
196
|
+
'blur'
|
|
197
|
+
],
|
|
198
|
+
data() {
|
|
199
|
+
return {
|
|
200
|
+
isOpen: false,
|
|
201
|
+
// single & multiple: array of ISO keys. range uses the two fields below.
|
|
202
|
+
selection: [],
|
|
203
|
+
rangeStartSel: null,
|
|
204
|
+
rangeEndSel: null,
|
|
205
|
+
rangePicking: false,
|
|
206
|
+
viewISO: dayjs().format(ISO),
|
|
207
|
+
hoverISO: null,
|
|
208
|
+
focusISO: null,
|
|
209
|
+
timeValue: { hour: 0, minute: 0, second: 0 }
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
computed: {
|
|
213
|
+
currentValue() {
|
|
214
|
+
return this.modelValue !== undefined ? this.modelValue : this.value
|
|
215
|
+
},
|
|
216
|
+
monthsCount() {
|
|
217
|
+
const n = parseInt(this.months, 10) || 1
|
|
218
|
+
return Math.min(3, Math.max(1, n))
|
|
219
|
+
},
|
|
220
|
+
resolvedFormat() {
|
|
221
|
+
if (this.format) return this.format
|
|
222
|
+
return this.showTime ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD'
|
|
223
|
+
},
|
|
224
|
+
min() {
|
|
225
|
+
return toDayjs(this.minDate)
|
|
226
|
+
},
|
|
227
|
+
max() {
|
|
228
|
+
return toDayjs(this.maxDate)
|
|
229
|
+
},
|
|
230
|
+
disableOpts() {
|
|
231
|
+
return { minDate: this.min, maxDate: this.max, disabledDate: this.disabledDate }
|
|
232
|
+
},
|
|
233
|
+
weekdays() {
|
|
234
|
+
return weekdayLabels(this.weekStart, this.locale)
|
|
235
|
+
},
|
|
236
|
+
views() {
|
|
237
|
+
const base = dayjs(this.viewISO, ISO)
|
|
238
|
+
const out = []
|
|
239
|
+
for (let i = 0; i < this.monthsCount; i++) {
|
|
240
|
+
const v = base.add(i, 'month')
|
|
241
|
+
out.push({
|
|
242
|
+
matrix: buildDayMatrix(v, this.weekStart, this.disableOpts),
|
|
243
|
+
monthLabel: (this.locale ? v.locale(this.locale) : v).format('MMMM YYYY'),
|
|
244
|
+
month: v.month(),
|
|
245
|
+
year: v.year()
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
return out
|
|
249
|
+
},
|
|
250
|
+
// Localized full month names as header-select options (value = '0'..'11').
|
|
251
|
+
monthOptions() {
|
|
252
|
+
const names = (this.locale ? dayjs().locale(this.locale) : dayjs()).localeData().months()
|
|
253
|
+
return names.map((label, i) => ({ label, value: String(i) }))
|
|
254
|
+
},
|
|
255
|
+
// Year range for the header select — bounded by min/max when present,
|
|
256
|
+
// otherwise ±10 years around the visible month; always spans the view.
|
|
257
|
+
yearOptions() {
|
|
258
|
+
const cur = dayjs(this.viewISO, ISO).year()
|
|
259
|
+
let startY = this.min ? this.min.year() : cur - 10
|
|
260
|
+
let endY = this.max ? this.max.year() : cur + 10
|
|
261
|
+
startY = Math.min(startY, cur)
|
|
262
|
+
endY = Math.max(endY, cur + this.monthsCount - 1)
|
|
263
|
+
const out = []
|
|
264
|
+
for (let y = startY; y <= endY; y++) out.push({ label: String(y), value: String(y) })
|
|
265
|
+
return out
|
|
266
|
+
},
|
|
267
|
+
// What CalendarView highlights as 'selected' (range handled via rangeStart/End).
|
|
268
|
+
viewSelected() {
|
|
269
|
+
return this.mode === 'range' ? [] : this.selection
|
|
270
|
+
},
|
|
271
|
+
// Range band endpoints, including the live hover preview mid-selection.
|
|
272
|
+
resolvedRange() {
|
|
273
|
+
if (this.mode !== 'range') return { start: null, end: null }
|
|
274
|
+
if (this.rangePicking && this.rangeStartSel) {
|
|
275
|
+
return orderISO(this.rangeStartSel, this.hoverISO || this.rangeStartSel)
|
|
276
|
+
}
|
|
277
|
+
if (this.rangeStartSel || this.rangeEndSel) {
|
|
278
|
+
return orderISO(this.rangeStartSel, this.rangeEndSel)
|
|
279
|
+
}
|
|
280
|
+
return { start: null, end: null }
|
|
281
|
+
},
|
|
282
|
+
hasValue() {
|
|
283
|
+
if (this.mode === 'range') return !!(this.rangeStartSel || this.rangeEndSel)
|
|
284
|
+
return this.selection.length > 0
|
|
285
|
+
},
|
|
286
|
+
displayText() {
|
|
287
|
+
if (this.mode === 'single') {
|
|
288
|
+
return this.selection.length ? this.formatDisplay(this.selection[0]) : ''
|
|
289
|
+
}
|
|
290
|
+
if (this.mode === 'multiple') {
|
|
291
|
+
return this.selection.map(iso => this.formatDisplay(iso)).join(', ')
|
|
292
|
+
}
|
|
293
|
+
const s = this.rangeStartSel ? this.formatDisplay(this.rangeStartSel) : ''
|
|
294
|
+
const e = this.rangeEndSel ? this.formatDisplay(this.rangeEndSel) : ''
|
|
295
|
+
return s || e ? `${s} - ${e}` : ''
|
|
296
|
+
},
|
|
297
|
+
placeholderText() {
|
|
298
|
+
if (Array.isArray(this.placeholder)) {
|
|
299
|
+
return this.placeholder.filter(Boolean).join(' - ') || 'Select date'
|
|
300
|
+
}
|
|
301
|
+
if (this.placeholder) return this.placeholder
|
|
302
|
+
return this.mode === 'range' ? 'Start date - End date' : 'Select date'
|
|
303
|
+
},
|
|
304
|
+
confirmVisible() {
|
|
305
|
+
return this.showConfirm === undefined ? this.showTime : this.showConfirm
|
|
306
|
+
},
|
|
307
|
+
confirmDisabled() {
|
|
308
|
+
return !this.hasValue
|
|
309
|
+
},
|
|
310
|
+
todayDisabled() {
|
|
311
|
+
return isDateDisabled(dayjs(), this.disableOpts)
|
|
312
|
+
},
|
|
313
|
+
canPrev() {
|
|
314
|
+
if (!this.min) return true
|
|
315
|
+
return dayjs(this.viewISO, ISO).subtract(1, 'month').endOf('month').isSameOrAfter(this.min, 'day')
|
|
316
|
+
},
|
|
317
|
+
canNext() {
|
|
318
|
+
if (!this.max) return true
|
|
319
|
+
const last = dayjs(this.viewISO, ISO).add(this.monthsCount - 1, 'month')
|
|
320
|
+
return last.add(1, 'month').startOf('month').isSameOrBefore(this.max, 'day')
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
watch: {
|
|
324
|
+
currentValue: {
|
|
325
|
+
handler() {
|
|
326
|
+
this.syncFromValue()
|
|
327
|
+
},
|
|
328
|
+
deep: true
|
|
329
|
+
},
|
|
330
|
+
mode() {
|
|
331
|
+
this.syncFromValue()
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
created() {
|
|
335
|
+
this.syncFromValue()
|
|
336
|
+
},
|
|
337
|
+
methods: {
|
|
338
|
+
// ---- value <-> internal sync -------------------------------------------
|
|
339
|
+
syncFromValue() {
|
|
340
|
+
const val = this.currentValue
|
|
341
|
+
const fmt = this.resolvedFormat
|
|
342
|
+
if (this.mode === 'single') {
|
|
343
|
+
const iso = toISO(val, fmt)
|
|
344
|
+
this.selection = iso ? [iso] : []
|
|
345
|
+
if (iso) this.captureTime(val)
|
|
346
|
+
} else if (this.mode === 'multiple') {
|
|
347
|
+
const arr = Array.isArray(val) ? val : []
|
|
348
|
+
this.selection = arr.map(v => toISO(v, fmt)).filter(Boolean)
|
|
349
|
+
} else {
|
|
350
|
+
const arr = Array.isArray(val) ? val : []
|
|
351
|
+
this.rangeStartSel = toISO(arr[0], fmt)
|
|
352
|
+
this.rangeEndSel = toISO(arr[1], fmt)
|
|
353
|
+
this.rangePicking = false
|
|
354
|
+
}
|
|
355
|
+
this.syncViewToSelection()
|
|
356
|
+
},
|
|
357
|
+
captureTime(val) {
|
|
358
|
+
const d = toDayjs(val, this.resolvedFormat)
|
|
359
|
+
if (d) this.timeValue = { hour: d.hour(), minute: d.minute(), second: d.second() }
|
|
360
|
+
},
|
|
361
|
+
syncViewToSelection() {
|
|
362
|
+
const anchor = (this.mode === 'range' ? this.rangeStartSel || this.rangeEndSel : this.selection[0]) || null
|
|
363
|
+
this.viewISO = (anchor ? dayjs(anchor, ISO) : dayjs()).format(ISO)
|
|
364
|
+
},
|
|
365
|
+
// ---- output -------------------------------------------------------------
|
|
366
|
+
buildOut(iso) {
|
|
367
|
+
if (!iso) return null
|
|
368
|
+
let d = dayjs(iso, ISO)
|
|
369
|
+
if (this.showTime) {
|
|
370
|
+
d = d
|
|
371
|
+
.hour(this.timeValue.hour)
|
|
372
|
+
.minute(this.timeValue.minute)
|
|
373
|
+
.second(this.showSeconds ? this.timeValue.second : 0)
|
|
374
|
+
}
|
|
375
|
+
return this.valueFormat ? d.format(this.valueFormat) : d.toDate()
|
|
376
|
+
},
|
|
377
|
+
emitValue() {
|
|
378
|
+
let out
|
|
379
|
+
if (this.mode === 'single') {
|
|
380
|
+
out = this.selection.length ? this.buildOut(this.selection[0]) : null
|
|
381
|
+
} else if (this.mode === 'multiple') {
|
|
382
|
+
out = this.selection.map(iso => this.buildOut(iso))
|
|
383
|
+
} else {
|
|
384
|
+
out = [
|
|
385
|
+
this.rangeStartSel ? this.buildOut(this.rangeStartSel) : null,
|
|
386
|
+
this.rangeEndSel ? this.buildOut(this.rangeEndSel) : null
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
this.$emit('change', out)
|
|
390
|
+
this.$emit('update:modelValue', out)
|
|
391
|
+
},
|
|
392
|
+
formatDisplay(iso) {
|
|
393
|
+
let d = dayjs(iso, ISO)
|
|
394
|
+
if (this.showTime) {
|
|
395
|
+
d = d.hour(this.timeValue.hour).minute(this.timeValue.minute).second(this.timeValue.second)
|
|
396
|
+
}
|
|
397
|
+
if (this.locale) d = d.locale(this.locale)
|
|
398
|
+
return d.format(this.resolvedFormat)
|
|
399
|
+
},
|
|
400
|
+
// ---- selection ----------------------------------------------------------
|
|
401
|
+
onSelectDate(iso) {
|
|
402
|
+
if (!iso) return
|
|
403
|
+
if (this.mode === 'single') {
|
|
404
|
+
this.selection = [iso]
|
|
405
|
+
if (this.confirmVisible) return // wait for Confirm
|
|
406
|
+
this.emitValue()
|
|
407
|
+
this.closePicker()
|
|
408
|
+
} else if (this.mode === 'multiple') {
|
|
409
|
+
const idx = this.selection.indexOf(iso)
|
|
410
|
+
let next = idx === -1 ? this.selection.concat([iso]) : this.selection.filter(x => x !== iso)
|
|
411
|
+
if (this.maxCount > 0 && next.length > this.maxCount) {
|
|
412
|
+
next = next.slice(next.length - this.maxCount)
|
|
413
|
+
}
|
|
414
|
+
this.selection = next
|
|
415
|
+
this.emitValue()
|
|
416
|
+
} else {
|
|
417
|
+
this.selectRange(iso)
|
|
418
|
+
}
|
|
419
|
+
},
|
|
420
|
+
selectRange(iso) {
|
|
421
|
+
if (!this.rangePicking) {
|
|
422
|
+
this.rangeStartSel = iso
|
|
423
|
+
this.rangeEndSel = null
|
|
424
|
+
this.rangePicking = true
|
|
425
|
+
this.hoverISO = iso
|
|
426
|
+
this.$emit('calendar-change', [this.buildOut(iso), null])
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
const ordered = orderISO(this.rangeStartSel, iso)
|
|
430
|
+
this.rangeStartSel = ordered.start
|
|
431
|
+
this.rangeEndSel = ordered.end
|
|
432
|
+
this.rangePicking = false
|
|
433
|
+
this.hoverISO = null
|
|
434
|
+
this.$emit('calendar-change', [this.buildOut(ordered.start), this.buildOut(ordered.end)])
|
|
435
|
+
if (this.confirmVisible) return
|
|
436
|
+
this.emitValue()
|
|
437
|
+
this.closePicker()
|
|
438
|
+
},
|
|
439
|
+
onHover(iso) {
|
|
440
|
+
if (this.mode === 'range' && this.rangePicking) this.hoverISO = iso
|
|
441
|
+
},
|
|
442
|
+
onTimeChange(t) {
|
|
443
|
+
this.timeValue = t
|
|
444
|
+
// Reflect time live for single once a day is chosen (still gated by Confirm for emit).
|
|
445
|
+
if (!this.confirmVisible && this.hasValue) this.emitValue()
|
|
446
|
+
},
|
|
447
|
+
// ---- footer actions -----------------------------------------------------
|
|
448
|
+
onToday() {
|
|
449
|
+
if (this.todayDisabled) return
|
|
450
|
+
this.onSelectDate(dayjs().format(ISO))
|
|
451
|
+
this.$emit('today')
|
|
452
|
+
},
|
|
453
|
+
onClear() {
|
|
454
|
+
this.selection = []
|
|
455
|
+
this.rangeStartSel = null
|
|
456
|
+
this.rangeEndSel = null
|
|
457
|
+
this.rangePicking = false
|
|
458
|
+
this.hoverISO = null
|
|
459
|
+
this.emitValue()
|
|
460
|
+
this.$emit('clear')
|
|
461
|
+
},
|
|
462
|
+
onConfirm() {
|
|
463
|
+
this.emitValue()
|
|
464
|
+
this.$emit('confirm')
|
|
465
|
+
this.closePicker()
|
|
466
|
+
},
|
|
467
|
+
// ---- navigation ---------------------------------------------------------
|
|
468
|
+
shiftMonth(delta) {
|
|
469
|
+
this.viewISO = dayjs(this.viewISO, ISO).add(delta, 'month').format(ISO)
|
|
470
|
+
},
|
|
471
|
+
shiftYear(delta) {
|
|
472
|
+
this.viewISO = dayjs(this.viewISO, ISO).add(delta, 'year').format(ISO)
|
|
473
|
+
},
|
|
474
|
+
// Header month/year selects target panel `index`; rebase viewISO so that
|
|
475
|
+
// panel shows the chosen month/year (subtract its offset from the anchor).
|
|
476
|
+
onSetMonth({ index, month }) {
|
|
477
|
+
// date(1) first so setting the month never overflows (e.g. Jul 31 → Feb).
|
|
478
|
+
const target = dayjs(this.viewISO, ISO).date(1).add(index, 'month').month(month)
|
|
479
|
+
this.viewISO = target.subtract(index, 'month').format(ISO)
|
|
480
|
+
},
|
|
481
|
+
onSetYear({ index, year }) {
|
|
482
|
+
const target = dayjs(this.viewISO, ISO).date(1).add(index, 'month').year(year)
|
|
483
|
+
this.viewISO = target.subtract(index, 'month').format(ISO)
|
|
484
|
+
},
|
|
485
|
+
// ---- keyboard -----------------------------------------------------------
|
|
486
|
+
onTriggerClick() {
|
|
487
|
+
if (this.disabled) return
|
|
488
|
+
this.isOpen ? this.closePicker() : this.openPicker()
|
|
489
|
+
},
|
|
490
|
+
onTriggerKeydown(e) {
|
|
491
|
+
if (this.disabled) return
|
|
492
|
+
if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
|
|
493
|
+
e.preventDefault()
|
|
494
|
+
this.openPicker()
|
|
495
|
+
} else if (e.key === 'Escape') {
|
|
496
|
+
this.closePicker()
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
onGridKeydown(e) {
|
|
500
|
+
const map = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }
|
|
501
|
+
let base = this.focusISO || this.selection[0] || this.rangeStartSel || dayjs().format(ISO)
|
|
502
|
+
let d = dayjs(base, ISO)
|
|
503
|
+
let handled = true
|
|
504
|
+
if (map[e.key] !== undefined) {
|
|
505
|
+
d = d.add(map[e.key], 'day')
|
|
506
|
+
} else if (e.key === 'Home') {
|
|
507
|
+
d = d.subtract((d.day() - this.weekStart + 7) % 7, 'day')
|
|
508
|
+
} else if (e.key === 'End') {
|
|
509
|
+
d = d.add(6 - ((d.day() - this.weekStart + 7) % 7), 'day')
|
|
510
|
+
} else if (e.key === 'PageUp') {
|
|
511
|
+
d = d.add(e.shiftKey ? -1 : 0, 'year').add(e.shiftKey ? 0 : -1, 'month')
|
|
512
|
+
} else if (e.key === 'PageDown') {
|
|
513
|
+
d = d.add(e.shiftKey ? 1 : 0, 'year').add(e.shiftKey ? 0 : 1, 'month')
|
|
514
|
+
} else if (e.key === 'Enter' || e.key === ' ') {
|
|
515
|
+
if (this.focusISO && !isDateDisabled(dayjs(this.focusISO, ISO), this.disableOpts)) {
|
|
516
|
+
this.onSelectDate(this.focusISO)
|
|
517
|
+
}
|
|
518
|
+
e.preventDefault()
|
|
519
|
+
return
|
|
520
|
+
} else {
|
|
521
|
+
handled = false
|
|
522
|
+
}
|
|
523
|
+
if (!handled) return
|
|
524
|
+
e.preventDefault()
|
|
525
|
+
this.focusISO = d.format(ISO)
|
|
526
|
+
this.ensureVisible(d)
|
|
527
|
+
this.$nextTick(this.focusActiveCell)
|
|
528
|
+
},
|
|
529
|
+
ensureVisible(d) {
|
|
530
|
+
const first = dayjs(this.viewISO, ISO).startOf('month')
|
|
531
|
+
const last = dayjs(this.viewISO, ISO)
|
|
532
|
+
.add(this.monthsCount - 1, 'month')
|
|
533
|
+
.endOf('month')
|
|
534
|
+
if (d.isBefore(first, 'day')) this.viewISO = d.format(ISO)
|
|
535
|
+
else if (d.isAfter(last, 'day')) this.viewISO = d.subtract(this.monthsCount - 1, 'month').format(ISO)
|
|
536
|
+
},
|
|
537
|
+
focusActiveCell() {
|
|
538
|
+
const el =
|
|
539
|
+
this.$el && this.$el.querySelector ? document.querySelector('.ui-menu .ui-cal-cell[tabindex="0"]') : null
|
|
540
|
+
if (el) el.focus()
|
|
541
|
+
},
|
|
542
|
+
// ---- open/close ---------------------------------------------------------
|
|
543
|
+
openPicker() {
|
|
544
|
+
if (this.disabled || this.isOpen) return
|
|
545
|
+
this.isOpen = true
|
|
546
|
+
},
|
|
547
|
+
closePicker() {
|
|
548
|
+
if (!this.isOpen) return
|
|
549
|
+
this.isOpen = false
|
|
550
|
+
},
|
|
551
|
+
onMenuChange(v) {
|
|
552
|
+
this.isOpen = v
|
|
553
|
+
},
|
|
554
|
+
onMenuOpen() {
|
|
555
|
+
this.syncViewToSelection()
|
|
556
|
+
this.focusISO = (this.mode === 'range' ? this.rangeStartSel : this.selection[0]) || dayjs().format(ISO)
|
|
557
|
+
this.$emit('open')
|
|
558
|
+
},
|
|
559
|
+
onMenuClose() {
|
|
560
|
+
// Abandon a half-picked range so reopening starts clean.
|
|
561
|
+
if (this.rangePicking) {
|
|
562
|
+
this.rangePicking = false
|
|
563
|
+
this.hoverISO = null
|
|
564
|
+
this.syncFromValue()
|
|
565
|
+
}
|
|
566
|
+
this.$emit('close')
|
|
567
|
+
},
|
|
568
|
+
// ---- exposed (via $refs) ------------------------------------------------
|
|
569
|
+
focus() {
|
|
570
|
+
const el = this.$el && this.$el.querySelector('.ui-date-picker__trigger')
|
|
571
|
+
if (el) el.focus()
|
|
572
|
+
},
|
|
573
|
+
blur() {
|
|
574
|
+
const el = this.$el && this.$el.querySelector('.ui-date-picker__trigger')
|
|
575
|
+
if (el) el.blur()
|
|
576
|
+
},
|
|
577
|
+
open() {
|
|
578
|
+
this.openPicker()
|
|
579
|
+
},
|
|
580
|
+
close() {
|
|
581
|
+
this.closePicker()
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
</script>
|
|
586
|
+
|
|
587
|
+
<style src="./date-picker.css" scoped></style>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="ui-cal-footer">
|
|
3
|
+
<div class="ui-cal-footer__left">
|
|
4
|
+
<button
|
|
5
|
+
v-if="showToday"
|
|
6
|
+
type="button"
|
|
7
|
+
class="ui-cal-footer__link"
|
|
8
|
+
:disabled="todayDisabled"
|
|
9
|
+
@click="$emit('today')"
|
|
10
|
+
>
|
|
11
|
+
{{ todayText }}
|
|
12
|
+
</button>
|
|
13
|
+
</div>
|
|
14
|
+
<div class="ui-cal-footer__right">
|
|
15
|
+
<WkButton v-if="showClear" size="mini" variant="ghost" @click="$emit('clear')">
|
|
16
|
+
{{ clearText }}
|
|
17
|
+
</WkButton>
|
|
18
|
+
<WkButton v-if="showConfirm" size="mini" variant="primary" :disabled="confirmDisabled" @click="$emit('confirm')">
|
|
19
|
+
{{ confirmText }}
|
|
20
|
+
</WkButton>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script>
|
|
26
|
+
import WkButton from '../button/Button.vue'
|
|
27
|
+
|
|
28
|
+
export default {
|
|
29
|
+
name: 'PanelFooter',
|
|
30
|
+
components: { WkButton },
|
|
31
|
+
props: {
|
|
32
|
+
showToday: { type: Boolean, default: true },
|
|
33
|
+
showClear: { type: Boolean, default: true },
|
|
34
|
+
showConfirm: { type: Boolean, default: false },
|
|
35
|
+
todayDisabled: { type: Boolean, default: false },
|
|
36
|
+
confirmDisabled: { type: Boolean, default: false },
|
|
37
|
+
todayText: { type: String, default: 'Today' },
|
|
38
|
+
clearText: { type: String, default: 'Clear' },
|
|
39
|
+
confirmText: { type: String, default: 'OK' }
|
|
40
|
+
},
|
|
41
|
+
emits: ['today', 'clear', 'confirm']
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<style src="./panel-footer.css" scoped></style>
|