vue2-excel-grid 2.6.7 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
</template>
|
|
55
55
|
|
|
56
56
|
<script>
|
|
57
|
-
import { parseDateValue, formatDate, toISO, todayISO } from '../utils/dateUtils.js'
|
|
57
|
+
import { parseDateValue, parseDateStrict, getFormatSpec, formatDate, toISO, todayISO, DEFAULT_DATE_FORMAT } from '../utils/dateUtils.js'
|
|
58
58
|
import { getMessages } from '../utils/i18n.js'
|
|
59
59
|
|
|
60
60
|
export default {
|
|
@@ -62,7 +62,7 @@ export default {
|
|
|
62
62
|
|
|
63
63
|
props: {
|
|
64
64
|
value: { default: null },
|
|
65
|
-
format: { type: String, default:
|
|
65
|
+
format: { type: String, default: DEFAULT_DATE_FORMAT },
|
|
66
66
|
locale: { type: String, default: 'en' },
|
|
67
67
|
triggerRect: { type: Object, default: null }
|
|
68
68
|
},
|
|
@@ -167,14 +167,7 @@ export default {
|
|
|
167
167
|
},
|
|
168
168
|
|
|
169
169
|
_autoFillSep(raw) {
|
|
170
|
-
const
|
|
171
|
-
let sep, segments
|
|
172
|
-
if (fmt === 'YYYY-MM-DD') { sep = '-'; segments = [4, 2, 2] }
|
|
173
|
-
else if (fmt === 'DD.MM.YYYY') { sep = '.'; segments = [2, 2, 4] }
|
|
174
|
-
else if (fmt === 'DD/MM/YYYY') { sep = '/'; segments = [2, 2, 4] }
|
|
175
|
-
else if (fmt === 'MM/DD/YYYY') { sep = '/'; segments = [2, 2, 4] }
|
|
176
|
-
else return raw
|
|
177
|
-
|
|
170
|
+
const { sep, segments } = getFormatSpec(this.format)
|
|
178
171
|
const maxDigits = segments.reduce((a, b) => a + b, 0)
|
|
179
172
|
const digits = raw.replace(/\D/g, '').slice(0, maxDigits)
|
|
180
173
|
|
|
@@ -207,7 +200,9 @@ export default {
|
|
|
207
200
|
_commitInput() {
|
|
208
201
|
const val = this.inputVal.trim()
|
|
209
202
|
if (val === '') { this.$emit('pick', ''); return }
|
|
210
|
-
|
|
203
|
+
// Strict: must match THIS column's own format exactly (not just any
|
|
204
|
+
// recognized format) and be a real calendar date — see parseDateStrict.
|
|
205
|
+
const d = parseDateStrict(val, this.format)
|
|
211
206
|
if (!d) { this.inputError = true; return }
|
|
212
207
|
this.$emit('pick', formatDate(d, this.format))
|
|
213
208
|
},
|
|
@@ -144,6 +144,8 @@
|
|
|
144
144
|
tabindex="0"
|
|
145
145
|
@keydown="onKeyDown"
|
|
146
146
|
@keypress="onKeyPress"
|
|
147
|
+
@mousemove="onGridMouseMove"
|
|
148
|
+
@mouseleave="onGridMouseLeave"
|
|
147
149
|
ref="wrapper"
|
|
148
150
|
>
|
|
149
151
|
<!-- Context menu -->
|
|
@@ -508,6 +510,7 @@
|
|
|
508
510
|
:key="'srh-' + ri"
|
|
509
511
|
class="eg-row-header-cell"
|
|
510
512
|
:style="getRowHeaderStyle(ri)"
|
|
513
|
+
:data-row="ri"
|
|
511
514
|
@mousedown="onRowHeaderMouseDown($event, ri)"
|
|
512
515
|
@contextmenu.prevent="onRowContextMenu($event, ri)"
|
|
513
516
|
>
|
|
@@ -548,7 +551,7 @@
|
|
|
548
551
|
<div :style="{ height: totalVirtualHeight + 'px', position: 'relative' }">
|
|
549
552
|
<div :style="{ transform: 'translateY(' + virtualOffsetTop + 'px)', willChange: 'transform', contain: 'layout style' }">
|
|
550
553
|
<template v-for="ri in visibleRowRange">
|
|
551
|
-
<div v-if="!isRowHidden(ri)" :key="'fc-row-' + ri" class="eg-row" :class="{ 'eg-row--even': ri % 2 === 0 }" :style="getRowStyle(ri)">
|
|
554
|
+
<div v-if="!isRowHidden(ri)" :key="'fc-row-' + ri" class="eg-row" :class="{ 'eg-row--even': ri % 2 === 0 }" :style="getRowStyle(ri)" :data-row="ri">
|
|
552
555
|
<template v-for="(col, ci) in leafCols">
|
|
553
556
|
<template v-if="isColumnPinnedLeft(ci) && !isColHidden(ci)">
|
|
554
557
|
<div
|
|
@@ -667,7 +670,7 @@
|
|
|
667
670
|
<div :style="{ height: totalVirtualHeight + 'px', position: 'relative' }">
|
|
668
671
|
<div :style="{ transform: 'translateY(' + virtualOffsetTop + 'px)', willChange: 'transform', contain: 'layout style' }">
|
|
669
672
|
<template v-for="ri in visibleRowRange">
|
|
670
|
-
<div v-if="!isRowHidden(ri)" :key="'pr-row-' + ri" class="eg-row" :class="{ 'eg-row--even': ri % 2 === 0 }" :style="getRowStyle(ri)">
|
|
673
|
+
<div v-if="!isRowHidden(ri)" :key="'pr-row-' + ri" class="eg-row" :class="{ 'eg-row--even': ri % 2 === 0 }" :style="getRowStyle(ri)" :data-row="ri">
|
|
671
674
|
<template v-for="col in pinnedRightCols">
|
|
672
675
|
<div
|
|
673
676
|
:key="'pr-cell-' + ri + '-' + col._flatIndex"
|
|
@@ -978,7 +981,7 @@ import EgSelect from './EgSelect.vue'
|
|
|
978
981
|
import EgTreeSelect from './EgTreeSelect.vue'
|
|
979
982
|
import EgDatePicker from './EgDatePicker.vue'
|
|
980
983
|
import EgLocalSyncModal from './EgLocalSyncModal.vue'
|
|
981
|
-
import { parseDateValue, formatDate } from '../utils/dateUtils.js'
|
|
984
|
+
import { parseDateValue, formatDate, DEFAULT_DATE_FORMAT } from '../utils/dateUtils.js'
|
|
982
985
|
import { formatNumber as _formatNumber } from '../utils/numberFormat.js'
|
|
983
986
|
import { Mask, MaskInput } from '../utils/mask.js'
|
|
984
987
|
import { listVersions, getVersion, deleteVersion, saveOrTouchVersion, putBuffer, getBuffer, clearBuffer } from '../utils/idbStore.js'
|
|
@@ -2622,6 +2625,7 @@ const ExcelGridComponent = {
|
|
|
2622
2625
|
// iterating all 50,000 rows each time. With 1200 cells per render that's
|
|
2623
2626
|
// 60 million iterations per render (~600ms). _dataRaw bypasses this.
|
|
2624
2627
|
this._dataRaw = this.data || []
|
|
2628
|
+
this._hoveredRi = -1
|
|
2625
2629
|
|
|
2626
2630
|
// Non-reactive caches: same (w,h) → same style object → Vue 2 skips DOM style writes
|
|
2627
2631
|
this._cellStyleCache = Object.create(null)
|
|
@@ -3824,6 +3828,45 @@ const ExcelGridComponent = {
|
|
|
3824
3828
|
this.hideSortMenu()
|
|
3825
3829
|
},
|
|
3826
3830
|
|
|
3831
|
+
|
|
3832
|
+
// =========================================================
|
|
3833
|
+
// HOVER HIGHLIGHT
|
|
3834
|
+
// =========================================================
|
|
3835
|
+
onGridMouseMove(e) {
|
|
3836
|
+
const rowNode = e.target.closest('[data-row]')
|
|
3837
|
+
const newRi = rowNode ? parseInt(rowNode.getAttribute('data-row'), 10) : -1
|
|
3838
|
+
|
|
3839
|
+
if (this._hoveredRi !== newRi) {
|
|
3840
|
+
if (this._hoveredRi >= 0) {
|
|
3841
|
+
const oldEls = this.$refs.wrapper.querySelectorAll(`[data-row="${this._hoveredRi}"]`)
|
|
3842
|
+
for (let i = 0; i < oldEls.length; i++) {
|
|
3843
|
+
oldEls[i].classList.remove('eg-row--hover')
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
this._hoveredRi = newRi
|
|
3848
|
+
|
|
3849
|
+
if (this._hoveredRi >= 0) {
|
|
3850
|
+
const newEls = this.$refs.wrapper.querySelectorAll(`[data-row="${this._hoveredRi}"]`)
|
|
3851
|
+
for (let i = 0; i < newEls.length; i++) {
|
|
3852
|
+
newEls[i].classList.add('eg-row--hover')
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
},
|
|
3857
|
+
|
|
3858
|
+
onGridMouseLeave() {
|
|
3859
|
+
if (this._hoveredRi >= 0) {
|
|
3860
|
+
if (this.$refs.wrapper) {
|
|
3861
|
+
const oldEls = this.$refs.wrapper.querySelectorAll(`[data-row="${this._hoveredRi}"]`)
|
|
3862
|
+
for (let i = 0; i < oldEls.length; i++) {
|
|
3863
|
+
oldEls[i].classList.remove('eg-row--hover')
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
this._hoveredRi = -1
|
|
3867
|
+
}
|
|
3868
|
+
},
|
|
3869
|
+
|
|
3827
3870
|
// =========================================================
|
|
3828
3871
|
// KEYBOARD NAVIGATION (3.2)
|
|
3829
3872
|
// =========================================================
|
|
@@ -4536,7 +4579,9 @@ const ExcelGridComponent = {
|
|
|
4536
4579
|
rawOptions: this.resolveOptionsArray(col, ri, ci),
|
|
4537
4580
|
currentValue,
|
|
4538
4581
|
rect: rectObj,
|
|
4539
|
-
enableSearch:
|
|
4582
|
+
// Defaults to on (opt out with enableSearch: false) — same convention
|
|
4583
|
+
// as clearable below.
|
|
4584
|
+
enableSearch: col.enableSearch !== false,
|
|
4540
4585
|
clearable: col.clearable !== false
|
|
4541
4586
|
}
|
|
4542
4587
|
},
|
|
@@ -4620,7 +4665,7 @@ const ExcelGridComponent = {
|
|
|
4620
4665
|
if (this.isSelectLikeCol(col)) return this.getSelectLabel(ci, val, ri)
|
|
4621
4666
|
if (col.type === 'date') {
|
|
4622
4667
|
const d = parseDateValue(val)
|
|
4623
|
-
return d ? formatDate(d, col.format ||
|
|
4668
|
+
return d ? formatDate(d, col.format || DEFAULT_DATE_FORMAT) : (val == null ? '' : val)
|
|
4624
4669
|
}
|
|
4625
4670
|
return val == null ? '' : val
|
|
4626
4671
|
},
|
|
@@ -4950,7 +4995,7 @@ const ExcelGridComponent = {
|
|
|
4950
4995
|
const val = this.getCellValue(row, col)
|
|
4951
4996
|
this.datePickerOverlay = {
|
|
4952
4997
|
row, col,
|
|
4953
|
-
format: colDef.format ||
|
|
4998
|
+
format: colDef.format || DEFAULT_DATE_FORMAT,
|
|
4954
4999
|
value: val == null ? '' : String(val),
|
|
4955
5000
|
rect: { top: rect.top, bottom: rect.bottom, left: rect.left, width: rect.width }
|
|
4956
5001
|
}
|
|
@@ -8440,6 +8485,35 @@ export default ExcelGridComponent
|
|
|
8440
8485
|
line-height: 1;
|
|
8441
8486
|
}
|
|
8442
8487
|
|
|
8488
|
+
/* ===== DATE CELL ICON ===== */
|
|
8489
|
+
/* Same reasoning/placement as the select caret above — reserve room at the
|
|
8490
|
+
cell level so col.align:'right'/centered text never reaches the icon's
|
|
8491
|
+
zone, regardless of alignment. mask-image (not content:url()/an emoji) so
|
|
8492
|
+
the icon is a single flat color that follows --eg-color-text-muted in
|
|
8493
|
+
every theme, light and dark alike, instead of a fixed-color raster/emoji
|
|
8494
|
+
glyph that wouldn't match. */
|
|
8495
|
+
.eg-cell-date:not(.eg-cell--has-suffix) {
|
|
8496
|
+
padding-right: calc(var(--eg-cell-padding-x, var(--eg-cell-padding-x-default)) + 16px);
|
|
8497
|
+
}
|
|
8498
|
+
.eg-cell--has-suffix.eg-cell-date::after { display: none; }
|
|
8499
|
+
.eg-cell-date::after {
|
|
8500
|
+
content: '';
|
|
8501
|
+
position: absolute;
|
|
8502
|
+
right: 5px;
|
|
8503
|
+
top: 50%;
|
|
8504
|
+
width: 12px;
|
|
8505
|
+
height: 12px;
|
|
8506
|
+
transform: translateY(-50%);
|
|
8507
|
+
background-color: var(--eg-color-text-muted, var(--eg-color-text-muted-default));
|
|
8508
|
+
pointer-events: none;
|
|
8509
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round'%3E%3Crect x='1.5' y='3' width='13' height='11' rx='1.5'/%3E%3Cline x1='1.5' y1='6.3' x2='14.5' y2='6.3'/%3E%3Cline x1='4.5' y1='1.2' x2='4.5' y2='4.2'/%3E%3Cline x1='11.5' y1='1.2' x2='11.5' y2='4.2'/%3E%3C/svg%3E");
|
|
8510
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='1.3' stroke-linecap='round'%3E%3Crect x='1.5' y='3' width='13' height='11' rx='1.5'/%3E%3Cline x1='1.5' y1='6.3' x2='14.5' y2='6.3'/%3E%3Cline x1='4.5' y1='1.2' x2='4.5' y2='4.2'/%3E%3Cline x1='11.5' y1='1.2' x2='11.5' y2='4.2'/%3E%3C/svg%3E");
|
|
8511
|
+
-webkit-mask-repeat: no-repeat;
|
|
8512
|
+
mask-repeat: no-repeat;
|
|
8513
|
+
-webkit-mask-size: contain;
|
|
8514
|
+
mask-size: contain;
|
|
8515
|
+
}
|
|
8516
|
+
|
|
8443
8517
|
/* ===== CHECKBOX CELL ===== */
|
|
8444
8518
|
.eg-cell-checkbox {
|
|
8445
8519
|
justify-content: center;
|
|
@@ -8475,7 +8549,12 @@ export default ExcelGridComponent
|
|
|
8475
8549
|
.eg-row--even .eg-cell:not(.eg-cell-selected):not(.eg-cell-active):not(.eg-cell-fill-preview) {
|
|
8476
8550
|
background: var(--eg-color-row-stripe, var(--eg-color-row-stripe-default));
|
|
8477
8551
|
}
|
|
8478
|
-
.eg-row:hover .eg-cell:not(.eg-cell-selected):not(.eg-cell-active):not(.eg-cell-fill-preview)
|
|
8552
|
+
.eg-row:hover .eg-cell:not(.eg-cell-selected):not(.eg-cell-active):not(.eg-cell-fill-preview),
|
|
8553
|
+
.eg-row.eg-row--hover .eg-cell:not(.eg-cell-selected):not(.eg-cell-active):not(.eg-cell-fill-preview) {
|
|
8554
|
+
background: var(--eg-color-row-hover, var(--eg-color-row-hover-default));
|
|
8555
|
+
}
|
|
8556
|
+
.eg-row-header-cell:hover,
|
|
8557
|
+
.eg-row-header-cell.eg-row--hover {
|
|
8479
8558
|
background: var(--eg-color-row-hover, var(--eg-color-row-hover-default));
|
|
8480
8559
|
}
|
|
8481
8560
|
|
package/src/utils/dateUtils.js
CHANGED
|
@@ -2,24 +2,54 @@
|
|
|
2
2
|
* Date parsing and formatting utilities for the date cell type.
|
|
3
3
|
* No external dependencies — pure JS.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Display/storage format controlled by column config `format` prop
|
|
6
|
+
* (default 'DD-MM-YYYY').
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// One definition per supported format string: which regex a candidate string
|
|
10
|
+
// must match, in what order its captured groups map to year/month/day, and
|
|
11
|
+
// (sep/segments) how manual typing auto-inserts separators as digits are
|
|
12
|
+
// entered — see EgDatePicker.vue's _autoFillSep, which reads this table
|
|
13
|
+
// directly so the two never drift apart.
|
|
14
|
+
const FORMATS = {
|
|
15
|
+
'YYYY-MM-DD': { re: /^(\d{4})-(\d{2})-(\d{2})$/, order: ['Y', 'M', 'D'], sep: '-', segments: [4, 2, 2] },
|
|
16
|
+
'DD-MM-YYYY': { re: /^(\d{1,2})-(\d{1,2})-(\d{4})$/, order: ['D', 'M', 'Y'], sep: '-', segments: [2, 2, 4] },
|
|
17
|
+
'DD.MM.YYYY': { re: /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/, order: ['D', 'M', 'Y'], sep: '.', segments: [2, 2, 4] },
|
|
18
|
+
'DD/MM/YYYY': { re: /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/, order: ['D', 'M', 'Y'], sep: '/', segments: [2, 2, 4] },
|
|
19
|
+
'MM/DD/YYYY': { re: /^(\d{1,2})\/(\d{1,2})\/(\d{4})$/, order: ['M', 'D', 'Y'], sep: '/', segments: [2, 2, 4] }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const DEFAULT_DATE_FORMAT = 'DD-MM-YYYY'
|
|
13
23
|
|
|
14
24
|
function pad(n) { return String(n).padStart(2, '0') }
|
|
15
25
|
|
|
26
|
+
// Real calendar-day check — rejects e.g. day 30 of February, month 13, day 0 —
|
|
27
|
+
// instead of relying on `new Date(y, m-1, d)`'s silent overflow-rollover
|
|
28
|
+
// (which would otherwise turn "30.02.2024" into a DIFFERENT, wrong date
|
|
29
|
+
// instead of failing). Leap years handled correctly via the day-0-of-next-
|
|
30
|
+
// month trick (JS Date's own calendar math, not reimplemented here).
|
|
31
|
+
function isValidYMD(y, m, d) {
|
|
32
|
+
if (!Number.isFinite(y) || !Number.isFinite(m) || !Number.isFinite(d)) return false
|
|
33
|
+
if (m < 1 || m > 12 || d < 1) return false
|
|
34
|
+
return d <= new Date(y, m, 0).getDate()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function extract(match, order) {
|
|
38
|
+
const parts = {}
|
|
39
|
+
order.forEach((field, i) => { parts[field] = +match[i + 1] })
|
|
40
|
+
if (!isValidYMD(parts.Y, parts.M, parts.D)) return null
|
|
41
|
+
return new Date(parts.Y, parts.M - 1, parts.D)
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
/**
|
|
17
45
|
* Parse any common date string into a Date object (local midnight).
|
|
18
|
-
*
|
|
19
|
-
*
|
|
46
|
+
* Lenient — tries every known format (not just `hint`/a specific column's
|
|
47
|
+
* own format), for reading already-stored values that may come from an API
|
|
48
|
+
* or a different format than the column's current config. Returns null if
|
|
49
|
+
* unparseable OR not a real calendar date (see isValidYMD).
|
|
20
50
|
*
|
|
21
51
|
* @param {string|number|Date|null} val
|
|
22
|
-
* @param {string} [hint] - 'MDY' to treat slash format as MM/DD/YYYY
|
|
52
|
+
* @param {string} [hint] - 'MDY' to treat an ambiguous slash format as MM/DD/YYYY
|
|
23
53
|
* @returns {Date|null}
|
|
24
54
|
*/
|
|
25
55
|
export function parseDateValue(val, hint) {
|
|
@@ -29,30 +59,53 @@ export function parseDateValue(val, hint) {
|
|
|
29
59
|
const s = String(val).trim()
|
|
30
60
|
|
|
31
61
|
let m
|
|
32
|
-
if ((m =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if ((m =
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
if ((m = DMY_SLASH.exec(s))) {
|
|
39
|
-
if (hint === 'MDY') return new Date(+m[3], +m[1] - 1, +m[2])
|
|
40
|
-
return new Date(+m[3], +m[2] - 1, +m[1])
|
|
62
|
+
if ((m = FORMATS['YYYY-MM-DD'].re.exec(s))) return extract(m, FORMATS['YYYY-MM-DD'].order)
|
|
63
|
+
if ((m = FORMATS['DD-MM-YYYY'].re.exec(s))) return extract(m, FORMATS['DD-MM-YYYY'].order)
|
|
64
|
+
if ((m = FORMATS['DD.MM.YYYY'].re.exec(s))) return extract(m, FORMATS['DD.MM.YYYY'].order)
|
|
65
|
+
if ((m = FORMATS['DD/MM/YYYY'].re.exec(s))) {
|
|
66
|
+
return extract(m, hint === 'MDY' ? FORMATS['MM/DD/YYYY'].order : FORMATS['DD/MM/YYYY'].order)
|
|
41
67
|
}
|
|
42
68
|
return null
|
|
43
69
|
}
|
|
44
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Parse a string strictly against ONE specific format — used to validate
|
|
73
|
+
* manual text-input before it's allowed to commit (EgDatePicker's
|
|
74
|
+
* _commitInput). Unlike parseDateValue(), a string shaped like a DIFFERENT
|
|
75
|
+
* known format (e.g. ISO typed into a 'DD.MM.YYYY' column) is rejected, not
|
|
76
|
+
* silently accepted — and, like parseDateValue(), an out-of-range calendar
|
|
77
|
+
* date (Feb 30, month 13, ...) is rejected rather than rolled over.
|
|
78
|
+
*
|
|
79
|
+
* @param {string} val
|
|
80
|
+
* @param {string} [fmt] - defaults to DEFAULT_DATE_FORMAT if unrecognized/omitted
|
|
81
|
+
* @returns {Date|null}
|
|
82
|
+
*/
|
|
83
|
+
export function parseDateStrict(val, fmt) {
|
|
84
|
+
if (val == null || val === '') return null
|
|
85
|
+
const spec = FORMATS[fmt] || FORMATS[DEFAULT_DATE_FORMAT]
|
|
86
|
+
const m = spec.re.exec(String(val).trim())
|
|
87
|
+
if (!m) return null
|
|
88
|
+
return extract(m, spec.order)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Exposed so EgDatePicker's manual-typing auto-separator logic (_autoFillSep)
|
|
92
|
+
// shares the exact same per-format segment lengths as parsing/validation,
|
|
93
|
+
// instead of keeping a second, driftable copy of the same table.
|
|
94
|
+
export function getFormatSpec(fmt) {
|
|
95
|
+
return FORMATS[fmt] || FORMATS[DEFAULT_DATE_FORMAT]
|
|
96
|
+
}
|
|
97
|
+
|
|
45
98
|
/**
|
|
46
99
|
* Format a Date object according to the format string.
|
|
47
100
|
* Supported tokens: YYYY MM DD
|
|
48
101
|
*
|
|
49
102
|
* @param {Date} date
|
|
50
|
-
* @param {string} [fmt=
|
|
103
|
+
* @param {string} [fmt=DEFAULT_DATE_FORMAT]
|
|
51
104
|
* @returns {string}
|
|
52
105
|
*/
|
|
53
106
|
export function formatDate(date, fmt) {
|
|
54
107
|
if (!date || isNaN(date.getTime())) return ''
|
|
55
|
-
const f = fmt ||
|
|
108
|
+
const f = fmt || DEFAULT_DATE_FORMAT
|
|
56
109
|
return f
|
|
57
110
|
.replace('YYYY', date.getFullYear())
|
|
58
111
|
.replace('MM', pad(date.getMonth() + 1))
|