vue2-excel-grid 2.0.2 → 2.2.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/package.json
CHANGED
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
{{ opt.label }}
|
|
58
58
|
</div>
|
|
59
59
|
<div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
|
|
60
|
+
<div v-if="clearable && selectedOpt" class="eg-select-footer">
|
|
61
|
+
<button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
|
|
62
|
+
</div>
|
|
60
63
|
</template>
|
|
61
64
|
|
|
62
65
|
<!-- Options panel in standalone mode (floating dropdown below trigger) -->
|
|
@@ -97,6 +100,9 @@
|
|
|
97
100
|
{{ opt.label }}
|
|
98
101
|
</div>
|
|
99
102
|
<div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
|
|
103
|
+
<div v-if="clearable && selectedOpt" class="eg-select-footer">
|
|
104
|
+
<button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
|
|
105
|
+
</div>
|
|
100
106
|
</div>
|
|
101
107
|
|
|
102
108
|
<div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
|
|
@@ -129,7 +135,8 @@ export default {
|
|
|
129
135
|
dropdownOnly: { type: Boolean, default: false },
|
|
130
136
|
triggerRect: { type: Object, default: null },
|
|
131
137
|
size: { type: String, default: 'middle' },
|
|
132
|
-
locale: { type: String, default: 'en' }
|
|
138
|
+
locale: { type: String, default: 'en' },
|
|
139
|
+
clearable: { type: Boolean, default: true }
|
|
133
140
|
},
|
|
134
141
|
data() {
|
|
135
142
|
return {
|
|
@@ -239,6 +246,11 @@ export default {
|
|
|
239
246
|
this.highlight = -1
|
|
240
247
|
}
|
|
241
248
|
},
|
|
249
|
+
// Reuses pickOption's write path (same input/select emit + dropdown-close
|
|
250
|
+
// handling) — a clear is just "pick the empty option", not a separate flow.
|
|
251
|
+
pickClear() {
|
|
252
|
+
this.pickOption({ value: '', label: '' })
|
|
253
|
+
},
|
|
242
254
|
onSearch(e) {
|
|
243
255
|
this._hideTooltip()
|
|
244
256
|
this.searchQuery = e.target.value
|
|
@@ -535,6 +547,29 @@ export default {
|
|
|
535
547
|
margin-top: 3px;
|
|
536
548
|
}
|
|
537
549
|
|
|
550
|
+
/* "Clear" footer — same recipe as EgDatePicker.vue's .eg-dp-footer/.eg-dp-btn,
|
|
551
|
+
just a single right-aligned button (no "Today" equivalent here). Also
|
|
552
|
+
duplicated in ExcelGrid.vue's own <style> — see the comment above
|
|
553
|
+
.eg-select-wrapper there for why, and keep both copies token-identical. */
|
|
554
|
+
.eg-select-footer {
|
|
555
|
+
display: flex;
|
|
556
|
+
justify-content: flex-end;
|
|
557
|
+
padding: 4px 6px 6px;
|
|
558
|
+
border-top: 1px solid var(--eg-color-border, var(--eg-color-border-default));
|
|
559
|
+
}
|
|
560
|
+
.eg-select-btn {
|
|
561
|
+
background: none;
|
|
562
|
+
border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
|
|
563
|
+
border-radius: var(--eg-radius, var(--eg-radius-default));
|
|
564
|
+
padding: 4px 10px;
|
|
565
|
+
cursor: pointer;
|
|
566
|
+
font-size: 12px;
|
|
567
|
+
color: var(--eg-color-text, var(--eg-color-text-default));
|
|
568
|
+
}
|
|
569
|
+
.eg-select-btn:hover { background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default)); }
|
|
570
|
+
.eg-select-btn--clear { color: var(--eg-color-danger, #c62828); border-color: var(--eg-color-danger, #f1b0b0); }
|
|
571
|
+
.eg-select-btn--clear:hover { background: #fff0f0; }
|
|
572
|
+
|
|
538
573
|
/* Themed option/trigger tooltip — same recipe as .eg-select-dropdown
|
|
539
574
|
(cell-bg/border/radius) plus .eg-validation-tooltip's shadow, so it reads
|
|
540
575
|
as part of the grid's own chrome in every built-in theme instead of a
|
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
{{ opt.label }}
|
|
64
64
|
</div>
|
|
65
65
|
<div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
|
|
66
|
+
<div v-if="clearable && selectedLabel != null" class="eg-select-footer">
|
|
67
|
+
<button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
|
|
68
|
+
</div>
|
|
66
69
|
</template>
|
|
67
70
|
|
|
68
71
|
<!-- Options panel in standalone mode -->
|
|
@@ -110,6 +113,9 @@
|
|
|
110
113
|
{{ opt.label }}
|
|
111
114
|
</div>
|
|
112
115
|
<div v-if="!filteredOptions.length" class="eg-select-empty">{{ t.noOptions }}</div>
|
|
116
|
+
<div v-if="clearable && selectedLabel != null" class="eg-select-footer">
|
|
117
|
+
<button class="eg-select-btn eg-select-btn--clear" tabindex="-1" @mousedown.prevent="pickClear">{{ t.clear }}</button>
|
|
118
|
+
</div>
|
|
113
119
|
</div>
|
|
114
120
|
|
|
115
121
|
<div v-if="hoverTooltip" class="eg-option-tooltip" :style="hoverTooltipStyle">{{ hoverTooltip.text }}</div>
|
|
@@ -140,7 +146,8 @@ export default {
|
|
|
140
146
|
dropdownOnly: { type: Boolean, default: false },
|
|
141
147
|
triggerRect: { type: Object, default: null },
|
|
142
148
|
size: { type: String, default: 'middle' },
|
|
143
|
-
locale: { type: String, default: 'en' }
|
|
149
|
+
locale: { type: String, default: 'en' },
|
|
150
|
+
clearable: { type: Boolean, default: true }
|
|
144
151
|
},
|
|
145
152
|
data() {
|
|
146
153
|
return {
|
|
@@ -269,6 +276,11 @@ export default {
|
|
|
269
276
|
this.highlight = -1
|
|
270
277
|
}
|
|
271
278
|
},
|
|
279
|
+
// Reuses pickOption's write path (same input/select emit + dropdown-close
|
|
280
|
+
// handling) — a clear is just "pick the empty option", not a separate flow.
|
|
281
|
+
pickClear() {
|
|
282
|
+
this.pickOption({ value: '', label: '' })
|
|
283
|
+
},
|
|
272
284
|
toggleNode(idx) {
|
|
273
285
|
const opt = this.flatOptions[idx]
|
|
274
286
|
if (!opt || !opt.hasChildren) return
|
|
@@ -854,6 +854,7 @@
|
|
|
854
854
|
:dropdown-only="true"
|
|
855
855
|
:size="internalSize"
|
|
856
856
|
:locale="locale"
|
|
857
|
+
:clearable="selectDropdown.clearable"
|
|
857
858
|
@select="onComponentSelect"
|
|
858
859
|
@close="closeSelectDropdown"
|
|
859
860
|
/>
|
|
@@ -871,6 +872,7 @@
|
|
|
871
872
|
:dropdown-only="true"
|
|
872
873
|
:size="internalSize"
|
|
873
874
|
:locale="locale"
|
|
875
|
+
:clearable="selectDropdown.clearable"
|
|
874
876
|
@select="onComponentSelect"
|
|
875
877
|
@close="closeSelectDropdown"
|
|
876
878
|
/>
|
|
@@ -3667,6 +3669,17 @@ const ExcelGridComponent = {
|
|
|
3667
3669
|
return
|
|
3668
3670
|
}
|
|
3669
3671
|
|
|
3672
|
+
if (key === 'd' && (e.ctrlKey || e.metaKey)) {
|
|
3673
|
+
e.preventDefault()
|
|
3674
|
+
this.fillDown()
|
|
3675
|
+
return
|
|
3676
|
+
}
|
|
3677
|
+
if (key === 'r' && (e.ctrlKey || e.metaKey)) {
|
|
3678
|
+
e.preventDefault()
|
|
3679
|
+
this.fillRight()
|
|
3680
|
+
return
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3670
3683
|
if (key === 'Delete' || key === 'Backspace') {
|
|
3671
3684
|
e.preventDefault()
|
|
3672
3685
|
this.clearSelection()
|
|
@@ -4287,7 +4300,8 @@ const ExcelGridComponent = {
|
|
|
4287
4300
|
rawOptions: this.resolveOptionsArray(col, ri, ci),
|
|
4288
4301
|
currentValue,
|
|
4289
4302
|
rect: rectObj,
|
|
4290
|
-
enableSearch: !!col.enableSearch
|
|
4303
|
+
enableSearch: !!col.enableSearch,
|
|
4304
|
+
clearable: col.clearable !== false
|
|
4291
4305
|
}
|
|
4292
4306
|
},
|
|
4293
4307
|
|
|
@@ -4319,7 +4333,10 @@ const ExcelGridComponent = {
|
|
|
4319
4333
|
const { row, col } = this.selectDropdown
|
|
4320
4334
|
const colDef = this.leafCols[col]
|
|
4321
4335
|
if (colDef) {
|
|
4322
|
-
|
|
4336
|
+
// Clearing (value === '') always goes through, same as onDatePick()'s
|
|
4337
|
+
// empty-string bypass — "no selection" is a valid state regardless of
|
|
4338
|
+
// what cellValidator requires of a real picked value.
|
|
4339
|
+
if (value !== '' && !this.isCellValueValid(row, col, value)) {
|
|
4323
4340
|
this._showInvalidCell(row, col, value)
|
|
4324
4341
|
this.closeSelectDropdown()
|
|
4325
4342
|
return
|
|
@@ -5051,6 +5068,107 @@ const ExcelGridComponent = {
|
|
|
5051
5068
|
if (skipped.length > 0) this._emitBulkValidationError(skipped, false)
|
|
5052
5069
|
},
|
|
5053
5070
|
|
|
5071
|
+
// =========================================================
|
|
5072
|
+
// Ctrl+D / Ctrl+R — Excel's standard fill-down/fill-right (3.96)
|
|
5073
|
+
// =========================================================
|
|
5074
|
+
// Plain copy, NOT the fill-handle's series continuation (generateFillValues,
|
|
5075
|
+
// 1,2,3->4,5,6) — matches real Excel: Ctrl+D/Ctrl+R just duplicates a row/
|
|
5076
|
+
// column's values, it never extrapolates a pattern.
|
|
5077
|
+
async _fillAxis(direction) {
|
|
5078
|
+
if (this.internalLocked) return
|
|
5079
|
+
if (!this.selectionRanges.length) return
|
|
5080
|
+
const oldData = this.data
|
|
5081
|
+
const newData = oldData.slice()
|
|
5082
|
+
const skipped = []
|
|
5083
|
+
const appliedRanges = []
|
|
5084
|
+
|
|
5085
|
+
for (const range of this.selectionRanges) {
|
|
5086
|
+
const r = normalizeRange(range)
|
|
5087
|
+
if (direction === 'down') {
|
|
5088
|
+
let srcRow, firstTargetRow, lastTargetRow
|
|
5089
|
+
if (r.startRow === r.endRow) {
|
|
5090
|
+
// Single-row selection: Excel fills from the row directly above.
|
|
5091
|
+
srcRow = r.startRow - 1
|
|
5092
|
+
if (srcRow < 0) continue
|
|
5093
|
+
firstTargetRow = r.startRow
|
|
5094
|
+
lastTargetRow = r.startRow
|
|
5095
|
+
} else {
|
|
5096
|
+
srcRow = r.startRow
|
|
5097
|
+
firstTargetRow = r.startRow + 1
|
|
5098
|
+
lastTargetRow = r.endRow
|
|
5099
|
+
}
|
|
5100
|
+
for (let ci = r.startCol; ci <= r.endCol; ci++) {
|
|
5101
|
+
const col = this.leafCols[ci]
|
|
5102
|
+
if (!col) continue
|
|
5103
|
+
const srcVal = oldData[srcRow] ? oldData[srcRow][col.key] : ''
|
|
5104
|
+
for (let ri = firstTargetRow; ri <= lastTargetRow; ri++) {
|
|
5105
|
+
if (!this.isCellEditable(ri, ci)) continue
|
|
5106
|
+
const v = this._coerceCellValue(ci, this._applyColMask(ci, srcVal))
|
|
5107
|
+
if (!this.isCellValueValid(ri, ci, v)) { skipped.push({ row: ri, col: ci, value: v }); continue }
|
|
5108
|
+
if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }
|
|
5109
|
+
newData[ri][col.key] = v
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
appliedRanges.push({
|
|
5113
|
+
sourceRange: { startRow: srcRow, endRow: srcRow, startCol: r.startCol, endCol: r.endCol },
|
|
5114
|
+
targetRange: { startRow: firstTargetRow, endRow: lastTargetRow, startCol: r.startCol, endCol: r.endCol }
|
|
5115
|
+
})
|
|
5116
|
+
} else {
|
|
5117
|
+
let srcCol, firstTargetCol, lastTargetCol
|
|
5118
|
+
if (r.startCol === r.endCol) {
|
|
5119
|
+
// Single-column selection: Excel fills from the column directly to the left.
|
|
5120
|
+
srcCol = r.startCol - 1
|
|
5121
|
+
if (srcCol < 0) continue
|
|
5122
|
+
firstTargetCol = r.startCol
|
|
5123
|
+
lastTargetCol = r.startCol
|
|
5124
|
+
} else {
|
|
5125
|
+
srcCol = r.startCol
|
|
5126
|
+
firstTargetCol = r.startCol + 1
|
|
5127
|
+
lastTargetCol = r.endCol
|
|
5128
|
+
}
|
|
5129
|
+
const srcColDef = this.leafCols[srcCol]
|
|
5130
|
+
for (let ri = r.startRow; ri <= r.endRow; ri++) {
|
|
5131
|
+
const srcVal = srcColDef && oldData[ri] ? oldData[ri][srcColDef.key] : ''
|
|
5132
|
+
for (let ci = firstTargetCol; ci <= lastTargetCol; ci++) {
|
|
5133
|
+
const col = this.leafCols[ci]
|
|
5134
|
+
if (!col || !this.isCellEditable(ri, ci)) continue
|
|
5135
|
+
const v = this._coerceCellValue(ci, this._applyColMask(ci, srcVal))
|
|
5136
|
+
if (!this.isCellValueValid(ri, ci, v)) { skipped.push({ row: ri, col: ci, value: v }); continue }
|
|
5137
|
+
if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }
|
|
5138
|
+
newData[ri][col.key] = v
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
appliedRanges.push({
|
|
5142
|
+
sourceRange: { startRow: r.startRow, endRow: r.endRow, startCol: srcCol, endCol: srcCol },
|
|
5143
|
+
targetRange: { startRow: r.startRow, endRow: r.endRow, startCol: firstTargetCol, endCol: lastTargetCol }
|
|
5144
|
+
})
|
|
5145
|
+
}
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
if (!appliedRanges.length) return
|
|
5149
|
+
|
|
5150
|
+
if (this.strictBulkValidation && skipped.length > 0) {
|
|
5151
|
+
this._emitBulkValidationError(skipped, true)
|
|
5152
|
+
return
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
this._emitDataChange(oldData, newData)
|
|
5156
|
+
for (const ar of appliedRanges) {
|
|
5157
|
+
this.$emit('fill', { sourceRange: ar.sourceRange, targetRange: ar.targetRange, values: newData })
|
|
5158
|
+
}
|
|
5159
|
+
if (skipped.length > 0) this._emitBulkValidationError(skipped, false)
|
|
5160
|
+
|
|
5161
|
+
// See pasteAt's/commitFillDrag's _fireCellCallbacksForRange call — same
|
|
5162
|
+
// nextTick deferral reasoning (col.onInput/onSelect after the DOM settles).
|
|
5163
|
+
await this.$nextTick()
|
|
5164
|
+
for (const ar of appliedRanges) {
|
|
5165
|
+
this._fireCellCallbacksForRange(oldData, newData, ar.targetRange.startRow, ar.targetRange.endRow, ar.targetRange.startCol, ar.targetRange.endCol)
|
|
5166
|
+
}
|
|
5167
|
+
},
|
|
5168
|
+
|
|
5169
|
+
fillDown() { return this._fillAxis('down') },
|
|
5170
|
+
fillRight() { return this._fillAxis('right') },
|
|
5171
|
+
|
|
5054
5172
|
// =========================================================
|
|
5055
5173
|
// COPY / PASTE (3.4)
|
|
5056
5174
|
// =========================================================
|
|
@@ -8156,6 +8274,24 @@ export default ExcelGridComponent
|
|
|
8156
8274
|
word-break: break-word;
|
|
8157
8275
|
z-index: 10000;
|
|
8158
8276
|
}
|
|
8277
|
+
.eg-select-footer {
|
|
8278
|
+
display: flex;
|
|
8279
|
+
justify-content: flex-end;
|
|
8280
|
+
padding: 4px 6px 6px;
|
|
8281
|
+
border-top: 1px solid var(--eg-color-border, var(--eg-color-border-default));
|
|
8282
|
+
}
|
|
8283
|
+
.eg-select-btn {
|
|
8284
|
+
background: none;
|
|
8285
|
+
border: 1px solid var(--eg-color-border, var(--eg-color-border-default));
|
|
8286
|
+
border-radius: var(--eg-radius, var(--eg-radius-default));
|
|
8287
|
+
padding: 4px 10px;
|
|
8288
|
+
cursor: pointer;
|
|
8289
|
+
font-size: 12px;
|
|
8290
|
+
color: var(--eg-color-text, var(--eg-color-text-default));
|
|
8291
|
+
}
|
|
8292
|
+
.eg-select-btn:hover { background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default)); }
|
|
8293
|
+
.eg-select-btn--clear { color: var(--eg-color-danger, #c62828); border-color: var(--eg-color-danger, #f1b0b0); }
|
|
8294
|
+
.eg-select-btn--clear:hover { background: #fff0f0; }
|
|
8159
8295
|
/* Grid `size` overrides — must live in this *unscoped* style block, not the
|
|
8160
8296
|
scoped one above: .eg-select-option is rendered by the separate EgSelect /
|
|
8161
8297
|
EgTreeSelect components, so Vue's scoped-CSS attribute (added only to this
|