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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2-excel-grid",
3
- "version": "2.2.1",
3
+ "version": "2.3.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",
@@ -1460,10 +1460,34 @@ const ExcelGridComponent = {
1460
1460
  localSyncKey: { type: String, default: null },
1461
1461
 
1462
1462
  /**
1463
- * Maximum number of local versions kept per `localSyncKey`. Once a new
1464
- * version pushes the count past this, the oldest version(s) (by
1465
- * `createdAt`) are deleted automatically (FIFO). Has no effect without
1466
- * `localSyncKey`.
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 _loadLocalVersionsOnMount()
2423
- // once, at mount time. Without this watcher, a key that starts empty (a
2424
- // host conditionally passing `:local-sync-key="ready ? id : null"`, or
2425
- // one only known after an async load) and later becomes truthy would
2426
- // never load its version list, never promote a leftover buffer, and
2427
- // never show the toolbar badge — the feature would look "broken" for the
2428
- // rest of that page's lifetime even though everything else works.
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.localSyncAvailable = false
2432
- if (newKey) this._loadLocalVersionsOnMount()
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
- if ((e.ctrlKey || e.metaKey) && key === 'z' && !e.shiftKey) {
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) && (key === 'y' || (key === 'z' && e.shiftKey))) {
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 (key === 'a' && (e.ctrlKey || e.metaKey)) {
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 (key === 'c' && (e.ctrlKey || e.metaKey)) {
3721
+ if (shortcutKey === 'c' && (e.ctrlKey || e.metaKey)) {
3670
3722
  e.preventDefault()
3671
3723
  this.copySelection()
3672
3724
  return
3673
3725
  }
3674
- if (key === 'x' && (e.ctrlKey || e.metaKey)) {
3726
+ if (shortcutKey === 'x' && (e.ctrlKey || e.metaKey)) {
3675
3727
  e.preventDefault()
3676
3728
  this.cutSelection()
3677
3729
  return
3678
3730
  }
3679
- if (key === 'v' && (e.ctrlKey || e.metaKey)) {
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 (key === 'd' && (e.ctrlKey || e.metaKey)) {
3737
+ if (shortcutKey === 'd' && (e.ctrlKey || e.metaKey)) {
3686
3738
  e.preventDefault()
3687
3739
  this.fillDown()
3688
3740
  return
3689
3741
  }
3690
- if (key === 'r' && (e.ctrlKey || e.metaKey)) {
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 being left (not per-edit).
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.localSyncKey)
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.localSyncKey // captured now, not re-read 800ms later —
6457
- // a host that swaps localSyncKey mid-edit (rare, but now reactive, see
6458
- // the localSyncKey watcher above) must not have this land under 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.localSyncKey)
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.localSyncKey, buffered.data, hash, this.localSyncMaxVersions
6547
+ this.effectiveSyncKey, buffered.data, hash, this.localSyncMaxVersions
6486
6548
  )
6487
- await clearBuffer(this.localSyncKey)
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.localSyncKey, data)
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.localSyncKey, data, hash, this.localSyncMaxVersions
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.localSyncKey).catch(() => {})
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.localSyncKey)
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 })