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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-excel-grid",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Excel-style grid component for Vue 2.6.14 with virtual scrolling",
5
5
  "license": "MIT",
6
6
  "author": "Fozilbek Raimberdiyev",
@@ -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 (!this.localSyncKey) return
6444
+ async _writeBufferNow(key, data) {
6445
+ if (!key) return
6317
6446
  try {
6318
- await putBuffer(this.localSyncKey, data)
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