vue2-excel-grid 2.2.1 → 2.3.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
|
@@ -1460,10 +1460,34 @@ const ExcelGridComponent = {
|
|
|
1460
1460
|
localSyncKey: { type: String, default: null },
|
|
1461
1461
|
|
|
1462
1462
|
/**
|
|
1463
|
-
*
|
|
1464
|
-
*
|
|
1465
|
-
*
|
|
1466
|
-
* `
|
|
1463
|
+
* Optional tenant/organization identifier for multi-tenant hosts (SaaS
|
|
1464
|
+
* apps where the same browser/device is used across different
|
|
1465
|
+
* organizations). When set, it's combined with `localSyncKey`
|
|
1466
|
+
* internally (`${tenant}::${localSyncKey}`) so each tenant gets a fully
|
|
1467
|
+
* isolated IndexedDB version history — switching tenants on the same
|
|
1468
|
+
* device never shows another tenant's saved drafts. Omit it (default)
|
|
1469
|
+
* for single-tenant hosts; behavior is then identical to before this
|
|
1470
|
+
* prop existed.
|
|
1471
|
+
*
|
|
1472
|
+
* Has no effect without `localSyncKey` — it only changes how that key
|
|
1473
|
+
* is namespaced internally, it doesn't opt into the feature by itself.
|
|
1474
|
+
* Reactive: changing `tenant` (e.g. after an async "current
|
|
1475
|
+
* organization" load) reloads that tenant's own version list/buffer,
|
|
1476
|
+
* the same way a `localSyncKey` that starts empty and becomes truthy
|
|
1477
|
+
* later does. Avoid `::` inside `tenant`/`localSyncKey` values
|
|
1478
|
+
* themselves — it's the internal separator between the two.
|
|
1479
|
+
* @type {String}
|
|
1480
|
+
* @default null
|
|
1481
|
+
* @example
|
|
1482
|
+
* <excel-grid :columns="columns" v-model="data" local-sync-key="invoice-42" :tenant="currentOrgId" toolbar />
|
|
1483
|
+
*/
|
|
1484
|
+
tenant: { type: String, default: null },
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Maximum number of local versions kept per `localSyncKey` (per
|
|
1488
|
+
* `tenant`, when set). Once a new version pushes the count past this,
|
|
1489
|
+
* the oldest version(s) (by `createdAt`) are deleted automatically
|
|
1490
|
+
* (FIFO). Has no effect without `localSyncKey`.
|
|
1467
1491
|
* @type {Number}
|
|
1468
1492
|
* @default 10
|
|
1469
1493
|
* @example
|
|
@@ -1666,6 +1690,17 @@ const ExcelGridComponent = {
|
|
|
1666
1690
|
computed: {
|
|
1667
1691
|
t() { return getMessages(this.locale) },
|
|
1668
1692
|
|
|
1693
|
+
// localSyncKey feature — the actual IndexedDB storage key. idbStore.js
|
|
1694
|
+
// itself knows nothing about "tenants"; this is the one place that
|
|
1695
|
+
// namespaces localSyncKey by tenant when one is given, so every
|
|
1696
|
+
// storage-layer call site below just uses this instead of
|
|
1697
|
+
// this.localSyncKey directly. null whenever localSyncKey itself is
|
|
1698
|
+
// unset — tenant alone doesn't opt into the feature.
|
|
1699
|
+
effectiveSyncKey() {
|
|
1700
|
+
if (!this.localSyncKey) return null
|
|
1701
|
+
return this.tenant ? `${this.tenant}::${this.localSyncKey}` : this.localSyncKey
|
|
1702
|
+
},
|
|
1703
|
+
|
|
1669
1704
|
pinnedLeftSet() {
|
|
1670
1705
|
const s = new Set()
|
|
1671
1706
|
this.leafCols.forEach(col => { if (this.internalPinnedColumns[col.key] === 'left') s.add(col._flatIndex) })
|
|
@@ -2419,17 +2454,23 @@ const ExcelGridComponent = {
|
|
|
2419
2454
|
},
|
|
2420
2455
|
widthMode() { this.$nextTick(this.syncHeaderViewportWidth) },
|
|
2421
2456
|
|
|
2422
|
-
// localSyncKey feature — mounted() only calls
|
|
2423
|
-
// once, at mount time. Without
|
|
2424
|
-
//
|
|
2425
|
-
//
|
|
2426
|
-
//
|
|
2427
|
-
//
|
|
2428
|
-
//
|
|
2457
|
+
// localSyncKey/tenant feature — mounted() only calls
|
|
2458
|
+
// _loadLocalVersionsOnMount() once, at mount time. Without these
|
|
2459
|
+
// watchers, a key/tenant that starts empty (a host conditionally
|
|
2460
|
+
// passing `:local-sync-key="ready ? id : null"`, or a `tenant` only
|
|
2461
|
+
// known after an async "current organization" load) and later becomes
|
|
2462
|
+
// truthy would never load its version list, never promote a leftover
|
|
2463
|
+
// buffer, and never show the toolbar badge — the feature would look
|
|
2464
|
+
// "broken" for the rest of that page's lifetime even though everything
|
|
2465
|
+
// else works. Both share _reloadLocalSyncForKeyChange() since either one
|
|
2466
|
+
// changing means the effective storage key changed.
|
|
2429
2467
|
localSyncKey(newKey, oldKey) {
|
|
2430
2468
|
if (newKey === oldKey) return
|
|
2431
|
-
this.
|
|
2432
|
-
|
|
2469
|
+
this._reloadLocalSyncForKeyChange()
|
|
2470
|
+
},
|
|
2471
|
+
tenant(newTenant, oldTenant) {
|
|
2472
|
+
if (newTenant === oldTenant) return
|
|
2473
|
+
this._reloadLocalSyncForKeyChange()
|
|
2433
2474
|
}
|
|
2434
2475
|
},
|
|
2435
2476
|
|
|
@@ -3648,46 +3689,57 @@ const ExcelGridComponent = {
|
|
|
3648
3689
|
return
|
|
3649
3690
|
}
|
|
3650
3691
|
const key = e.key
|
|
3651
|
-
|
|
3652
|
-
|
|
3692
|
+
// Layout-independent letter for the Ctrl/Cmd shortcuts below (z/y/a/c/x/v/d/r).
|
|
3693
|
+
// e.key reports the character a layout PRODUCES — on a non-Latin layout
|
|
3694
|
+
// (Cyrillic, etc.) the physical C key can report e.key: 'с' (Cyrillic es)
|
|
3695
|
+
// even with Ctrl held and the physical key/position unchanged, silently
|
|
3696
|
+
// breaking `key === 'c'`. e.code is the physical key position and is
|
|
3697
|
+
// immune to layout, so it's used here instead wherever available;
|
|
3698
|
+
// falls back to e.key when e.code is missing (e.g. synthetic events in
|
|
3699
|
+
// tests that don't set it).
|
|
3700
|
+
const shortcutKey = (e.code && e.code.length === 4 && e.code.slice(0, 3) === 'Key')
|
|
3701
|
+
? e.code.charAt(3).toLowerCase()
|
|
3702
|
+
: key
|
|
3703
|
+
|
|
3704
|
+
if ((e.ctrlKey || e.metaKey) && shortcutKey === 'z' && !e.shiftKey) {
|
|
3653
3705
|
e.preventDefault()
|
|
3654
3706
|
this.undo()
|
|
3655
3707
|
return
|
|
3656
3708
|
}
|
|
3657
|
-
if ((e.ctrlKey || e.metaKey) && (
|
|
3709
|
+
if ((e.ctrlKey || e.metaKey) && (shortcutKey === 'y' || (shortcutKey === 'z' && e.shiftKey))) {
|
|
3658
3710
|
e.preventDefault()
|
|
3659
3711
|
this.redo()
|
|
3660
3712
|
return
|
|
3661
3713
|
}
|
|
3662
3714
|
|
|
3663
|
-
if (
|
|
3715
|
+
if (shortcutKey === 'a' && (e.ctrlKey || e.metaKey)) {
|
|
3664
3716
|
e.preventDefault()
|
|
3665
3717
|
this.selectAll()
|
|
3666
3718
|
return
|
|
3667
3719
|
}
|
|
3668
3720
|
|
|
3669
|
-
if (
|
|
3721
|
+
if (shortcutKey === 'c' && (e.ctrlKey || e.metaKey)) {
|
|
3670
3722
|
e.preventDefault()
|
|
3671
3723
|
this.copySelection()
|
|
3672
3724
|
return
|
|
3673
3725
|
}
|
|
3674
|
-
if (
|
|
3726
|
+
if (shortcutKey === 'x' && (e.ctrlKey || e.metaKey)) {
|
|
3675
3727
|
e.preventDefault()
|
|
3676
3728
|
this.cutSelection()
|
|
3677
3729
|
return
|
|
3678
3730
|
}
|
|
3679
|
-
if (
|
|
3731
|
+
if (shortcutKey === 'v' && (e.ctrlKey || e.metaKey)) {
|
|
3680
3732
|
e.preventDefault()
|
|
3681
3733
|
this.pasteAt(this.activeCell.row, this.activeCell.col)
|
|
3682
3734
|
return
|
|
3683
3735
|
}
|
|
3684
3736
|
|
|
3685
|
-
if (
|
|
3737
|
+
if (shortcutKey === 'd' && (e.ctrlKey || e.metaKey)) {
|
|
3686
3738
|
e.preventDefault()
|
|
3687
3739
|
this.fillDown()
|
|
3688
3740
|
return
|
|
3689
3741
|
}
|
|
3690
|
-
if (
|
|
3742
|
+
if (shortcutKey === 'r' && (e.ctrlKey || e.metaKey)) {
|
|
3691
3743
|
e.preventDefault()
|
|
3692
3744
|
this.fillRight()
|
|
3693
3745
|
return
|
|
@@ -6412,9 +6464,19 @@ const ExcelGridComponent = {
|
|
|
6412
6464
|
|
|
6413
6465
|
// =========================================================
|
|
6414
6466
|
// LOCAL SYNC — save `data` as a local IndexedDB *version*, keyed by
|
|
6415
|
-
// localSyncKey, only when the page is
|
|
6467
|
+
// localSyncKey (optionally namespaced by tenant), only when the page is
|
|
6468
|
+
// being left (not per-edit).
|
|
6416
6469
|
// =========================================================
|
|
6417
6470
|
|
|
6471
|
+
// Shared by the localSyncKey and tenant watchers above — either one
|
|
6472
|
+
// changing means effectiveSyncKey now points at a (possibly) different
|
|
6473
|
+
// version history, so the badge/version-list state must be reloaded
|
|
6474
|
+
// from scratch rather than left stale.
|
|
6475
|
+
_reloadLocalSyncForKeyChange() {
|
|
6476
|
+
this.localSyncAvailable = false
|
|
6477
|
+
if (this.effectiveSyncKey) this._loadLocalVersionsOnMount()
|
|
6478
|
+
},
|
|
6479
|
+
|
|
6418
6480
|
// Internal, called once from mounted(): populates the toolbar "unseen
|
|
6419
6481
|
// local versions" dot and emits local-sync-available if any versions
|
|
6420
6482
|
// already exist for this key (e.g. saved in an earlier visit).
|
|
@@ -6422,7 +6484,7 @@ const ExcelGridComponent = {
|
|
|
6422
6484
|
if (!this.localSyncKey) return
|
|
6423
6485
|
await this._promoteBufferIfAny()
|
|
6424
6486
|
try {
|
|
6425
|
-
const versions = await listVersions(this.
|
|
6487
|
+
const versions = await listVersions(this.effectiveSyncKey)
|
|
6426
6488
|
if (!versions.length) return
|
|
6427
6489
|
this.localSyncAvailable = true
|
|
6428
6490
|
this.$emit('local-sync-available', {
|
|
@@ -6453,9 +6515,9 @@ const ExcelGridComponent = {
|
|
|
6453
6515
|
// turns it into a real version next time the grid mounts.
|
|
6454
6516
|
_scheduleBufferWrite(data) {
|
|
6455
6517
|
if (!this.localSyncKey) return
|
|
6456
|
-
const key = this.
|
|
6457
|
-
// a host that swaps localSyncKey mid-edit (rare, but now
|
|
6458
|
-
// the
|
|
6518
|
+
const key = this.effectiveSyncKey // captured now, not re-read 800ms later —
|
|
6519
|
+
// a host that swaps localSyncKey/tenant mid-edit (rare, but now
|
|
6520
|
+
// reactive, see the watchers below) must not have this land under the
|
|
6459
6521
|
// NEW key with the OLD key's data.
|
|
6460
6522
|
clearTimeout(this._bufferWriteTimer)
|
|
6461
6523
|
this._bufferWriteTimer = setTimeout(() => { this._writeBufferNow(key, data) }, 800)
|
|
@@ -6478,13 +6540,13 @@ const ExcelGridComponent = {
|
|
|
6478
6540
|
// like any other non-leave-signal path.
|
|
6479
6541
|
async _promoteBufferIfAny() {
|
|
6480
6542
|
try {
|
|
6481
|
-
const buffered = await getBuffer(this.
|
|
6543
|
+
const buffered = await getBuffer(this.effectiveSyncKey)
|
|
6482
6544
|
if (!buffered) return
|
|
6483
6545
|
const hash = await hashRowsAsync(buffered.data)
|
|
6484
6546
|
const { version, overwritten } = await saveOrTouchVersion(
|
|
6485
|
-
this.
|
|
6547
|
+
this.effectiveSyncKey, buffered.data, hash, this.localSyncMaxVersions
|
|
6486
6548
|
)
|
|
6487
|
-
await clearBuffer(this.
|
|
6549
|
+
await clearBuffer(this.effectiveSyncKey)
|
|
6488
6550
|
this.$emit('local-sync-save', {
|
|
6489
6551
|
key: this.localSyncKey,
|
|
6490
6552
|
id: version.id,
|
|
@@ -6554,12 +6616,12 @@ const ExcelGridComponent = {
|
|
|
6554
6616
|
// the version-store write below is best-effort on top of it, reliable
|
|
6555
6617
|
// for the common case (tab switch/background, page stays alive).
|
|
6556
6618
|
clearTimeout(this._bufferWriteTimer)
|
|
6557
|
-
this._writeBufferNow(this.
|
|
6619
|
+
this._writeBufferNow(this.effectiveSyncKey, data)
|
|
6558
6620
|
try {
|
|
6559
6621
|
const hash = sync ? hashRows(data) : await hashRowsAsync(data)
|
|
6560
6622
|
if (this.data !== data) return // data changed again mid-hash — a later flush will catch it
|
|
6561
6623
|
const { version, overwritten } = await saveOrTouchVersion(
|
|
6562
|
-
this.
|
|
6624
|
+
this.effectiveSyncKey, data, hash, this.localSyncMaxVersions
|
|
6563
6625
|
)
|
|
6564
6626
|
this._lastFlushedDataRef = data
|
|
6565
6627
|
// The version store now has this content durably — the buffer's job
|
|
@@ -6567,7 +6629,7 @@ const ExcelGridComponent = {
|
|
|
6567
6629
|
// doesn't complete before teardown, _promoteBufferIfAny() finds the
|
|
6568
6630
|
// (now-redundant but harmless, dedup makes it a no-op touch) buffer
|
|
6569
6631
|
// on next mount anyway.
|
|
6570
|
-
clearBuffer(this.
|
|
6632
|
+
clearBuffer(this.effectiveSyncKey).catch(() => {})
|
|
6571
6633
|
this.$emit('local-sync-save', {
|
|
6572
6634
|
key: this.localSyncKey,
|
|
6573
6635
|
id: version.id,
|
|
@@ -6597,7 +6659,7 @@ const ExcelGridComponent = {
|
|
|
6597
6659
|
async listLocalVersions() {
|
|
6598
6660
|
if (!this.localSyncKey) return []
|
|
6599
6661
|
try {
|
|
6600
|
-
const versions = await listVersions(this.
|
|
6662
|
+
const versions = await listVersions(this.effectiveSyncKey)
|
|
6601
6663
|
return versions.map(v => ({ id: v.id, createdAt: v.createdAt, updatedAt: v.updatedAt, rowCount: v.data.length }))
|
|
6602
6664
|
} catch (error) {
|
|
6603
6665
|
this.$emit('local-sync-error', { action: 'load', error })
|