vue2-excel-grid 2.0.1 → 2.1.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/vue2-excel-grid.esm.js +807 -510
- package/dist/vue2-excel-grid.umd.js +2 -2
- package/package.json +1 -1
- package/src/components/ExcelGrid.vue +135 -6
package/package.json
CHANGED
|
@@ -2402,7 +2402,20 @@ const ExcelGridComponent = {
|
|
|
2402
2402
|
naturalGridWidth() {
|
|
2403
2403
|
if (this.widthMode === 'auto') this.$nextTick(this.syncHeaderViewportWidth)
|
|
2404
2404
|
},
|
|
2405
|
-
widthMode() { this.$nextTick(this.syncHeaderViewportWidth) }
|
|
2405
|
+
widthMode() { this.$nextTick(this.syncHeaderViewportWidth) },
|
|
2406
|
+
|
|
2407
|
+
// localSyncKey feature — mounted() only calls _loadLocalVersionsOnMount()
|
|
2408
|
+
// once, at mount time. Without this watcher, a key that starts empty (a
|
|
2409
|
+
// host conditionally passing `:local-sync-key="ready ? id : null"`, or
|
|
2410
|
+
// one only known after an async load) and later becomes truthy would
|
|
2411
|
+
// never load its version list, never promote a leftover buffer, and
|
|
2412
|
+
// never show the toolbar badge — the feature would look "broken" for the
|
|
2413
|
+
// rest of that page's lifetime even though everything else works.
|
|
2414
|
+
localSyncKey(newKey, oldKey) {
|
|
2415
|
+
if (newKey === oldKey) return
|
|
2416
|
+
this.localSyncAvailable = false
|
|
2417
|
+
if (newKey) this._loadLocalVersionsOnMount()
|
|
2418
|
+
}
|
|
2406
2419
|
},
|
|
2407
2420
|
|
|
2408
2421
|
created() {
|
|
@@ -3654,6 +3667,17 @@ const ExcelGridComponent = {
|
|
|
3654
3667
|
return
|
|
3655
3668
|
}
|
|
3656
3669
|
|
|
3670
|
+
if (key === 'd' && (e.ctrlKey || e.metaKey)) {
|
|
3671
|
+
e.preventDefault()
|
|
3672
|
+
this.fillDown()
|
|
3673
|
+
return
|
|
3674
|
+
}
|
|
3675
|
+
if (key === 'r' && (e.ctrlKey || e.metaKey)) {
|
|
3676
|
+
e.preventDefault()
|
|
3677
|
+
this.fillRight()
|
|
3678
|
+
return
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3657
3681
|
if (key === 'Delete' || key === 'Backspace') {
|
|
3658
3682
|
e.preventDefault()
|
|
3659
3683
|
this.clearSelection()
|
|
@@ -5038,6 +5062,107 @@ const ExcelGridComponent = {
|
|
|
5038
5062
|
if (skipped.length > 0) this._emitBulkValidationError(skipped, false)
|
|
5039
5063
|
},
|
|
5040
5064
|
|
|
5065
|
+
// =========================================================
|
|
5066
|
+
// Ctrl+D / Ctrl+R — Excel's standard fill-down/fill-right (3.96)
|
|
5067
|
+
// =========================================================
|
|
5068
|
+
// Plain copy, NOT the fill-handle's series continuation (generateFillValues,
|
|
5069
|
+
// 1,2,3->4,5,6) — matches real Excel: Ctrl+D/Ctrl+R just duplicates a row/
|
|
5070
|
+
// column's values, it never extrapolates a pattern.
|
|
5071
|
+
async _fillAxis(direction) {
|
|
5072
|
+
if (this.internalLocked) return
|
|
5073
|
+
if (!this.selectionRanges.length) return
|
|
5074
|
+
const oldData = this.data
|
|
5075
|
+
const newData = oldData.slice()
|
|
5076
|
+
const skipped = []
|
|
5077
|
+
const appliedRanges = []
|
|
5078
|
+
|
|
5079
|
+
for (const range of this.selectionRanges) {
|
|
5080
|
+
const r = normalizeRange(range)
|
|
5081
|
+
if (direction === 'down') {
|
|
5082
|
+
let srcRow, firstTargetRow, lastTargetRow
|
|
5083
|
+
if (r.startRow === r.endRow) {
|
|
5084
|
+
// Single-row selection: Excel fills from the row directly above.
|
|
5085
|
+
srcRow = r.startRow - 1
|
|
5086
|
+
if (srcRow < 0) continue
|
|
5087
|
+
firstTargetRow = r.startRow
|
|
5088
|
+
lastTargetRow = r.startRow
|
|
5089
|
+
} else {
|
|
5090
|
+
srcRow = r.startRow
|
|
5091
|
+
firstTargetRow = r.startRow + 1
|
|
5092
|
+
lastTargetRow = r.endRow
|
|
5093
|
+
}
|
|
5094
|
+
for (let ci = r.startCol; ci <= r.endCol; ci++) {
|
|
5095
|
+
const col = this.leafCols[ci]
|
|
5096
|
+
if (!col) continue
|
|
5097
|
+
const srcVal = oldData[srcRow] ? oldData[srcRow][col.key] : ''
|
|
5098
|
+
for (let ri = firstTargetRow; ri <= lastTargetRow; ri++) {
|
|
5099
|
+
if (!this.isCellEditable(ri, ci)) continue
|
|
5100
|
+
const v = this._coerceCellValue(ci, this._applyColMask(ci, srcVal))
|
|
5101
|
+
if (!this.isCellValueValid(ri, ci, v)) { skipped.push({ row: ri, col: ci, value: v }); continue }
|
|
5102
|
+
if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }
|
|
5103
|
+
newData[ri][col.key] = v
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
appliedRanges.push({
|
|
5107
|
+
sourceRange: { startRow: srcRow, endRow: srcRow, startCol: r.startCol, endCol: r.endCol },
|
|
5108
|
+
targetRange: { startRow: firstTargetRow, endRow: lastTargetRow, startCol: r.startCol, endCol: r.endCol }
|
|
5109
|
+
})
|
|
5110
|
+
} else {
|
|
5111
|
+
let srcCol, firstTargetCol, lastTargetCol
|
|
5112
|
+
if (r.startCol === r.endCol) {
|
|
5113
|
+
// Single-column selection: Excel fills from the column directly to the left.
|
|
5114
|
+
srcCol = r.startCol - 1
|
|
5115
|
+
if (srcCol < 0) continue
|
|
5116
|
+
firstTargetCol = r.startCol
|
|
5117
|
+
lastTargetCol = r.startCol
|
|
5118
|
+
} else {
|
|
5119
|
+
srcCol = r.startCol
|
|
5120
|
+
firstTargetCol = r.startCol + 1
|
|
5121
|
+
lastTargetCol = r.endCol
|
|
5122
|
+
}
|
|
5123
|
+
const srcColDef = this.leafCols[srcCol]
|
|
5124
|
+
for (let ri = r.startRow; ri <= r.endRow; ri++) {
|
|
5125
|
+
const srcVal = srcColDef && oldData[ri] ? oldData[ri][srcColDef.key] : ''
|
|
5126
|
+
for (let ci = firstTargetCol; ci <= lastTargetCol; ci++) {
|
|
5127
|
+
const col = this.leafCols[ci]
|
|
5128
|
+
if (!col || !this.isCellEditable(ri, ci)) continue
|
|
5129
|
+
const v = this._coerceCellValue(ci, this._applyColMask(ci, srcVal))
|
|
5130
|
+
if (!this.isCellValueValid(ri, ci, v)) { skipped.push({ row: ri, col: ci, value: v }); continue }
|
|
5131
|
+
if (newData[ri] === oldData[ri]) newData[ri] = { ...oldData[ri] }
|
|
5132
|
+
newData[ri][col.key] = v
|
|
5133
|
+
}
|
|
5134
|
+
}
|
|
5135
|
+
appliedRanges.push({
|
|
5136
|
+
sourceRange: { startRow: r.startRow, endRow: r.endRow, startCol: srcCol, endCol: srcCol },
|
|
5137
|
+
targetRange: { startRow: r.startRow, endRow: r.endRow, startCol: firstTargetCol, endCol: lastTargetCol }
|
|
5138
|
+
})
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
|
|
5142
|
+
if (!appliedRanges.length) return
|
|
5143
|
+
|
|
5144
|
+
if (this.strictBulkValidation && skipped.length > 0) {
|
|
5145
|
+
this._emitBulkValidationError(skipped, true)
|
|
5146
|
+
return
|
|
5147
|
+
}
|
|
5148
|
+
|
|
5149
|
+
this._emitDataChange(oldData, newData)
|
|
5150
|
+
for (const ar of appliedRanges) {
|
|
5151
|
+
this.$emit('fill', { sourceRange: ar.sourceRange, targetRange: ar.targetRange, values: newData })
|
|
5152
|
+
}
|
|
5153
|
+
if (skipped.length > 0) this._emitBulkValidationError(skipped, false)
|
|
5154
|
+
|
|
5155
|
+
// See pasteAt's/commitFillDrag's _fireCellCallbacksForRange call — same
|
|
5156
|
+
// nextTick deferral reasoning (col.onInput/onSelect after the DOM settles).
|
|
5157
|
+
await this.$nextTick()
|
|
5158
|
+
for (const ar of appliedRanges) {
|
|
5159
|
+
this._fireCellCallbacksForRange(oldData, newData, ar.targetRange.startRow, ar.targetRange.endRow, ar.targetRange.startCol, ar.targetRange.endCol)
|
|
5160
|
+
}
|
|
5161
|
+
},
|
|
5162
|
+
|
|
5163
|
+
fillDown() { return this._fillAxis('down') },
|
|
5164
|
+
fillRight() { return this._fillAxis('right') },
|
|
5165
|
+
|
|
5041
5166
|
// =========================================================
|
|
5042
5167
|
// COPY / PASTE (3.4)
|
|
5043
5168
|
// =========================================================
|
|
@@ -6308,14 +6433,18 @@ const ExcelGridComponent = {
|
|
|
6308
6433
|
// turns it into a real version next time the grid mounts.
|
|
6309
6434
|
_scheduleBufferWrite(data) {
|
|
6310
6435
|
if (!this.localSyncKey) return
|
|
6436
|
+
const key = this.localSyncKey // captured now, not re-read 800ms later —
|
|
6437
|
+
// a host that swaps localSyncKey mid-edit (rare, but now reactive, see
|
|
6438
|
+
// the localSyncKey watcher above) must not have this land under the
|
|
6439
|
+
// NEW key with the OLD key's data.
|
|
6311
6440
|
clearTimeout(this._bufferWriteTimer)
|
|
6312
|
-
this._bufferWriteTimer = setTimeout(() => { this._writeBufferNow(data) }, 800)
|
|
6441
|
+
this._bufferWriteTimer = setTimeout(() => { this._writeBufferNow(key, data) }, 800)
|
|
6313
6442
|
},
|
|
6314
6443
|
|
|
6315
|
-
async _writeBufferNow(data) {
|
|
6316
|
-
if (!
|
|
6444
|
+
async _writeBufferNow(key, data) {
|
|
6445
|
+
if (!key) return
|
|
6317
6446
|
try {
|
|
6318
|
-
await putBuffer(
|
|
6447
|
+
await putBuffer(key, data)
|
|
6319
6448
|
} catch (error) {
|
|
6320
6449
|
this.$emit('local-sync-error', { action: 'save', error })
|
|
6321
6450
|
}
|
|
@@ -6405,7 +6534,7 @@ const ExcelGridComponent = {
|
|
|
6405
6534
|
// the version-store write below is best-effort on top of it, reliable
|
|
6406
6535
|
// for the common case (tab switch/background, page stays alive).
|
|
6407
6536
|
clearTimeout(this._bufferWriteTimer)
|
|
6408
|
-
this._writeBufferNow(data)
|
|
6537
|
+
this._writeBufferNow(this.localSyncKey, data)
|
|
6409
6538
|
try {
|
|
6410
6539
|
const hash = sync ? hashRows(data) : await hashRowsAsync(data)
|
|
6411
6540
|
if (this.data !== data) return // data changed again mid-hash — a later flush will catch it
|