Componente de hoja de cálculo para Angular | ng-hub-ui-spreadsheet

Hoja de cálculo editable para Angular con el teclado de Excel, selección rectangular, portapapeles que va y viene con Excel, paneles congelados y celdas combinadas. MIT, accesible y sin funciones de pago.

Referencia API

Aquí tienes el contrato completo de spreadsheet: todo lo que puedes enlazar, escuchar, proyectar y personalizar, reunido en un solo sitio. Enlaza lo que necesites y dale el estilo que quieras — es standalone y reactivo con signals.

Inputs

Ajusta spreadsheet a tu gusto con 31 inputs. Se enlazan como cualquier @Input de Angular.

NombreTipoPor defectoDescripción
rowsObligatorioreadonly TRow[]—Las filas, tal como las tienes. La hoja nunca escribe en ellas.
columnsObligatorioreadonly HubSpreadsheetColumn<TRow>[]—Qué muestra y qué permite cada columna.
rowKeyObligatorio(row: TRow) => string—Un nombre estable por fila, para que el estado de guardado la siga tras una recarga.
errorsRecord<string, string>{}Un mensaje de validación por celda, con la misma clave que `states`. Lo tiene quien usa la hoja, porque validar es de quien sabe qué significan los datos.
statesRecord<string, HubSpreadsheetCellState>{}Estado de guardado por celda, con la clave de la fila y el alias de la columna unidos por un tabulador.
decimalMark',' | '.'','Con qué carácter escribe los decimales este lector. Solo se consulta cuando lo pegado no traía número crudo.
emptyTextstring''Qué dice una hoja vacía.
pageSizenumber10Cuántas filas recorren Re Pág y Av Pág.
readonlybooleanfalseDesactiva todos los editores, digan lo que digan las celdas.
frozenColumnsnumber0Cuántas columnas quedan fijadas al margen inicial, contadas como congela paneles Excel.
frozenRowsnumber0Cuántas filas quedan fijadas bajo la cabecera.
spansreadonly HubGridSpan[][]Bloques combinados, cada uno con su esquina superior izquierda y su extensión.
fillHandlebooleanfalseSi la hoja ofrece el tirador de relleno en la esquina de la selección.
canUndobooleanfalseSi se puede deshacer. La hoja solo pregunta; el histórico lo tiene quien la usa.
canRedobooleanfalseSi se puede rehacer.
resizableColumnsbooleanfalseSi el lector puede arrastrar el borde de una columna para ensancharla o estrecharla.
reorderableColumnsbooleanfalseSi el lector puede arrastrar una cabecera para llevarse la columna a otro sitio.
columnWidthsRecord<string, number>{}El ancho de cada columna en píxeles, con el alias por clave. De dos sentidos: la hoja lo escribe al arrastrar y tú puedes guardarlo.
minColumnWidthnumber48Lo más estrecha que se puede dejar una columna.
virtualbooleanfalseDibuja solo las filas que cubre la ventana y reserva la altura del resto, para una hoja de miles. Apagado por omisión, y tiene coste: las filas fuera de la vista no están en el documento, así que la búsqueda del navegador y la impresión solo alcanzan lo dibujado, y las filas tienen que medir lo mismo. Activarlo además le da a la hoja una altura de 24rem, porque una hoja que se deja crecer mide lo que su contenido y entonces no hay nada que virtualizar.
rowHeightnumber0Cuánto mide una fila al virtualizar, en píxeles. Cero mide la primera fila dibujada, que es lo correcto cuando son iguales. Decláralo si la hoja se construye oculta: una fila que nunca se ha maquetado mide cero, y una ventana calculada desde cero es la hoja entera.
formulasbooleanfalseSi una celda cuyo valor empieza por = es una fórmula. Apagado salvo que se pida: una columna de texto puede llevar perfectamente =1+1 como lo que alguien escribió. Una fórmula puede nombrar una columna por su alias —=[units] * [price], =SUM([total:])—, que sigue queriendo decir lo mismo después de mover una columna o reescribir una cabecera, o por coordenada como se escribe en una hoja de cálculo —=ROUND(SUM(E1:E4), 2)—, que dice dónde estaba una columna y no cuál es; rewriteRowFormulas() mueve esas con la hoja, y una referencia a algo borrado pasa a ser #REF! en lugar de leer en silencio a su vecina. Los dos puntos leen la columna entera dejando fuera la celda que pregunta; cualquier otro círculo se muestra como #CYCLE! en lugar de perseguirse.
mergeablebooleanfalseSi el lector puede combinar celdas y separarlas, desde el menú contextual —así que necesita también contextMenu—. Desactivado salvo que se pida, como todo cambio de estructura.
editOn'click' | 'double-click''double-click'Qué abre el editor con el puntero. Doble clic por omisión, la convención de las hojas de cálculo: un clic elige la celda y deja su valor en paz, el primer carácter que se teclea abre el editor y cae dentro, y una celda con lista o fecha enseña una marca que la abre de un solo clic. Con click se abre al primero, para una hoja que existe para escribir en ella. En ambos casos el editor abre al soltar y solo si el puntero no se ha movido, así que arrastrar un rango sigue funcionando.
contextMenubooleanfalseSi el botón derecho ofrece el menú de cambios de estructura. Cada entrada tiene que pasar los permisos, y el menú nunca se abre vacío.
columns[].kind'text' | 'number' | 'currency' | 'date' | 'boolean' | 'select''text'Cómo muestra la columna sus valores y cómo lee lo que se escribe. Una columna de lista abre un desplegable y rechaza lo que no esté en él; una de sí/no dibuja una casilla que se cambia de un clic; una de fecha abre un campo de fecha.
columns[].formulastring—Una fórmula de la que sale cada celda de esta columna, declarada en código y no guardada en los datos: un total de línea, un impuesto, un saldo acumulado. Es lo contrario de una fórmula que escribe el lector, que es un valor que la fila tiene y se cambia como cualquier otro. Las celdas de una columna así no se pueden editar: sustituir una perdería la fórmula solo en esa fila, y eso no se nota hasta que los totales dejan de cuadrar. Necesita formulas en la hoja.
columns[].placeholderstring—Un aviso que sale en una celda vacía de esta columna mientras el puntero está encima. Nunca en lugar de un valor: una columna de avisos siempre visibles se lee como una columna con contenido.
columns[].optionsreadonly HubSpreadsheetOption[]—Las respuestas que ofrece una columna de lista. La celda muestra la etiqueta que corresponde y no el valor guardado, y si no encaja ninguna muestra el valor en crudo, para que una fila con una opción retirada siga leyéndose.
structureHubSpreadsheetStructureOptions<TRow>{}Qué cambios de estructura puede pedir el lector. Todo se rechaza hasta que se permite.
retiredColumnKeysreadonly string[][]Alias de columnas borradas, para que una nueva no herede una identidad muerta.

Outputs

Reacciona a lo que hace spreadsheet: 12 eventos a los que enganchar tu lógica.

NombreTipoDescripción
commitHubSpreadsheetCommit<TRow>Una celda toma un valor nuevo, distinto del que tenía.
pastedHubSpreadsheetPaste<TRow>Se pegó un bloque, con la cuenta de lo que cayó fuera y de lo que no era número.
clearedHubSpreadsheetCellRef<TRow>[]Se pulsó Supr sobre una selección; trae las celdas editables que cubría.
selectionChangeHubGridRange | nullCambió el rectángulo seleccionado.
filledHubSpreadsheetPaste<TRow>Terminó un arrastre del tirador, avisado como un solo evento para todo el gesto.
undoRequestedvoidEl lector pidió deshacer.
redoRequestedvoidEl lector pidió rehacer.
columnMoved{ from: number; to: number; key: string; keys: string[] }Se soltó una columna en otro sitio. Se avisa, no se aplica: el aviso trae los alias ya reordenados.
mergeRequestedHubSpreadsheetMergeRequestEl lector pide combinar una selección. Lleva el bloque que se crea y las anclas de los que absorbe, porque añadirlo sin quitar esos deja dos bloques reclamando la misma celda. applySpanMerge() lo resuelve en una línea.
unmergeRequestedreadonly HubGridCoords[]El lector pide separar los bloques que toca su selección, nombrados por sus anclas. applySpanUnmerge() lo aplica.
insertRequestedHubSpreadsheetInsertRequestEl lector pidió añadir filas o columnas.
deleteRequestedHubSpreadsheetDeleteRequestEl lector pidió quitar filas o columnas, nombrando los alias en juego.

Métodos

Controla spreadsheet desde el código — 17 métodos en su superficie programática.

NombreFirmaDevuelveDescripción
sheetToXlsxsheetToXlsx<TRow>(rows, columns, options?: HubXlsxOptions): Uint8ArrayUint8ArrayLa hoja como un .xlsx de verdad, en bytes. Los números siguen siendo números y una columna de fecha es una fecha, que es lo que un CSV no sabe llevar. Escribe lo que muestra la celda —una columna de lista exporta su etiqueta; una fórmula, su resultado— salvo que se pida values: 'stored'.
downloadXlsxdownloadXlsx(filename: string, bytes: Uint8Array): void—Le da ese libro al lector para que lo guarde, añadiendo .xlsx al nombre si le falta. Está aquí porque todos los anfitriones escriben las mismas seis líneas y se olvidan de liberar la URL del objeto.
xlsxToRecordsxlsxToRecords<TRow>(bytes, columns, options?: HubXlsxReadOptions): Promise<Array<Record<string, string>>>Promise<Array<Record<string, string>>>Lee la primera hoja de un libro y devuelve las filas con los alias de columna por clave, deshaciendo las dos cosas que un CSV nunca hace: las cadenas compartidas y las fechas guardadas como número de días. Es asíncrona porque un libro de verdad viene comprimido y el descompresor lo trae la plataforma.
sheetToCsvsheetToCsv<TRow>(rows, columns, options?: HubCsvOptions): stringstringLa hoja como CSV. Si no se dice el separador, sale de la marca decimal —una coma no puede separar lo que también puntúa—, así que un archivo que se va a leer donde se escribe 1.234,56 lleva punto y coma.
downloadTextdownloadText(filename: string, text: string, type?: string): void—Le da al lector un archivo de texto para guardar, con la marca que hace que una hoja de cálculo lo abra como UTF-8 y no como galimatías.
csvToRecordscsvToRecords<TRow>(text, columns, options?: HubCsvOptions): Array<Record<string, string>>Array<Record<string, string>>Lee un CSV y devuelve las filas con los alias de columna por clave, olfateando el separador en la primera línea en lugar de suponerlo. Las cabeceras se emparejan primero por cabecera y después por alias, sin distinguir mayúsculas ni espacios.
sheetValuessheetValues<TRow>(rows, columns, options?: HubSheetValuesOptions): HubSpreadsheetValue[][]HubSpreadsheetValue[][]Lo que tiene cada celda, con las fórmulas ya resueltas y las que fallan escritas como las escribe una hoja de cálculo. Es lo que leen los dos exportadores, expuesto para quien escriba su propio archivo.
evaluateSheetevaluateSheet<TRow>(input: HubFormulaSheetInput<TRow>): HubFormulaSheetHubFormulaSheetEjecuta todas las fórmulas de una hoja en el orden que exijan las dependencias, con el resultado calculado a demanda y recordado. Un círculo se detecta en cuanto se cierra, en lugar de perseguirse. Se exporta para quien quiera las respuestas sin dibujarlas.
rewriteRowFormulasrewriteRowFormulas<TRow extends object>(rows, change: HubFormulaShapeChange): TRow[]TRow[]Mueve todas las coordenadas de las filas cuando la hoja cambia de forma: una columna movida, o filas y columnas insertadas o borradas. Una referencia a algo borrado pasa a ser #REF! en lugar de leer en silencio a su vecina. La llama el anfitrión, porque las filas son suyas.
mergeRequestFormergeRequestFor(spans, range): HubSpreadsheetMergeRequest | nullHubSpreadsheetMergeRequest | nullEl bloque en que se convertiría una selección, y las anclas que absorbe; null si es una sola celda. Las anclas absorbidas son la parte que se hace mal a mano: si se añade sin quitarlas, dos bloques reclaman la misma celda.
applySpanMergeapplySpanMerge(spans, request: HubSpreadsheetMergeRequest): HubGridSpan[]HubGridSpan[]La lista de bloques después de esa combinación, sin los que absorbe.
applySpanUnmergeapplySpanUnmerge(spans, anchors: readonly HubGridCoords[]): HubGridSpan[]HubGridSpan[]La lista de bloques después de separar esos.
spreadsheetRecordsspreadsheetRecords<TRow>(rows, columns): HubSpreadsheetRecord[]HubSpreadsheetRecord[]Todas las filas como datos planos con los alias de columna por clave, para trabajar con { price: 12, units: 3 } en lugar de emparejar coordenadas con los campos propios por posición.
parseForColumnparseForColumn<TRow>(text, column, decimalMark: ',' | '.'): HubSpreadsheetValue | undefinedHubSpreadsheetValue | undefinedLo que vale en una columna un texto escrito o pegado: el valor, null si la celda está vacía y undefined si es algo que la columna no puede tener, con lo que quien llama puede rechazarlo en vez de guardar basura.
nextColumnKeynextColumnKey(taken: Iterable<string>, prefix?: string): stringstringUn alias que no ha sido nunca de otra columna, así que una columna nueva no reutiliza un nombre retirado y ninguna referencia guardada puede acabar apuntando a otra cosa.
danglingColumnKeysdanglingColumnKeys<TRow>(referenced: Iterable<string>, columns): string[]string[]Las referencias que ya no apuntan a ninguna columna, que es lo que un borrado dejaría colgando.
provideHubSpreadsheetControlsprovideHubSpreadsheetControls(adapter: HubSpreadsheetControlsAdapter): EnvironmentProvidersEnvironmentProvidersRegistra un adaptador de controles, y entonces todas las columnas de lista se abren con él: con búsqueda, ya desplegado y con el carácter que abrió la celda en su caja de búsqueda. Sin adaptador la hoja dibuja su lista nativa, que es lo que la deja instalable por su cuenta.

Templates

Hazlo tuyo — 2 plantillas para proyectar tu propio marcado.

NombreDescripciónEjemplo
hubSpreadsheetEditorSustituye el editor de una columna, nombrada por su alias. La plantilla recibe el valor, el par confirmar y cancelar, y `seed`: el carácter con el que el lector abrió la celda al escribir, que es lo que va en el buscador de un selector. Permanece abierta hasta que llama a uno de los dos. La hoja no depende de lo que pongas dentro.<ng-template hubSpreadsheetEditor="assignee" let-value let-seed="seed" let-commit="commit"> <hub-select [items]="people" autoOpen [initialSearchTerm]="seed ?? null" [ngModel]="value" (ngModelChange)="commit($event)" /> </ng-template>
hubSpreadsheetCellDibuja tú las celdas de una columna mientras no se editan: una insignia para un estado, un avatar junto a un nombre, una fila de acciones. Es el hermano de hubSpreadsheetEditor, que cubre la otra mitad de la vida de una celda. El portapapeles sigue llevando el valor y no el dibujo, y la celda sigue siendo lo que tiene el teclado. Pasando las filas por hubSpreadsheetCellRows, el compilador sabe qué es `row`.<ng-template hubSpreadsheetCell="state" let-value> <hub-badge variant="soft" [color]="colourOf(value)">{{ labelOf(value) }}</hub-badge> </ng-template>