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.
Files changed (44) hide show
  1. package/package.json +7 -2
  2. package/src/components/alert/Alert.vue +3 -2
  3. package/src/components/alert/alert.css +29 -3
  4. package/src/components/date-picker/CalendarPanel.vue +154 -0
  5. package/src/components/date-picker/CalendarView.vue +104 -0
  6. package/src/components/date-picker/DatePicker.vue +587 -0
  7. package/src/components/date-picker/PanelFooter.vue +45 -0
  8. package/src/components/date-picker/PanelHeader.vue +102 -0
  9. package/src/components/date-picker/TimePanel.vue +98 -0
  10. package/src/components/date-picker/calendar-panel.css +36 -0
  11. package/src/components/date-picker/calendar-view.css +134 -0
  12. package/src/components/date-picker/calendar.js +116 -0
  13. package/src/components/date-picker/date-picker.css +95 -0
  14. package/src/components/date-picker/dayjs.js +34 -0
  15. package/src/components/date-picker/panel-footer.css +42 -0
  16. package/src/components/date-picker/panel-header.css +71 -0
  17. package/src/components/date-picker/time-panel.css +63 -0
  18. package/src/components/dropdown/Dropdown.vue +14 -2
  19. package/src/components/dropdown/DropdownMenuItems.vue +7 -3
  20. package/src/components/dropdown/dropdown-menu-items.css +4 -11
  21. package/src/components/input/input.css +7 -2
  22. package/src/components/menu/Menu.vue +7 -2
  23. package/src/components/menu/menu.css +0 -1
  24. package/src/components/message/Message.vue +72 -0
  25. package/src/components/message/MessageContainer.vue +200 -0
  26. package/src/components/message/message-container.css +108 -0
  27. package/src/components/message/message.css +69 -0
  28. package/src/components/message/message.js +133 -0
  29. package/src/components/progress/Progress.vue +42 -0
  30. package/src/components/progress/progress.css +15 -0
  31. package/src/components/radio/Radio.vue +1 -1
  32. package/src/components/radio-group/RadioGroup.vue +2 -2
  33. package/src/components/select/Select.vue +5 -1
  34. package/src/components/select/select.css +13 -7
  35. package/src/components/select-option/SelectOption.vue +6 -0
  36. package/src/components/select-option/select_option.css +20 -0
  37. package/src/components/table/Table.vue +434 -92
  38. package/src/components/table/table.css +178 -5
  39. package/src/components/tooltip/Tooltip.vue +6 -3
  40. package/src/floating/click-outside-mixin.js +21 -1
  41. package/src/index.js +5 -0
  42. package/src/styles/color_general.css +20 -12
  43. package/src/styles/raw_colors.css +4 -4
  44. 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>