@dimarborda/xlsx-editor 0.3.0 → 0.3.2
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/README.md +1 -1
- package/package.json +1 -1
- package/src/index.d.ts +1 -1
- package/src/xlsx_editor.js +23 -8
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @dimarborda/xlsx-editor
|
|
2
2
|
|
|
3
|
-
View and edit Excel (`.xlsx`) workbooks in the browser, drawn on canvas the way Excel shows them, and work with them from Node.js. A Rust and WebAssembly engine reads the file, recalculates formulas and saves losslessly: what it does not touch stays byte by byte. It works
|
|
3
|
+
View and edit Excel (`.xlsx`) workbooks in the browser, drawn on canvas the way Excel shows them, and work with them from Node.js. A Rust and WebAssembly engine reads the file, recalculates formulas and saves losslessly: what it does not touch stays byte by byte. It works with React, Vue, Svelte, Angular or plain HTML, and in desktop apps such as Tauri. It needs no server: the library sends your files nowhere.
|
|
4
4
|
|
|
5
5
|
For AI agents, `wb.area()` gives a table's columns, types and totals without reading the whole sheet, and a change that would break something is refused with the reason, leaving the workbook as it was.
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimarborda/xlsx-editor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Excel (.xlsx) editor for the browser and engine for Node.js: Rust + WebAssembly that edits workbooks without losing what it does not touch, from a canvas grid or from code. An AI agent can read a table's structure without loading the whole sheet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/src/index.d.ts
CHANGED
|
@@ -78,7 +78,7 @@ export interface ChangeDetail {
|
|
|
78
78
|
range: string;
|
|
79
79
|
/** Every cell of `range` with its new value (`null` when empty); empty above 200 000 cells */
|
|
80
80
|
cells: { ref: string; value: CellValue }[];
|
|
81
|
-
source: 'edit' | 'clear' | 'paste' | 'cut' | 'move' | 'insert' | 'delete' | 'fill' | 'replace' | 'undo' | 'redo';
|
|
81
|
+
source: 'edit' | 'clear' | 'paste' | 'cut' | 'move' | 'insert' | 'delete' | 'resize' | 'hide' | 'show' | 'fill' | 'replace' | 'undo' | 'redo';
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
export interface XlsxEditorEventMap {
|
package/src/xlsx_editor.js
CHANGED
|
@@ -992,8 +992,9 @@ export class XlsxEditor extends EventTarget {
|
|
|
992
992
|
/** Sets rows or columns to sizes in pixels, as one undo step: `[[line, px or null]]` */
|
|
993
993
|
#applySizes(axis, sizes) {
|
|
994
994
|
if (!sizes.length) return false;
|
|
995
|
+
const changed = this.#linesRange(axis, sizes[0][0], sizes[sizes.length - 1][0]);
|
|
995
996
|
try {
|
|
996
|
-
this.#workbook.checkpoint(this.#historyToken(
|
|
997
|
+
this.#workbook.checkpoint(this.#historyToken(changed));
|
|
997
998
|
const sheet = this.#activeSheet();
|
|
998
999
|
// Runs of lines of one size are one call
|
|
999
1000
|
for (let i = 0; i < sizes.length;) {
|
|
@@ -1010,11 +1011,18 @@ export class XlsxEditor extends EventTarget {
|
|
|
1010
1011
|
this.reload();
|
|
1011
1012
|
return false;
|
|
1012
1013
|
}
|
|
1013
|
-
this.#workbook.setSelectionAfter(this.#historyToken(
|
|
1014
|
+
this.#workbook.setSelectionAfter(this.#historyToken(changed));
|
|
1014
1015
|
this.reload();
|
|
1016
|
+
this.#emitChange(changed, 'resize');
|
|
1015
1017
|
return true;
|
|
1016
1018
|
}
|
|
1017
1019
|
|
|
1020
|
+
/** The lines `a..=b` as a range, only as far across as the sheet has cells */
|
|
1021
|
+
#linesRange(axis, a, b) {
|
|
1022
|
+
const { maxRow, maxCol } = this.#sheet.used;
|
|
1023
|
+
return axis === 'row' ? rangeOf({ row: a, col: 1 }, { row: b, col: Math.max(maxCol, 1) }) : rangeOf({ row: 1, col: a }, { row: Math.max(maxRow, 1), col: b });
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1018
1026
|
/** Double click: on the edge of a header fits its lines to their content, in a cell edits it */
|
|
1019
1027
|
#onDoubleClick(e) {
|
|
1020
1028
|
if (!this.#sheet) return;
|
|
@@ -1243,17 +1251,19 @@ export class XlsxEditor extends EventTarget {
|
|
|
1243
1251
|
#setLinesHidden(axis, a, b, hidden) {
|
|
1244
1252
|
if (this.#readOnly || !this.#sheet) return false;
|
|
1245
1253
|
this.#commitEdit(null, { focus: false });
|
|
1254
|
+
const changed = this.#linesRange(axis, a, b);
|
|
1246
1255
|
try {
|
|
1247
|
-
this.#workbook.checkpoint(this.#historyToken(
|
|
1256
|
+
this.#workbook.checkpoint(this.#historyToken(changed));
|
|
1248
1257
|
const sheet = this.#activeSheet();
|
|
1249
1258
|
if (axis === 'row') sheet.setRowsHidden(`${a}:${b}`, hidden);
|
|
1250
1259
|
else sheet.setColumnsHidden(`${columnName(a)}:${columnName(b)}`, hidden);
|
|
1251
|
-
this.#workbook.setSelectionAfter(this.#historyToken(
|
|
1260
|
+
this.#workbook.setSelectionAfter(this.#historyToken(changed));
|
|
1252
1261
|
} catch (error) {
|
|
1253
1262
|
this.#fail(error);
|
|
1254
1263
|
return false;
|
|
1255
1264
|
}
|
|
1256
1265
|
this.reload();
|
|
1266
|
+
this.#emitChange(changed, hidden ? 'hide' : 'show');
|
|
1257
1267
|
return true;
|
|
1258
1268
|
}
|
|
1259
1269
|
|
|
@@ -1328,9 +1338,11 @@ export class XlsxEditor extends EventTarget {
|
|
|
1328
1338
|
return { index, first, last, found };
|
|
1329
1339
|
});
|
|
1330
1340
|
const only = blocks.length === 1 && blocks[0].index === this.#sheetIndex ? rangeOf(blocks[0].first, blocks[0].last) : null;
|
|
1341
|
+
// Other sheets too: undoing says what changed in each one
|
|
1342
|
+
const others = only ? null : blocks.map(({ index, first, last }) => [index, rangeOf(first, last)]);
|
|
1331
1343
|
let written = false;
|
|
1332
1344
|
try {
|
|
1333
|
-
this.#workbook.checkpoint(this.#historyToken(only));
|
|
1345
|
+
this.#workbook.checkpoint(this.#historyToken(only, others));
|
|
1334
1346
|
for (const { index, first, last, found } of blocks) {
|
|
1335
1347
|
// Holes leave the cells between the hits as they are
|
|
1336
1348
|
const rows = Array.from({ length: last.row - first.row + 1 }, () => new Array(last.col - first.col + 1));
|
|
@@ -1342,7 +1354,7 @@ export class XlsxEditor extends EventTarget {
|
|
|
1342
1354
|
this.#workbook.sheet(index).setValues(refOf(first), rows);
|
|
1343
1355
|
written = true;
|
|
1344
1356
|
}
|
|
1345
|
-
this.#workbook.setSelectionAfter(this.#historyToken(only));
|
|
1357
|
+
this.#workbook.setSelectionAfter(this.#historyToken(only, others));
|
|
1346
1358
|
} catch (error) {
|
|
1347
1359
|
// All or nothing, also when a later sheet refuses
|
|
1348
1360
|
if (written) this.#workbook.undo();
|
|
@@ -2359,9 +2371,9 @@ export class XlsxEditor extends EventTarget {
|
|
|
2359
2371
|
}
|
|
2360
2372
|
|
|
2361
2373
|
/** What the undo history remembers about a change: the sheet, the selection, the cells */
|
|
2362
|
-
#historyToken(changed) {
|
|
2374
|
+
#historyToken(changed, others = null) {
|
|
2363
2375
|
const sel = this.selection;
|
|
2364
|
-
return JSON.stringify({ sheet: this.#sheetIndex, range: sel?.range, active: sel?.ref, whole: sel?.whole ?? null, changed });
|
|
2376
|
+
return JSON.stringify({ sheet: this.#sheetIndex, range: sel?.range, active: sel?.ref, whole: sel?.whole ?? null, changed, others });
|
|
2365
2377
|
}
|
|
2366
2378
|
|
|
2367
2379
|
/** Once the selection has moved after a change, redo puts it back there */
|
|
@@ -2447,6 +2459,9 @@ export class XlsxEditor extends EventTarget {
|
|
|
2447
2459
|
if (whole === 'col') this.#scrollIntoView({ col: active.col });
|
|
2448
2460
|
}
|
|
2449
2461
|
if (token?.changed) this.#emitChange(token.changed, direction);
|
|
2462
|
+
for (const [index, range] of token?.others ?? []) {
|
|
2463
|
+
if (tabs[index]?.kind === 'worksheet') this.#emitChange(range, direction, this.#workbook.sheet(index));
|
|
2464
|
+
}
|
|
2450
2465
|
return true;
|
|
2451
2466
|
}
|
|
2452
2467
|
|