@dimarborda/xlsx-editor 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +264 -2
- package/package.json +64 -4
- package/src/draw.js +389 -0
- package/src/elements.js +86 -0
- package/src/engine.d.ts +320 -0
- package/src/engine.js +403 -0
- package/src/fonts.js +46 -0
- package/src/grid.js +149 -0
- package/src/index.d.ts +127 -0
- package/src/index.js +11 -0
- package/src/input.js +260 -0
- package/src/style.css +161 -0
- package/src/xlsx_editor.js +1106 -0
- package/wasm/xlsx_engine.d.ts +196 -0
- package/wasm/xlsx_engine.js +817 -0
- package/wasm/xlsx_engine_bg.wasm +0 -0
- package/wasm/xlsx_engine_bg.wasm.d.ts +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dimar Borda
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,265 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @dimarborda/xlsx-editor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Componente para ver y editar libros de Excel (`.xlsx`) en el navegador, dibujados en canvas como los muestra Excel. Por dentro usa un motor escrito en Rust y compilado a WebAssembly, que lee el archivo, resuelve estilos y formatos de número, recalcula fórmulas y guarda sin pérdidas. Ese motor también se usa sin interfaz, en Node.js, desde [`@dimarborda/xlsx-editor/engine`](#motor-sin-interfaz-nodejs). Comparte con [`@dimarborda/docx-editor`](../docx-editor) la capa de paquete OOXML (`crates/ooxml-core`). Funciona en cualquier frontend (HTML, React, Vue, Svelte, Angular, Tauri) y nada sale del navegador.
|
|
4
|
+
|
|
5
|
+
> **Estado: fase 2, edición de celdas.** Se escriben valores y fórmulas en la cuadrícula, se borran, se copian y pegan, y se deshacen. Las fórmulas habituales se recalculan al momento (ver [Limitaciones](#limitaciones)). Con el atributo `readonly` sigue siendo un visor.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @dimarborda/xlsx-editor
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```html
|
|
12
|
+
<xlsx-editor src="/informe.xlsx" locale="es" style="height: 80vh"></xlsx-editor>
|
|
13
|
+
<script type="module">
|
|
14
|
+
import '@dimarborda/xlsx-editor';
|
|
15
|
+
import '@dimarborda/xlsx-editor/fonts'; // opcional: sustitutos de Calibri, Arial…
|
|
16
|
+
import '@dimarborda/xlsx-editor/style.css';
|
|
17
|
+
</script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Qué muestra
|
|
21
|
+
|
|
22
|
+
- **Celdas como en Excel:** fuentes, negrita, cursiva, subrayado, tachado, colores (incluidos los del tema y sus tonos), rellenos y bordes con todos sus estilos.
|
|
23
|
+
- **Formatos de número:** separadores según el idioma, moneda, porcentajes, notación científica, fracciones, secciones y condiciones, colores como `[Red]`, y fechas y horas con nombres de meses y días en español o inglés.
|
|
24
|
+
- **Alineación:** la de Excel por defecto (números a la derecha, texto a la izquierda), horizontal y vertical, sangría, ajuste de texto, texto que se desborda sobre celdas vacías y `####` en los números que no caben.
|
|
25
|
+
- **Cuadrícula:** anchos de columna y altos de fila del archivo, filas y columnas ocultas, celdas combinadas, paneles inmovilizados y líneas de cuadrícula (o su ausencia).
|
|
26
|
+
- **Navegación:** desplazamiento fluido en hojas grandes (solo se dibuja lo visible), zoom, pestañas de hojas, selección con ratón y teclado, y una barra con la celda activa y su contenido o fórmula.
|
|
27
|
+
|
|
28
|
+
Aún no se dibujan gráficos, imágenes, formato condicional ni comentarios, y el texto girado se dibuja horizontal. Las fórmulas se recalculan tras cada cambio con el motor del paquete; las que usan algo que no admite muestran el último valor que guardó Excel.
|
|
29
|
+
|
|
30
|
+
## Edición
|
|
31
|
+
|
|
32
|
+
| Acción | Cómo |
|
|
33
|
+
| :--- | :--- |
|
|
34
|
+
| Escribir en una celda | Empieza a escribir (sustituye el contenido), pulsa **F2** o haz doble clic (edita el contenido actual), o escribe en la barra de celda. |
|
|
35
|
+
| Confirmar | **Enter** (baja), **Mayús+Enter** (sube), **Tab** / **Mayús+Tab** (derecha / izquierda). Si empezaste a escribir sin F2, las flechas también confirman y mueven; **F2** alterna entre ese modo y mover el cursor dentro del texto. |
|
|
36
|
+
| Rellenar la selección | **Ctrl/Cmd+Enter** escribe lo mismo en todas las celdas seleccionadas. |
|
|
37
|
+
| Salto de línea en la celda | **Alt+Enter**. |
|
|
38
|
+
| Cancelar | **Escape**. |
|
|
39
|
+
| Borrar | **Supr** o **Retroceso** vacían el rango seleccionado; el formato de las celdas se queda. |
|
|
40
|
+
| Copiar, cortar y pegar | **Ctrl/Cmd+C**, **X** y **V**, como texto separado por tabulaciones (TSV), el mismo que intercambian Excel y Google Sheets. Al copiar se copian los textos que muestran las celdas; si se pega en el mismo editor lo que se acaba de copiar, se pegan los valores exactos. Un bloque que cabe un número entero de veces en la selección se repite. Cortar vacía las celdas al momento (no al pegar, como hace Excel). |
|
|
41
|
+
| Deshacer y rehacer | **Ctrl/Cmd+Z**, y **Ctrl/Cmd+Y** o **Cmd+Mayús+Z**. Cada edición, borrado o pegado es un paso; deshacer vuelve a la hoja y a la selección de entonces. |
|
|
42
|
+
|
|
43
|
+
### Qué se guarda al escribir
|
|
44
|
+
|
|
45
|
+
Lo escrito se interpreta como lo hace Excel en el idioma del editor (`locale`; `es` y sus variantes usan las convenciones españolas y el resto las inglesas):
|
|
46
|
+
|
|
47
|
+
| Escribes (`es`) | Escribes (`en`) | Se guarda |
|
|
48
|
+
| :--- | :--- | :--- |
|
|
49
|
+
| `1234,5` · `1.234,5` · `-0,5` · `(300)` · `1,5E+3` | `1234.5` · `1,234.5` · `-0.5` · `(300)` · `1.5E+3` | Número |
|
|
50
|
+
| `1.234,50 €` | `$1,234.50` | Número (el símbolo se descarta) |
|
|
51
|
+
| `15%` · `12,5 %` | `15%` · `12.5 %` | Número dividido por 100 (0,15) |
|
|
52
|
+
| `15/03/2024` · `15-3-24` · `15/3` · `2024-03-15` | `3/15/2024` · `3-15-24` · `3/15` · `2024-03-15` | Fecha (sin año, el actual; años de dos cifras: 00–29 → 2000, 30–99 → 1900) |
|
|
53
|
+
| `15/03/2024 14:30` | `3/15/2024 2:30 PM` | Fecha y hora |
|
|
54
|
+
| `14:30` · `14:30:15` | `2:30 PM` | Hora, como fracción del día |
|
|
55
|
+
| `VERDADERO` · `FALSO` | `TRUE` · `FALSE` | Booleano (sin distinguir mayúsculas) |
|
|
56
|
+
| `'0012` | `'0012` | Texto `0012`: el apóstrofo inicial fuerza texto |
|
|
57
|
+
| `=SUM(A1:A3)` | `=SUM(A1:A3)` | Fórmula (ver Limitaciones) |
|
|
58
|
+
| Cualquier otra cosa | | Texto |
|
|
59
|
+
| (vacío) | | Celda vacía |
|
|
60
|
+
|
|
61
|
+
Los valores se escriben con `sheet.setValues` del motor, que conserva el formato de la celda: un número escrito en una celda con formato de moneda se ve como moneda. A las fechas se les da un formato de fecha basado en el de la celda si no lo tenía. Los porcentajes y las horas se guardan como su valor (0,15 o 0,604…) y se ven con el formato que ya tenga la celda. La misma función está disponible como `parseInput(texto, locale)`.
|
|
62
|
+
|
|
63
|
+
### Limitaciones
|
|
64
|
+
|
|
65
|
+
- **Fórmulas:** lo que empieza por `=` se guarda como fórmula y se calcula, igual que las fórmulas que dependen de ella. Se escriben en la sintaxis en inglés (`=SUM(A1:A3)`, `,` o `;` entre argumentos) en cualquier idioma del editor. Las funciones admitidas están en el [motor](#fórmulas); con otras, la celda muestra el último valor guardado y Excel la calcula al abrir el archivo (`fullCalcOnLoad`).
|
|
66
|
+
- **Formato:** escribir no cambia el formato de número (salvo en fechas), y las columnas no se ensanchan solas: una fecha en una columna estrecha se ve como `####`, como en Excel.
|
|
67
|
+
- No se puede sobrescribir la celda de origen de una fórmula compartida (la que usan otras celdas); el editor emite `error` y deja el libro como estaba.
|
|
68
|
+
|
|
69
|
+
### Áreas y contexto para agentes
|
|
70
|
+
|
|
71
|
+
- `editor.selectArea(destino)`: selecciona un área y muestra su hoja. Acepta una tabla o un rango con nombre, una dirección (`Ventas!A1:H40`) o un rango de la hoja activa. Devuelve el área (ver [Áreas para agentes](#áreas-para-agentes)).
|
|
72
|
+
- `editor.selectionContext({ maxRows })`: contexto de lo seleccionado para un agente: el rango, o con una sola celda la región que la rodea (su tabla, o el bloque que seleccionaría Ctrl+A). Devuelve título, cabecera, tipos y totales por columna, las primeras filas y una tabla Markdown.
|
|
73
|
+
|
|
74
|
+
Para dar formato desde código se usa `editor.workbook.sheet(…).setFormat(…)` y después `editor.reload()`.
|
|
75
|
+
|
|
76
|
+
## Atributos
|
|
77
|
+
|
|
78
|
+
| Atributo | Para qué |
|
|
79
|
+
| :--- | :--- |
|
|
80
|
+
| `src` | URL del `.xlsx` |
|
|
81
|
+
| `blank` | Empieza con un libro vacío |
|
|
82
|
+
| `locale` | Idioma de los formatos (`es`, `en-US`…); por defecto, el de la página |
|
|
83
|
+
| `sheet` | Hoja inicial, por nombre o posición |
|
|
84
|
+
| `zoom` | 0.25–4 |
|
|
85
|
+
| `tabs="false"` | Oculta las pestañas |
|
|
86
|
+
| `formula-bar="false"` | Oculta la barra de celda |
|
|
87
|
+
| `readonly` | Modo visor: se selecciona y se copia, pero no se edita (se puede quitar y poner en cualquier momento) |
|
|
88
|
+
| `quiet` | No escribe los errores en la consola |
|
|
89
|
+
| `wasm-url` | Dónde se sirve `xlsx_engine_bg.wasm`, si el bundler no lo hace |
|
|
90
|
+
|
|
91
|
+
Eventos en el elemento: `ready` (con `detail.editor`), `load`, `sheetchange`, `selectionchange`, `change` y `error`.
|
|
92
|
+
|
|
93
|
+
`change` llega tras cada edición, borrado, pegado, corte, deshacer o rehacer:
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
element.addEventListener('change', ({ detail }) => {
|
|
97
|
+
// detail: { sheet: 'Ventas', index: 0, range: 'B3:C4', source: 'edit' | 'clear' | 'paste' | 'cut' | 'undo' | 'redo',
|
|
98
|
+
// cells: [{ ref: 'B3', value: 1200 }, …] }
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## API
|
|
103
|
+
|
|
104
|
+
```js
|
|
105
|
+
const element = document.querySelector('xlsx-editor');
|
|
106
|
+
element.addEventListener('ready', async ({ detail: { editor } }) => {
|
|
107
|
+
await editor.open(file); // URL, File/Blob, ArrayBuffer o Uint8Array
|
|
108
|
+
editor.setSheet('Resumen');
|
|
109
|
+
editor.select('B3:D7');
|
|
110
|
+
editor.setZoom(1.25);
|
|
111
|
+
|
|
112
|
+
// Cambiar celdas con la API del motor y volver a dibujar
|
|
113
|
+
editor.workbook.fillTemplate({ CLIENTE: 'Acme' });
|
|
114
|
+
editor.workbook.sheet('Resumen').setValues('B2', [[1200, 0.25]]);
|
|
115
|
+
editor.workbook.addSheet('Notas'); // reload() también redibuja las pestañas
|
|
116
|
+
editor.reload();
|
|
117
|
+
|
|
118
|
+
editor.undo(); // también editor.redo(), editor.canUndo, editor.canRedo
|
|
119
|
+
editor.readOnly = true; // lo mismo que el atributo readonly
|
|
120
|
+
|
|
121
|
+
editor.download('informe.xlsx');
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Los cambios hechos desde código con `editor.workbook` no forman un paso de deshacer propio: llama antes a `editor.workbook.checkpoint()` para que lo sean.
|
|
126
|
+
|
|
127
|
+
Sin el elemento: `const editor = await XlsxEditor.create(contenedor, { locale: 'es', readOnly: false })`.
|
|
128
|
+
|
|
129
|
+
## Motor sin interfaz (Node.js)
|
|
130
|
+
|
|
131
|
+
`@dimarborda/xlsx-editor/engine` es el mismo motor sin cuadrícula ni DOM: lee, escribe, recalcula y da formato a libros en Node.js (scripts, servidores, herramientas de agentes) o en el navegador.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm install @dimarborda/xlsx-editor
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
```js
|
|
138
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
139
|
+
import { openWorkbook } from '@dimarborda/xlsx-editor/engine';
|
|
140
|
+
|
|
141
|
+
const wb = await openWorkbook(await readFile('plantilla.xlsx'));
|
|
142
|
+
|
|
143
|
+
// Variables {{CAMPO}} en cualquier texto del libro
|
|
144
|
+
wb.fillTemplate({ CLIENTE: 'Acme S.A.', FECHA: '15/01/2024' });
|
|
145
|
+
|
|
146
|
+
// Filas de datos a partir de una celda
|
|
147
|
+
const hoja = wb.sheet('Ventas');
|
|
148
|
+
hoja.setValues('A2', [
|
|
149
|
+
['Teclado', 3, 29.9, new Date(Date.UTC(2024, 0, 15))],
|
|
150
|
+
['Ratón', 5, 12.5, new Date(Date.UTC(2024, 0, 16))],
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
console.log(hoja.getValue('B10')); // resultado de una fórmula, ya recalculado
|
|
154
|
+
await writeFile('salida.xlsx', wb.toBytes());
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
En Node el motor se carga solo. En el navegador, los bundlers (Vite, webpack 5, Rollup) sirven el `.wasm` junto al JavaScript; si lo sirves desde otro sitio, llama antes a `initEngine(url)`. La entrada principal del paquete también exporta todo esto, así que en el navegador basta con `import { openWorkbook } from '@dimarborda/xlsx-editor'`; `editor.workbook` es un `Workbook` de esta misma API.
|
|
158
|
+
|
|
159
|
+
### API
|
|
160
|
+
|
|
161
|
+
| | |
|
|
162
|
+
|---|---|
|
|
163
|
+
| `openWorkbook(bytes)` / `createWorkbook()` | Abre un `.xlsx` o crea un libro con una hoja vacía. |
|
|
164
|
+
| `wb.sheetNames` | Nombres de las hojas en el orden de las pestañas. |
|
|
165
|
+
| `wb.sheet(nombreOÍndice)` / `wb.sheets` | Una hoja de cálculo (por nombre, sin distinguir mayúsculas, o por posición desde 0) o todas. Las hojas de gráfico no tienen celdas. |
|
|
166
|
+
| `hoja.getCell('B3')` | `{ ref, type, value, formula? }`. `type` es `empty`, `number`, `string`, `boolean`, `date` o `error` (`value` es entonces el código, p. ej. `#DIV/0!`). En las fórmulas, `value` es su resultado y `formula` su texto sin `=` (también en las celdas de una fórmula compartida). |
|
|
167
|
+
| `hoja.getValue('B3')` / `hoja.getValues('A1:C3')` | Solo los valores; las celdas vacías dan `null`. |
|
|
168
|
+
| `hoja.setValue('B3', valor)` | Escribe texto, número, booleano, `Date` o una fórmula (`{ formula: '=SUM(A1:A3)' }`); `null` borra el valor y deja el formato. Las fórmulas que dependen de la celda se recalculan. |
|
|
169
|
+
| `hoja.setValues('A2', filas)` | Escribe una matriz desde la celda indicada, creando filas y celdas en orden. `undefined` deja la celda como está. |
|
|
170
|
+
| `wb.fillTemplate(valores)` | Sustituye `{{CAMPO}}` (se admiten espacios y una pista: `{{ CAMPO }}`, `{{CAMPO: pista}}`) en las cadenas compartidas y en las cadenas en línea; devuelve cuántas sustituyó. Las variables sin valor se quedan como están. |
|
|
171
|
+
| `wb.addSheet(nombre, después?)` | Agrega una hoja vacía detrás de la pestaña `después` (nombre o posición; al final sin ella) y la devuelve. El nombre sigue las reglas de Excel: de 1 a 31 caracteres, sin `[ ] : * ? / \`, sin apóstrofo al principio ni al final, distinto de `History` y de las demás hojas (sin distinguir mayúsculas). |
|
|
172
|
+
| `wb.renameSheet(hoja, nombre)` | Renombra una pestaña (nombre o posición) y las referencias a ella en fórmulas, nombres definidos, validaciones de datos, formato condicional y gráficos; devuelve cuántas cambió. Las referencias dentro de textos (`INDIRECT("Hoja1!A1")`) y a otros libros se quedan como están, igual que en Excel. |
|
|
173
|
+
| `wb.markers({ kind })` | Marcadores del léxico estándar (el mismo de `@dimarborda/docx-editor`) en las celdas de texto de todas las hojas: `field` para `{{CLAVE}}` / `{clave}`, `pending` para `[…]` (contenido por completar). Cada uno con `sheet`, `sheetName`, `ref` (`B3`), `row`, `col`, `raw`, `key`, `hint`, `offset` y `length`. |
|
|
174
|
+
| `wb.checkpoint(selección?)` | Abre un paso de deshacer: los cambios hasta el siguiente `checkpoint` se deshacen juntos. `selección` es un texto libre que `undo()` devuelve. `wb.setSelectionAfter(selección)` guarda la de después, que devuelve `redo()`. |
|
|
175
|
+
| `wb.undo()` / `wb.redo()` | Deshace o rehace un paso: `{ selection, parts }`, o `null` si no hay nada. `wb.canUndo` / `wb.canRedo` dicen si se puede. |
|
|
176
|
+
| `wb.recalculate()` | Recalcula todas las fórmulas y devuelve cuántas celdas cambiaron de valor. |
|
|
177
|
+
| `wb.areas({ sheet })` / `hoja.areas()` | Las áreas del libro (ver [Áreas](#áreas-para-agentes)). |
|
|
178
|
+
| `wb.area(destino, { sheet, maxRows, locale })` / `hoja.area(rango)` | Contexto de un área para un agente. |
|
|
179
|
+
| `hoja.regionAt('B5')` | La región alrededor de una celda, como la selecciona Ctrl+A en Excel (o su tabla). |
|
|
180
|
+
| `hoja.setFormat('A1:C3', formato)` / `hoja.clearFormat(rango)` | Da formato (ver [Formato](#formato)) o lo quita. |
|
|
181
|
+
| `hoja.setColumnWidth('B:D', 14)` | Ancho de columnas en caracteres. |
|
|
182
|
+
| `hoja.mergeCells('A1:H1')` / `hoja.unmergeCells(rango)` | Combina o separa celdas; al combinar, solo la primera puede tener valor. |
|
|
183
|
+
| `wb.toBytes()` | El libro como `.xlsx`. |
|
|
184
|
+
| `wb.close()` | Libera la memoria del motor. |
|
|
185
|
+
|
|
186
|
+
#### Fórmulas
|
|
187
|
+
|
|
188
|
+
Las fórmulas se escriben con `{ formula: '=SUM(B2:B10)*IVA' }` y se calculan en el momento. Cada vez que se escribe una celda (o se rellena una plantilla) se recalculan, en orden de dependencias, todas las fórmulas del libro, y solo se reescribe el `<v>` de las celdas cuyo resultado cambió. `wb.recalculate()` hace lo mismo sin editar nada, por ejemplo para refrescar un libro recién abierto.
|
|
189
|
+
|
|
190
|
+
Se usa la sintaxis en inglés que guarda Excel (`SUM`, `IF`, `,` entre argumentos; también se acepta `;`). Se admiten referencias relativas y absolutas, rangos, columnas completas (`A:A`), otras hojas (`'Mis datos'!B2`), nombres definidos, fórmulas compartidas y matrices constantes (`{1,2;3,4}`), con las conversiones de Excel entre texto, número y booleano y sus códigos de error. Funciones:
|
|
191
|
+
|
|
192
|
+
- **Matemáticas:** `SUM`, `PRODUCT`, `AVERAGE`, `MIN`, `MAX`, `MEDIAN`, `COUNT`, `COUNTA`, `COUNTBLANK`, `ROUND`, `ROUNDUP`, `ROUNDDOWN`, `INT`, `TRUNC`, `ABS`, `SIGN`, `SQRT`, `POWER`, `MOD`, `PI`, `CEILING`, `FLOOR`, `CEILING.MATH`, `FLOOR.MATH`, `SUMPRODUCT`.
|
|
193
|
+
- **Condicionales:** `SUMIF`, `SUMIFS`, `COUNTIF`, `COUNTIFS`, `AVERAGEIF`, `AVERAGEIFS`, `MAXIFS`, `MINIFS` (criterios como `">5"`, `"<>x"`, `"n*"`).
|
|
194
|
+
- **Lógicas e información:** `IF`, `IFS`, `SWITCH`, `IFERROR`, `IFNA`, `AND`, `OR`, `XOR`, `NOT`, `TRUE`, `FALSE`, `ISBLANK`, `ISNUMBER`, `ISTEXT`, `ISNONTEXT`, `ISLOGICAL`, `ISERROR`, `ISERR`, `ISNA`, `NA`.
|
|
195
|
+
- **Búsqueda:** `VLOOKUP`, `HLOOKUP`, `XLOOKUP`, `MATCH`, `INDEX`, `CHOOSE`, `ROW`, `COLUMN`, `ROWS`, `COLUMNS`.
|
|
196
|
+
- **Texto:** `CONCATENATE`, `CONCAT`, `TEXTJOIN`, `LEFT`, `RIGHT`, `MID`, `LEN`, `UPPER`, `LOWER`, `PROPER`, `TRIM`, `SUBSTITUTE`, `REPT`, `EXACT`, `FIND`, `SEARCH`, `VALUE`, `TEXT`.
|
|
197
|
+
- **Fechas:** `TODAY`, `NOW`, `DATE`, `YEAR`, `MONTH`, `DAY`, `WEEKDAY`, `EDATE`, `EOMONTH`, `DAYS`.
|
|
198
|
+
|
|
199
|
+
Una fórmula que use otra cosa (una función que no está en la lista, `OFFSET`, `INDIRECT`, referencias a otros libros o a tablas, fórmulas matriciales, resultados que se desbordan a varias celdas) o que forme una referencia circular conserva el valor que guardó Excel, y las que dependen de ella lo usan. Al abrirse el archivo, Excel la calcula (ver abajo).
|
|
200
|
+
|
|
201
|
+
#### Áreas para agentes
|
|
202
|
+
|
|
203
|
+
Un agente no necesita leer toda la hoja: puede pedir las áreas y luego el contexto de la que le interesa. `wb.areas()` usa lo que el propio archivo marca, en este orden:
|
|
204
|
+
|
|
205
|
+
1. **Tablas** de Excel (Dar formato como tabla, `xl/tables/tableN.xml`): nombre, rango, nombres de columna y fila de totales.
|
|
206
|
+
2. **Nombres definidos** que apuntan a un rango (`Ventas = Datos!$A$3:$H$40`) y el **área de impresión**.
|
|
207
|
+
3. **Filtros** (`autoFilter`) de la hoja.
|
|
208
|
+
4. **Regiones**: bloques de celdas llenas contiguas, como los selecciona Ctrl+A, donde el archivo no marca nada. Las filas iniciales con una sola celda (o una combinada) son el **título**, igual que un texto suelto justo encima; la primera fila de textos seguida de datos (o en negrita) es la **cabecera**.
|
|
209
|
+
|
|
210
|
+
Cada área es `{ kind, name, sheet, sheetName, ref, address, title, headerRow, totalsRow, body, columns }`; `address` (`Ventas!A1:H64`) sirve como destino en cualquier llamada.
|
|
211
|
+
|
|
212
|
+
`wb.area(destino)` acepta el nombre de una tabla o de un rango con nombre, una dirección con hoja o, con `{ sheet }`, un rango de esa hoja. Devuelve la descripción del área y además:
|
|
213
|
+
|
|
214
|
+
- `columnsInfo`: por columna, su nombre, tipo (`number`, `text`, `date`, `mixed`…), cuántas celdas tienen valor, suma, mínimo, máximo y media de los números, y cuántas fórmulas hay con un ejemplo (`D4: =B4*C4`).
|
|
215
|
+
- `rows`: las primeras `maxRows` filas de datos (30 por defecto) con el texto que muestran las celdas, `firstRow`, `rowCount` y `truncated`.
|
|
216
|
+
- `totals`, `merges` y `markdown`: el área como tabla Markdown con números de fila y letras de columna, lista para pasarla a un modelo.
|
|
217
|
+
|
|
218
|
+
#### Formato
|
|
219
|
+
|
|
220
|
+
`hoja.setFormat(rango, formato)` cambia solo lo que se indica y conserva el resto del aspecto de cada celda:
|
|
221
|
+
|
|
222
|
+
| Propiedad | Valor |
|
|
223
|
+
|---|---|
|
|
224
|
+
| `bold`, `italic`, `underline`, `strike` | `true` / `false` |
|
|
225
|
+
| `color` | Color de la letra, `#RRGGBB` |
|
|
226
|
+
| `fontSize`, `fontName` | Puntos; nombre de fuente |
|
|
227
|
+
| `fill` | Fondo `#RRGGBB`; `null` lo quita |
|
|
228
|
+
| `numberFormat` | Código de Excel: `#,##0.00`, `0%`, `dd/mm/yyyy`, `#,##0.00 "€"`… |
|
|
229
|
+
| `align`, `valign`, `wrap` | `left`, `center`, `right`…; `top`, `center`, `bottom`; ajuste de texto |
|
|
230
|
+
| `border` | Un estilo (`'thin'`, `'medium'`, `'thick'`, `'dashed'`, `'dotted'`, `'double'`) en todos los lados, `null` para quitarlos, o `{ style, color, edges }` con `edges`: `all`, `outline`, `inside`, `top`, `bottom`, `left` o `right` |
|
|
231
|
+
|
|
232
|
+
Los formatos nuevos se añaden a `xl/styles.xml` y se reutilizan los iguales que ya existan; nada de lo que ya estaba se reescribe.
|
|
233
|
+
|
|
234
|
+
#### Fechas
|
|
235
|
+
|
|
236
|
+
Excel guarda las fechas como números de serie (días desde 1900, o desde 1904 en libros con ese sistema) que se ven como fecha por el formato de la celda. Al leer, una celda numérica con formato de fecha se devuelve como `Date`; al escribir un `Date`, la celda recibe un formato de fecha basado en el que tenía (misma fuente, relleno y bordes), salvo que ya mostrara fechas.
|
|
237
|
+
|
|
238
|
+
Las fechas de Excel no tienen zona horaria: la hora que muestra Excel se representa como hora **UTC** del `Date`. Escribe con `new Date(Date.UTC(2024, 0, 15))` y lee con `getUTCFullYear()`, `getUTCHours()` o `toISOString()`.
|
|
239
|
+
|
|
240
|
+
### Sin pérdidas
|
|
241
|
+
|
|
242
|
+
Solo se reescriben las partes del archivo que cambian (la hoja editada, las cadenas compartidas y, si hace falta, los estilos), y dentro de ellas solo las celdas escritas: gráficos, imágenes, formatos condicionales, validaciones, nombres definidos y todo lo demás se guarda byte a byte tal como estaba.
|
|
243
|
+
|
|
244
|
+
Cuando un libro cambia, se elimina `xl/calcChain.xml` (Excel lo reconstruye) y se marca `fullCalcOnLoad="1"` para que Excel recalcule las fórmulas al abrirlo: así quedan bien también las que este motor no calcula.
|
|
245
|
+
|
|
246
|
+
Limitaciones actuales:
|
|
247
|
+
|
|
248
|
+
- Las fórmulas fuera de lo admitido (ver [Fórmulas](#fórmulas)) no se recalculan: `getValue` devuelve su último resultado guardado.
|
|
249
|
+
- Las fórmulas se escriben en la sintaxis en inglés (`SUM`, no `SUMA`); los nombres de función en español no se traducen.
|
|
250
|
+
- Escribir en la celda de origen de una fórmula compartida (la que usan otras celdas) da error, para no romper esas celdas.
|
|
251
|
+
- Una variable `{{CAMPO}}` partida entre fragmentos de texto con formatos distintos no se reconoce.
|
|
252
|
+
|
|
253
|
+
## Content Security Policy
|
|
254
|
+
|
|
255
|
+
Como el motor es WebAssembly, el `script-src` de producción necesita `'wasm-unsafe-eval'`.
|
|
256
|
+
|
|
257
|
+
## Desarrollo
|
|
258
|
+
|
|
259
|
+
Desde la raíz del repositorio:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm run build:wasm:xlsx
|
|
263
|
+
npm test -w packages/xlsx-editor
|
|
264
|
+
npm run dev # http://localhost:5173/xlsx.html
|
|
265
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimarborda/xlsx-editor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
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, with a canvas grid as a framework-agnostic web component",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Dimar Borda",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/dimarborda/rust-web-docx.git",
|
|
11
|
+
"directory": "packages/xlsx-editor"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"xlsx",
|
|
15
|
+
"excel",
|
|
16
|
+
"spreadsheet",
|
|
17
|
+
"editor",
|
|
18
|
+
"viewer",
|
|
19
|
+
"formulas",
|
|
20
|
+
"template",
|
|
21
|
+
"webassembly",
|
|
22
|
+
"wasm",
|
|
23
|
+
"rust",
|
|
24
|
+
"openxml",
|
|
25
|
+
"canvas",
|
|
26
|
+
"web-component"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./src/index.d.ts",
|
|
34
|
+
"default": "./src/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./engine": {
|
|
37
|
+
"types": "./src/engine.d.ts",
|
|
38
|
+
"default": "./src/engine.js"
|
|
39
|
+
},
|
|
40
|
+
"./fonts": "./src/fonts.js",
|
|
41
|
+
"./style.css": "./src/style.css"
|
|
42
|
+
},
|
|
43
|
+
"types": "./src/index.d.ts",
|
|
44
|
+
"sideEffects": [
|
|
45
|
+
"./src/index.js",
|
|
46
|
+
"./src/fonts.js",
|
|
47
|
+
"*.css"
|
|
48
|
+
],
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "node --test test/*.test.mjs",
|
|
51
|
+
"prepublishOnly": "node -e \"require('fs').accessSync('wasm/xlsx_engine_bg.wasm')\" || (echo 'Falta el motor WebAssembly: publica con npm run release:xlsx-editor desde la raíz del repositorio' && exit 1)"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"src",
|
|
55
|
+
"wasm",
|
|
56
|
+
"README.md",
|
|
57
|
+
"LICENSE"
|
|
58
|
+
],
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@fontsource/arimo": "^5.3.0",
|
|
61
|
+
"@fontsource/caladea": "^5.3.0",
|
|
62
|
+
"@fontsource/carlito": "^5.3.0",
|
|
63
|
+
"@fontsource/cousine": "^5.3.0",
|
|
64
|
+
"@fontsource/tinos": "^5.3.0"
|
|
65
|
+
}
|
|
66
|
+
}
|