Angular Spreadsheet Component | ng-hub-ui-spreadsheet
Editable Angular spreadsheet with Excel's keyboard, rectangular selection, a clipboard that round-trips with Excel, frozen panes and merged cells. MIT, accessible, no paid tier.
Справочник API
Вот полный контракт spreadsheet: всё, что можно привязать, прослушать, спроецировать и тематизировать, собрано в одном месте. Подключите то, что нужно, и оформите так, как хотите — это standalone и дружит с сигналами.
Входные свойства
Настройте spreadsheet с помощью 31 входных свойств. Привязывайте их, как любой @Input в Angular.
| Имя | Тип | По умолчанию | Описание |
|---|---|---|---|
rowsОбязательно | readonly TRow[] | — | The rows, as you hold them. The sheet never writes to them. |
columnsОбязательно | readonly HubSpreadsheetColumn<TRow>[] | — | What each column shows and allows. |
rowKeyОбязательно | (row: TRow) => string | — | A stable name per row, so a save state follows its row across a reload. |
errors | Record<string, string> | {} | A validation message per cell, keyed like `states`. Owned by the host, because validation belongs to whoever knows what the data means. |
states | Record<string, HubSpreadsheetCellState> | {} | Save state per cell, keyed by the row key and the column alias joined by a tab. |
decimalMark | ',' | '.' | ',' | The character this reader types decimals with. Only consulted when a pasted value carried no raw number. |
emptyText | string | '' | What an empty sheet says. |
pageSize | number | 10 | How many rows Page Up and Page Down travel. |
readonly | boolean | false | Turns off every editor, whatever the individual cells say. |
frozenColumns | number | 0 | How many columns stay pinned to the leading edge, counted the way Excel freezes panes. |
frozenRows | number | 0 | How many rows stay pinned below the header. |
spans | readonly HubGridSpan[] | [] | Merged blocks, each named by its top-left corner and its extent. |
fillHandle | boolean | false | Whether the sheet offers the fill handle at the corner of the selection. |
canUndo | boolean | false | Whether undo is available. The sheet only asks; the owner keeps the history. |
canRedo | boolean | false | Whether redo is available. |
resizableColumns | boolean | false | Whether a reader may drag a column's trailing edge to widen or narrow it. |
reorderableColumns | boolean | false | Whether a reader may drag a header to put the column somewhere else. |
columnWidths | Record<string, number> | {} | The width of each column in pixels, keyed by alias. Two-way: the sheet writes it as the reader drags, and you may persist it. |
minColumnWidth | number | 48 | The narrowest a column may be dragged. |
virtual | boolean | false | Рисует только строки, попадающие в окно просмотра, и резервирует высоту остальных — для листа в тысячи строк. По умолчанию выключено, и за это есть плата: строк вне видимости нет в документе, поэтому поиск браузера и печать достают лишь нарисованное, а строки должны быть одной высоты. Включение также задаёт листу высоту 24rem: растущий свободно, он равен своему содержимому, и виртуализировать нечего. |
rowHeight | number | 0 | Высота строки при виртуализации, в пикселях. Ноль измеряет первую нарисованную строку — это верно, пока строки одинаковы. Укажите её для листа, собираемого скрытым: ни разу не размеченная строка измеряется нулём, а окно, посчитанное от нуля, — это весь лист. |
formulas | boolean | false | Считается ли формулой ячейка, значение которой начинается с =. Выключено, пока не попросят: текстовый столбец вполне может хранить =1+1 как то, что кто-то написал. Формула может обратиться к столбцу по алиасу — =[units] * [price], =SUM([total:]) — и после переноса столбца или переписанного заголовка она значит то же самое, либо по координате, как пишет табличный редактор — =ROUND(SUM(E1:E4), 2) —, а координата говорит, где столбец был, а не какой он; rewriteRowFormulas() переносит такие вместе с листом, а ссылка на удалённое становится #REF!, а не читает втихую соседний. Двоеточие читает весь столбец, исключая спрашивающую ячейку; любой другой круг показывается как #CYCLE!, а не преследуется. |
mergeable | boolean | false | Может ли читатель объединять ячейки и разделять их — предлагается в контекстном меню, так что нужен и contextMenu. Выключено, пока не попросят, как любое изменение структуры. |
editOn | 'click' | 'double-click' | 'double-click' | Что открывает редактор указателем. По умолчанию двойной щелчок — соглашение таблиц: щелчок выбирает ячейку и оставляет её значение в покое, первый набранный символ открывает редактор и попадает в него, а ячейка со списком или датой показывает значок, открывающий её одним щелчком. Значение click открывает с первого щелчка — для листа, который существует, чтобы в нём печатали. В обоих случаях редактор открывается при отпускании и только если указатель не двигался, поэтому выделение диапазона протягиванием работает по-прежнему. |
contextMenu | boolean | false | Whether a right-click offers the menu of structural changes. Each entry still has to pass the permissions, and the menu is never opened empty. |
columns[].kind | 'text' | 'number' | 'currency' | 'date' | 'boolean' | 'select' | 'text' | How the column shows its values and reads back what is typed. A select column opens a list and refuses anything not on it; a boolean draws a checkbox that turns over in one click; a date opens a date field. |
columns[].formula | string | — | Формула, из которой выводится каждая ячейка этого столбца, объявленная в коде, а не хранимая в данных: итог строки, налог, текущий остаток. Противоположность формуле, которую набирает читатель, — та является значением строки и меняется как любое другое. Ячейки такого столбца вообще нельзя редактировать: замена одной потеряла бы формулу лишь в этой строке, и этого никто не заметит, пока итоги не перестанут сходиться. Требует formulas на листе. |
columns[].placeholder | string | — | A hint shown in an empty cell of this column while the pointer rests on it. Never in place of a value: a column of hints always on screen reads as a column of content. |
columns[].options | readonly HubSpreadsheetOption[] | — | The answers a select column offers. A cell shows the matching label rather than the stored value, and falls back to the raw value when nothing matches, so a row holding a retired option stays readable. |
structure | HubSpreadsheetStructureOptions<TRow> | {} | Which structural changes the reader may ask for. Everything is refused until allowed. |
retiredColumnKeys | readonly string[] | [] | Aliases of columns that were deleted, so a new one never inherits a dead identity. |
События
Реагируйте на то, что делает spreadsheet, — 12 событий, к которым можно подключить вашу логику.
| Имя | Тип | Описание |
|---|---|---|
commit | HubSpreadsheetCommit<TRow> | A cell took a new value, and it differs from the one it held. |
pasted | HubSpreadsheetPaste<TRow> | A block was pasted, with counts of what fell outside the sheet and what was not a number. |
cleared | HubSpreadsheetCellRef<TRow>[] | Delete was pressed over a selection; carries the editable cells it covered. |
selectionChange | HubGridRange | null | The selected rectangle changed. |
filled | HubSpreadsheetPaste<TRow> | A drag of the fill handle finished, reported as one event for the whole gesture. |
undoRequested | void | The reader asked to undo. |
redoRequested | void | The reader asked to redo. |
columnMoved | { from: number; to: number; key: string; keys: string[] } | A column was dropped somewhere else. Reported, not applied: the payload carries the aliases already reordered. |
mergeRequested | HubSpreadsheetMergeRequest | Читатель попросил объединить выделение. В данных — создаваемый блок и якоря тех, что он поглощает: если просто добавить новый, два блока будут претендовать на одну ячейку. applySpanMerge() делает это одной строкой. |
unmergeRequested | readonly HubGridCoords[] | Читатель попросил разделить блоки, которых касается выделение, названные по их якорям. Применяется через applySpanUnmerge(). |
insertRequested | HubSpreadsheetInsertRequest | The reader asked to add rows or columns. |
deleteRequested | HubSpreadsheetDeleteRequest | The reader asked to remove rows or columns, naming the aliases at stake. |
Методы
Управляйте spreadsheet из кода — 17 методов его программного интерфейса.
| Имя | Сигнатура | Возвращает | Описание |
|---|---|---|---|
sheetToXlsx | sheetToXlsx<TRow>(rows, columns, options?: HubXlsxOptions): Uint8Array | Uint8Array | Лист как настоящий .xlsx, в байтах. Число остаётся числом, а столбец дат становится датой — того CSV не умеет. Записывается то, что показывает ячейка: столбец-список отдаёт свою подпись, формула — свой результат, если только values: 'stored' не потребует хранимого. |
downloadXlsx | downloadXlsx(filename: string, bytes: Uint8Array): void | — | Отдаёт читателю эту книгу на сохранение, добавляя .xlsx к имени, если его нет. Здесь потому, что каждый хост пишет одни и те же шесть строк и забывает освободить object URL. |
xlsxToRecords | xlsxToRecords<TRow>(bytes, columns, options?: HubXlsxReadOptions): Promise<Array<Record<string, string>>> | Promise<Array<Record<string, string>>> | Читает первый лист книги в строки с ключами по алиасам столбцов, отменяя две вещи, которых в CSV не бывает: общие строки и даты, хранимые как число дней. Асинхронная, потому что настоящая книга сжата, а распаковщик берётся у платформы. |
sheetToCsv | sheetToCsv<TRow>(rows, columns, options?: HubCsvOptions): string | string | Лист как CSV. Если разделитель не задан, он выводится из десятичного знака — запятая не может разделять то, что она же и разделяет внутри числа, — поэтому файл для тех, кто пишет 1.234,56, получает точку с запятой. |
downloadText | downloadText(filename: string, text: string, type?: string): void | — | Отдаёт читателю текстовый файл на сохранение, с меткой порядка байтов, из-за которой табличный редактор откроет его как UTF-8, а не как кракозябры. |
csvToRecords | csvToRecords<TRow>(text, columns, options?: HubCsvOptions): Array<Record<string, string>> | Array<Record<string, string>> | Читает CSV в строки с ключами по алиасам столбцов, определяя разделитель по первой строке, а не предполагая его. Заголовки сопоставляются сперва по заголовку, затем по алиасу, без учёта регистра и пробелов по краям. |
sheetValues | sheetValues<TRow>(rows, columns, options?: HubSheetValuesOptions): HubSpreadsheetValue[][] | HubSpreadsheetValue[][] | Что содержит каждая ячейка — с посчитанными формулами, а неудавшаяся записана так, как её пишет табличный редактор. Именно это читают оба экспорта; открыто для хоста, который пишет собственный файл. |
evaluateSheet | evaluateSheet<TRow>(input: HubFormulaSheetInput<TRow>): HubFormulaSheet | HubFormulaSheet | Выполняет все формулы листа в том порядке, какой требуют зависимости, считая по необходимости и запоминая. Круг замечается в момент замыкания, а не преследуется. Открыто для хоста, которому нужны ответы без отрисовки. |
rewriteRowFormulas | rewriteRowFormulas<TRow extends object>(rows, change: HubFormulaShapeChange): TRow[] | TRow[] | Переносит все координаты в строках, когда лист меняет форму: столбец переехал, строки или столбцы вставлены или удалены. Ссылка на удалённое становится #REF!, а не читает втихую соседний столбец. Вызывает её хост, потому что строки его. |
mergeRequestFor | mergeRequestFor(spans, range): HubSpreadsheetMergeRequest | null | HubSpreadsheetMergeRequest | null | Блок, которым стало бы выделение, и якоря, которые он поглощает; null для одной ячейки. Поглощённые якоря — как раз то, что хост делает неверно сам: добавив блок и не убрав их, получаешь два блока на одну ячейку. |
applySpanMerge | applySpanMerge(spans, request: HubSpreadsheetMergeRequest): HubGridSpan[] | HubGridSpan[] | Список блоков после этого объединения, без поглощённых. |
applySpanUnmerge | applySpanUnmerge(spans, anchors: readonly HubGridCoords[]): HubGridSpan[] | HubGridSpan[] | Список блоков после того, как эти разъединены. |
spreadsheetRecords | spreadsheetRecords<TRow>(rows, columns): HubSpreadsheetRecord[] | HubSpreadsheetRecord[] | Каждая строка как простые данные с ключами по алиасам столбцов, чтобы работать с { price: 12, units: 3 }, а не сопоставлять координаты со своими полями по позиции. |
parseForColumn | parseForColumn<TRow>(text, column, decimalMark: ',' | '.'): HubSpreadsheetValue | undefined | HubSpreadsheetValue | undefined | Чего стоит в столбце набранная или вставленная строка: значение, null для пустой ячейки и undefined для того, чего столбец держать не может, — чтобы вызывающий мог отказать, а не сохранить мусор. |
nextColumnKey | nextColumnKey(taken: Iterable<string>, prefix?: string): string | string | Алиас, который никогда не принадлежал другому столбцу: новый столбец не переиспользует отставное имя, и ни одна сохранённая ссылка не сможет указать не туда. |
danglingColumnKeys | danglingColumnKeys<TRow>(referenced: Iterable<string>, columns): string[] | string[] | Ссылки, которые больше не ведут ни к одному столбцу, — то есть то, что удаление оставило бы висеть в пустоте. |
provideHubSpreadsheetControls | provideHubSpreadsheetControls(adapter: HubSpreadsheetControlsAdapter): EnvironmentProviders | EnvironmentProviders | Регистрирует адаптер контролов, и тогда каждый столбец-список открывается через него: с поиском, уже раскрытым, и с символом, открывшим ячейку, в поле поиска. Без адаптера лист рисует свой родной список — именно это и позволяет ставить библиотеку саму по себе. |
Шаблоны
Сделайте его своим — 2 слотов шаблона позволяют проецировать собственную разметку.
| Имя | Описание | Пример |
|---|---|---|
hubSpreadsheetEditor | Заменяет редактор одного столбца, названного по его алиасу. Шаблон получает значение, пару commit и cancel и `seed` — символ, которым читатель открыл ячейку, начав печатать; ему место в поле поиска выбиралки. Шаблон остаётся открытым, пока не вызовет одно из двух. Лист не зависит от того, что вы в него положите. | <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> |
hubSpreadsheetCell | Рисуйте ячейки столбца сами, пока их не редактируют: значок для состояния, аватар рядом с именем, ряд действий. Пара к hubSpreadsheetEditor, который закрывает другую половину жизни ячейки. В буфер обмена по-прежнему уходит значение, а не рисунок, и клавиатура по-прежнему стоит на ячейке. Передайте строки через hubSpreadsheetCellRows — и компилятор будет знать, что такое `row`. | <ng-template hubSpreadsheetCell="state" let-value>
<hub-badge variant="soft" [color]="colourOf(value)">{{ labelOf(value) }}</hub-badge>
</ng-template> |