vue2-excel-grid 1.7.2 → 1.8.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 +382 -316
- package/dist/vue2-excel-grid.umd.js +2 -2
- package/package.json +1 -1
- package/src/components/ExcelGrid.vue +66 -14
- package/src/utils/i18n.js +4 -0
package/package.json
CHANGED
|
@@ -61,11 +61,12 @@
|
|
|
61
61
|
<button
|
|
62
62
|
v-if="localSyncKey && toolbarHas('localSync')"
|
|
63
63
|
type="button"
|
|
64
|
-
class="eg-toolbar-btn"
|
|
64
|
+
class="eg-toolbar-btn eg-toolbar-btn-badged"
|
|
65
65
|
:disabled="toolbarDisabled('localSync')"
|
|
66
|
-
:title="t.localSync"
|
|
66
|
+
:title="localSyncAvailable ? t.localSyncAvailable : t.localSync"
|
|
67
67
|
@click="openLocalSyncModal"
|
|
68
|
-
><span class="eg-toolbar-icon eg-icon-local-sync"></span
|
|
68
|
+
><span class="eg-toolbar-icon eg-icon-local-sync"></span
|
|
69
|
+
><span v-if="localSyncAvailable" class="eg-toolbar-badge" aria-hidden="true"></span></button>
|
|
69
70
|
</div>
|
|
70
71
|
|
|
71
72
|
<slot name="toolbar-extra" :locked="internalLocked" :fullscreen="isFullscreen"></slot>
|
|
@@ -1508,6 +1509,10 @@ const ExcelGridComponent = {
|
|
|
1508
1509
|
// localSyncKey feature — modal state + pending debounce timer (the
|
|
1509
1510
|
// timer itself lives outside `data()`'s reactive tracking, see created()).
|
|
1510
1511
|
localSyncModal: { visible: false, snapshot: null },
|
|
1512
|
+
// True when a snapshot already existed at mount and the user hasn't
|
|
1513
|
+
// dealt with it yet — drives the toolbar button's "unseen draft" dot.
|
|
1514
|
+
// See _checkLocalSnapshotOnMount() for the full lifecycle.
|
|
1515
|
+
localSyncAvailable: false,
|
|
1511
1516
|
|
|
1512
1517
|
resizing: {
|
|
1513
1518
|
active: false,
|
|
@@ -2414,14 +2419,6 @@ const ExcelGridComponent = {
|
|
|
2414
2419
|
this._dragScrollRAF = null
|
|
2415
2420
|
this._dragCleanupTimer = null
|
|
2416
2421
|
|
|
2417
|
-
// localSyncKey autosave — deliberately a plain (non-immediate) $watch,
|
|
2418
|
-
// NOT an entry in the `watch:` option above: the existing `data` watcher
|
|
2419
|
-
// there runs with `immediate: true` (fires once on mount with the
|
|
2420
|
-
// initial value), which would autosave a host's still-loading/empty
|
|
2421
|
-
// initial `data` and silently overwrite a previously-saved IndexedDB
|
|
2422
|
-
// snapshot before the user ever gets a chance to see/restore it via the
|
|
2423
|
-
// modal. A plain $watch only fires on later, real changes.
|
|
2424
|
-
this.$watch('data', () => this._scheduleLocalAutosave())
|
|
2425
2422
|
},
|
|
2426
2423
|
|
|
2427
2424
|
mounted() {
|
|
@@ -2454,6 +2451,7 @@ const ExcelGridComponent = {
|
|
|
2454
2451
|
if (this.$el.parentElement) this._scrollBodyResizeObserver.observe(this.$el.parentElement)
|
|
2455
2452
|
}
|
|
2456
2453
|
this._lockFlashArmed = true
|
|
2454
|
+
this._checkLocalSnapshotOnMount()
|
|
2457
2455
|
},
|
|
2458
2456
|
|
|
2459
2457
|
updated() {
|
|
@@ -6013,6 +6011,7 @@ const ExcelGridComponent = {
|
|
|
6013
6011
|
// All edit paths already use spread ({ ...row }) so frozen rows are safe.
|
|
6014
6012
|
const data = result.data.map(row => Object.freeze(row))
|
|
6015
6013
|
this.$emit('input', data)
|
|
6014
|
+
this._scheduleLocalAutosave()
|
|
6016
6015
|
this.$emit('update:columns', columns)
|
|
6017
6016
|
|
|
6018
6017
|
const hasMerges = merges && merges.length > 0
|
|
@@ -6128,6 +6127,7 @@ const ExcelGridComponent = {
|
|
|
6128
6127
|
}
|
|
6129
6128
|
}
|
|
6130
6129
|
this.$emit('input', newData)
|
|
6130
|
+
this._scheduleLocalAutosave()
|
|
6131
6131
|
},
|
|
6132
6132
|
|
|
6133
6133
|
// Emit 'update:merges' and record a merges diff entry.
|
|
@@ -6161,6 +6161,7 @@ const ExcelGridComponent = {
|
|
|
6161
6161
|
newData.length = entry.before.rowCount
|
|
6162
6162
|
}
|
|
6163
6163
|
this.$emit('input', newData)
|
|
6164
|
+
this._scheduleLocalAutosave()
|
|
6164
6165
|
} else if (entry.type === 'merges') {
|
|
6165
6166
|
this.internalMerges = entry.before.slice()
|
|
6166
6167
|
this.$emit('update:merges', entry.before.slice())
|
|
@@ -6183,6 +6184,7 @@ const ExcelGridComponent = {
|
|
|
6183
6184
|
newData[entry.after.indices[i]] = entry.after.rows[i]
|
|
6184
6185
|
}
|
|
6185
6186
|
this.$emit('input', newData)
|
|
6187
|
+
this._scheduleLocalAutosave()
|
|
6186
6188
|
} else if (entry.type === 'merges') {
|
|
6187
6189
|
this.internalMerges = entry.after.slice()
|
|
6188
6190
|
this.$emit('update:merges', entry.after.slice())
|
|
@@ -6219,9 +6221,20 @@ const ExcelGridComponent = {
|
|
|
6219
6221
|
// LOCAL SYNC — autosave `data` to IndexedDB, keyed by localSyncKey
|
|
6220
6222
|
// =========================================================
|
|
6221
6223
|
|
|
6222
|
-
// Internal: debounced (800ms) autosave
|
|
6223
|
-
// set
|
|
6224
|
-
//
|
|
6224
|
+
// Internal: debounced (800ms) autosave. No-op when localSyncKey isn't
|
|
6225
|
+
// set — zero IndexedDB access at all in that case.
|
|
6226
|
+
//
|
|
6227
|
+
// Deliberately called from the grid's OWN data-change points only
|
|
6228
|
+
// (_emitDataChange, undo, redo, import) — NOT from a $watch on `data`.
|
|
6229
|
+
// A watcher can't tell "the user edited a cell" from "the host replaced
|
|
6230
|
+
// `data` wholesale", and the second one is the common case right after
|
|
6231
|
+
// mount: a host that loads rows from an API assigns them ~100-2000ms in,
|
|
6232
|
+
// which a watcher would treat as a real change and autosave over the
|
|
6233
|
+
// user's previously saved draft — destroying exactly the work this
|
|
6234
|
+
// feature exists to protect, before they can open the modal to restore
|
|
6235
|
+
// it. Autosave must mean "the user worked", not "the data object
|
|
6236
|
+
// changed". restoreLocalSnapshot() likewise does NOT call this: the
|
|
6237
|
+
// snapshot it just applied is already what's stored.
|
|
6225
6238
|
_scheduleLocalAutosave() {
|
|
6226
6239
|
if (!this.localSyncKey) return
|
|
6227
6240
|
clearTimeout(this._localSyncTimer)
|
|
@@ -6230,6 +6243,23 @@ const ExcelGridComponent = {
|
|
|
6230
6243
|
}, 800)
|
|
6231
6244
|
},
|
|
6232
6245
|
|
|
6246
|
+
// Internal, called once from mounted(): a snapshot that already exists at
|
|
6247
|
+
// mount is a draft from an EARLIER session, which the user has no way of
|
|
6248
|
+
// knowing about unless they happen to click the toolbar button. Flags it
|
|
6249
|
+
// so the button shows a dot, and emits local-sync-available so a host can
|
|
6250
|
+
// surface its own prompt ("restore unsaved changes?") instead.
|
|
6251
|
+
async _checkLocalSnapshotOnMount() {
|
|
6252
|
+
if (!this.localSyncKey) return
|
|
6253
|
+
const snapshot = await this.getLocalSnapshot()
|
|
6254
|
+
if (!snapshot) return
|
|
6255
|
+
this.localSyncAvailable = true
|
|
6256
|
+
this.$emit('local-sync-available', {
|
|
6257
|
+
key: this.localSyncKey,
|
|
6258
|
+
rows: snapshot.data.length,
|
|
6259
|
+
updatedAt: snapshot.updatedAt
|
|
6260
|
+
})
|
|
6261
|
+
},
|
|
6262
|
+
|
|
6233
6263
|
// Public, ref-callable: saves the current `data` to IndexedDB right now,
|
|
6234
6264
|
// bypassing the debounce (e.g. a host wants to force a checkpoint before
|
|
6235
6265
|
// navigating away). No-op (resolves to null) when localSyncKey isn't set.
|
|
@@ -6238,6 +6268,9 @@ const ExcelGridComponent = {
|
|
|
6238
6268
|
clearTimeout(this._localSyncTimer)
|
|
6239
6269
|
try {
|
|
6240
6270
|
const { updatedAt } = await saveSnapshot(this.localSyncKey, this.data)
|
|
6271
|
+
// What's stored is now exactly what's on screen — there's no longer an
|
|
6272
|
+
// older draft to go back to, so the "unseen draft" dot would mislead.
|
|
6273
|
+
this.localSyncAvailable = false
|
|
6241
6274
|
this.$emit('local-sync-save', { key: this.localSyncKey, rows: this.data.length, updatedAt })
|
|
6242
6275
|
return updatedAt
|
|
6243
6276
|
} catch (error) {
|
|
@@ -6268,6 +6301,7 @@ const ExcelGridComponent = {
|
|
|
6268
6301
|
if (!snapshot) return
|
|
6269
6302
|
this.$emit('input', snapshot.data)
|
|
6270
6303
|
this.$emit('update:data', snapshot.data)
|
|
6304
|
+
this.localSyncAvailable = false
|
|
6271
6305
|
this.$emit('local-sync-restore', { key: this.localSyncKey, rows: snapshot.data.length })
|
|
6272
6306
|
this.closeLocalSyncModal()
|
|
6273
6307
|
} catch (error) {
|
|
@@ -6282,6 +6316,7 @@ const ExcelGridComponent = {
|
|
|
6282
6316
|
try {
|
|
6283
6317
|
await deleteSnapshot(this.localSyncKey)
|
|
6284
6318
|
if (this.localSyncModal.snapshot) this.localSyncModal.snapshot = null
|
|
6319
|
+
this.localSyncAvailable = false
|
|
6285
6320
|
this.$emit('local-sync-clear', { key: this.localSyncKey })
|
|
6286
6321
|
} catch (error) {
|
|
6287
6322
|
this.$emit('local-sync-error', { action: 'clear', error })
|
|
@@ -6318,6 +6353,7 @@ const ExcelGridComponent = {
|
|
|
6318
6353
|
async openLocalSyncModal() {
|
|
6319
6354
|
if (!this.localSyncKey) return
|
|
6320
6355
|
this.localSyncModal.visible = true
|
|
6356
|
+
this.localSyncAvailable = false // the draft has now been seen
|
|
6321
6357
|
try {
|
|
6322
6358
|
this.localSyncModal.snapshot = await loadSnapshot(this.localSyncKey)
|
|
6323
6359
|
} catch (error) {
|
|
@@ -6789,6 +6825,22 @@ export default ExcelGridComponent
|
|
|
6789
6825
|
color: var(--eg-color-text-secondary, var(--eg-color-text-secondary-default));
|
|
6790
6826
|
}
|
|
6791
6827
|
|
|
6828
|
+
/* Anchors the "unseen local draft" dot (localSyncKey feature) — only the
|
|
6829
|
+
local-sync button carries it, but kept as its own class so any future
|
|
6830
|
+
badged tool gets the same positioning. */
|
|
6831
|
+
.eg-toolbar-btn-badged {
|
|
6832
|
+
position: relative;
|
|
6833
|
+
}
|
|
6834
|
+
.eg-toolbar-badge {
|
|
6835
|
+
position: absolute;
|
|
6836
|
+
top: 2px;
|
|
6837
|
+
right: 2px;
|
|
6838
|
+
width: 7px;
|
|
6839
|
+
height: 7px;
|
|
6840
|
+
border-radius: 50%;
|
|
6841
|
+
background: var(--eg-color-accent, var(--eg-color-accent-default));
|
|
6842
|
+
pointer-events: none;
|
|
6843
|
+
}
|
|
6792
6844
|
.eg-toolbar-btn:hover:not(:disabled) {
|
|
6793
6845
|
background: var(--eg-color-option-hover-bg, var(--eg-color-option-hover-bg-default));
|
|
6794
6846
|
border-color: var(--eg-color-border, var(--eg-color-border-default));
|
package/src/utils/i18n.js
CHANGED
|
@@ -47,6 +47,7 @@ const messages = {
|
|
|
47
47
|
theme: 'Theme',
|
|
48
48
|
// local sync
|
|
49
49
|
localSync: 'Local sync',
|
|
50
|
+
localSyncAvailable: 'A locally saved copy is available',
|
|
50
51
|
localSyncTitle: 'Local saved copy',
|
|
51
52
|
localSyncEmpty: 'No local copy saved yet',
|
|
52
53
|
localSyncSavedAt: 'Last saved',
|
|
@@ -118,6 +119,7 @@ const messages = {
|
|
|
118
119
|
theme: 'Тема',
|
|
119
120
|
// local sync
|
|
120
121
|
localSync: 'Локальная синхронизация',
|
|
122
|
+
localSyncAvailable: 'Есть локально сохранённая копия',
|
|
121
123
|
localSyncTitle: 'Локально сохранённая копия',
|
|
122
124
|
localSyncEmpty: 'Локальная копия пока не сохранена',
|
|
123
125
|
localSyncSavedAt: 'Последнее сохранение',
|
|
@@ -189,6 +191,7 @@ const messages = {
|
|
|
189
191
|
theme: 'Mavzu',
|
|
190
192
|
// local sync
|
|
191
193
|
localSync: 'Lokal sinxronlash',
|
|
194
|
+
localSyncAvailable: 'Lokal saqlangan nusxa mavjud',
|
|
192
195
|
localSyncTitle: 'Lokal saqlangan nusxa',
|
|
193
196
|
localSyncEmpty: 'Hali lokal saqlangan nusxa yo\'q',
|
|
194
197
|
localSyncSavedAt: 'Oxirgi saqlangan',
|
|
@@ -260,6 +263,7 @@ const messages = {
|
|
|
260
263
|
theme: 'Мавзу',
|
|
261
264
|
// local sync
|
|
262
265
|
localSync: 'Лакал синхронлаш',
|
|
266
|
+
localSyncAvailable: 'Локал сақланган нусха мавжуд',
|
|
263
267
|
localSyncTitle: 'Лакал сақланган нусха',
|
|
264
268
|
localSyncEmpty: 'Ҳали лакал сақланган нусха йўқ',
|
|
265
269
|
localSyncSavedAt: 'Охирги сақланган',
|