Skip to content
Apertura
API reference

@apertura/xlsx-view

DOM renderer for Excel workbooks

24 exported symbols · 22 declared here · 2 re-exported

Classes

AxisMetrics
class AxisMetrics

One axis of the grid. Ranges must be sorted and must not overlap; the constructor enforces neither because both callers build them in order and the check would be the most expensive thing here.

count
number
total
number
defaultSize
number
size
(index: number) => number
offset
(index: number) => number
Pixel offset of the start of `index`.
indexAt
(position: number) => number
The index whose band contains `position`; clamped to the axis.
CellCssBuilder
class CellCssBuilder
stylesheet
StyleSheetBuilder
appearance
(format: CellFormat) => CellAppearance
The class and the measurement facts for a resolved cell format.
differential
(format: DifferentialFormat) => string | undefined
A class for a differential format, as conditional formatting applies.
ConditionalFormatter
class ConditionalFormatter
empty
boolean
needsEvaluator
boolean
Whether any rule needs a formula engine to decide. An `expression` rule always does. So does a comparison whose operand is not a number, a quoted string or a cell of this sheet — `Sheet2!$A$1` is the common case, and it is common because the schema cannot hold it: a rule that compares across sheets is written in an extension block, and its operand is a formula like any other.
useEvaluator
(predicate: PredicateEvaluator | undefined) => void
Gives the formatter a way to evaluate `expression` rules.
evaluate
(row: number, column: number, cell: Cell | undefined) => ConditionalResult | undefined
SheetGrid
class SheetGrid
element
HTMLElement
selection
GridSelection | undefined
refresh
() => void
Rebuilds the visible window; call after the container was resized.
destroy
() => void
scrollTo
(row: number, column: number) => void
Scrolls until a cell is inside the scrolling pane.
select
(row: number, column: number, extend?: boolean) => void
cellAt
(row: number, column: number) => Cell | undefined
The cell behind a selection, for the formula bar.
setSparklines
(sparklines: ReadonlyMap<number, SparklineCell>) => void
The sparklines of this sheet, by the cell each draws in. Set from outside rather than read here, because the numbers behind a sparkline come from a range that may be on another sheet, and resolving one needs the workbook and the formula engine — neither of which the grid has.
SheetMetrics
class SheetMetrics

The geometry of one sheet at one zoom level.

columns
AxisMetrics
rows
AxisMetrics
zoom
number
XlsxView
class XlsxView extends BaseDocumentView<XlsxDocument>
activeSheetIndex
number
showSheet
(index: number) => Promise<void>
Switches to another sheet.
goToCell
(row: number, column: number) => void
Selects a cell, scrolling it into view.
renderContent
() => Promise<void>
Renders the content into {@link root}. Called on every update.
onContainerResize
() => void
Called when the container is resized. The default is a full re-render. Renderers that can reflow incrementally should override this — a full rebuild on every resize frame is exactly the behaviour that makes viewers feel slow.
setZoom
(zoom: number) => void
Changes the zoom level and re-renders.
update
() => void
Re-render after the container was resized or settings changed.
destroy
() => void
Tear the view down and release resources. The container is left empty.

Functions

cellButton
function cellButton(row: number, column: number, filter: AutoFilter | undefined, validations: readonly DataValidation[]): "filter" | "filtering" | "list" | undefined

The button Excel puts in a cell that opens something. Two of them look alike and mean different things. A filter button sits in the header row of an `autoFilter` range and is always there; the arrow of a list validation belongs to any cell the list covers. Both are what a reader looks for to know a sheet can be filtered or chosen from. A column that is currently filtering is drawn apart from one that merely could, because in Excel it is: the funnel replaces the arrow, and it is the only thing on screen saying rows are missing.

columnWidthPixels
function columnWidthPixels(widthCharacters: number, digitWidth: number): number

A column width in characters converted to pixels. `px = trunc(width * digitWidth) + 5` (MS-OI29500). The truncation is not a rounding choice — it is what makes 8.43 characters come out as exactly 64 pixels, and dropping it puts every column a pixel out.

renderSparkline
function renderSparkline(ownerDocument: Document, group: SparklineGroup, values: readonly (number | undefined)[], box: SparklineBox, color: ColorLookup): SVGElement | undefined

Draws one sparkline into an SVG element. `values` are the numbers the range held, with `undefined` where a cell was empty — which is a hole in the line rather than a zero, unless the group says otherwise.

rotationCss
function rotationCss(rotation: number, lineHeightPx: number): { text: Record<string, string>; cell: Record<string, string>; } | undefined

How a cell turns its text, `xf/alignment/@textRotation`. Excel states one number for two directions and a special case, and the encoding is not an angle: 0 to 90 turn the text anticlockwise, 91 to 180 turn it *clockwise* by what is left after ninety — so 180 is a quarter turn down to the right, not half a turn — and 255 stacks the letters one under another without turning them at all. A turn is about the corner the line starts from: rising to the right it starts at the bottom left of the cell, falling to the right at the top left. Turning about the middle instead swings half the line out through the edge, and the cell clips it — a header reading `tated` where the file said `Rotated`. The turn also carries the line *left* by its own height, since the box pivots about a corner it no longer occupies, so the line is pushed back by that much first: without it a quarter turn leaves the text entirely outside its cell, and at forty-five degrees it eats the first letter.

shapeCssfrom @apertura/chart
function shapeCss(shape: ShapeLook, box: { width: number; height: number; }, themeColor: ThemeColorLookup): ShapeCss

The declarations that draw a shape. The geometry is honoured as far as CSS can take it: a rectangle is a rectangle, rounded corners are a radius, an ellipse is a radius of half. Everything else is drawn as its bounding rectangle — which the corpus says is a fair trade, since of the 2 862 shapes in it 2 827 are plain rectangles and 34 are rounded ones.

Interfaces

CellAppearance
interface CellAppearance

Everything a cell's class has to encode, beyond its format.

className
string | undefined
The class name, or `undefined` when the format needs no rule at all.
wrap
boolean
Whether the format wraps, which changes how the text is laid out.
fontFamily
string
Font as the measurer wants it, for the fit and overflow decisions.
fontSizePx
number
bold
boolean
italic
boolean
horizontal
import("/repo/packages/xlsx/dist/index").HorizontalAlignment
indentPx
number
rotation
number
ConditionalResult
interface ConditionalResult

What the rules decided about one cell.

className
string | undefined
Class of the differential format, when a rule matched.
bar
{ start: number; width: number; color: string; border: string | undefined; gradient: boolean; axis: { at: number; color: string; } | undefined; showValue: boolean; } | undefined
A data bar, as fractions of the cell's width. `start` and `width` rather than a length alone, because a bar does not always begin at the edge: where the values cross zero the bar grows out of an axis somewhere inside the cell, and the negative ones grow the other way.
scaleColor
string | undefined
Background from a colour scale.
icon
string | undefined
Icon glyph from an icon set.
hideValue
boolean
Whether the rule hides the value and shows only its decoration.
GridOptions
interface GridOptions
data
SheetData
metrics
SheetMetrics
styles
Stylesheet
css
CellCssBuilder
conditional
ConditionalFormatter
measurer
TextMetricsCache
date1904
boolean
showGridLines
boolean
showHeaders
boolean
zoom
number
imageUrl
(partName: string) => string | undefined
Resolves an image part to a URL the browser can load.
onSelect
(selection: GridSelection) => void
GridSelection
interface GridSelection
active
{ row: number; column: number; }
range
CellRange
MetricsOptions
interface MetricsOptions
zoom
number
digitWidth
number
Width of the digit zero in the workbook's default font, in pixels.
minimumColumns
number
Lowest number of columns and rows to lay out, so a small sheet still fills the view.
minimumRows
number
SizeRange
interface SizeRange

A run of consecutive indices that share a size.

from
number
to
number
size
number
Size in pixels; zero means hidden.
SparklineCell
interface SparklineCell

One sparkline ready to draw: the look it belongs to, and its numbers.

group
SparklineGroup
values
readonly (number | undefined)[]
XlsxViewOptions
interface XlsxViewOptions extends ViewOptions
formulaBar?
boolean | undefined
Show the formula bar above the grid. Defaults to true.
sheetTabs?
boolean | undefined
Show the sheet tabs. Defaults to true.
headers?
boolean | undefined
Show the row and column headers. Defaults to the sheet's own setting.
onSelectionChange?
((selection: GridSelection, sheetIndex: number) => void) | undefined
Called when the reader selects a different cell or range.
onSheetChange?
((index: number) => void) | undefined

Type aliases

ShapeCssfrom @apertura/chart
type ShapeCss = Partial<Record<string, string>>

Values

DEFAULT_COLUMN_WIDTH_CHARACTERS
DEFAULT_COLUMN_WIDTH_CHARACTERS: 8.43

Excel's default column width, in pixels. The file says "8.43 characters" and means the width of the digit zero in the workbook's Normal font, plus five pixels of padding. For Calibri 11 that digit is seven pixels wide, which is where the familiar 64 comes from.

DEFAULT_DIGIT_WIDTH
DEFAULT_DIGIT_WIDTH: 7
XLSX_VIEW_CSS
XLSX_VIEW_CSS: "\n.jo-xl {\n --jo-xl-line: #d0d7de;\n --jo-xl-header-bg: #f5f5f5;\n --jo-xl-header-fg: #575757;\n --jo-xl-surface: #ffffff;\n --jo-xl-text: #1f2328;\n --jo-xl-accent: #217346;\n --jo-xl-selection: rgba(33, 115, 70, 0.12);\n\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n background: var(--jo-xl-surface);\n color: var(--jo-xl-text);\n font-family: Calibri, 'Segoe UI', system-ui, sans-serif;\n font-size: 11pt;\n --jo-xl-row-header-width: 46px;\n}\n\n/* ------------------------------------------------------------- formula bar */\n\n.jo-xl__formula-bar {\n display: flex;\n align-items: stretch;\n gap: 0;\n flex: 0 0 auto;\n border-bottom: 1px solid var(--jo-xl-line);\n background: var(--jo-xl-surface);\n font-size: 12px;\n}\n\n.jo-xl__name-box {\n width: 140px;\n padding: 4px 8px;\n border-right: 1px solid var(--jo-xl-line);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.jo-xl__fx {\n padding: 4px 10px;\n color: var(--jo-xl-header-fg);\n font-style: italic;\n border-right: 1px solid var(--jo-xl-line);\n}\n\n.jo-xl__formula {\n flex: 1;\n padding: 4px 8px;\n white-space: pre;\n overflow: hidden;\n text-overflow: ellipsis;\n font-family: Consolas, 'SF Mono', monospace;\n}\n\n/* -------------------------------------------------------------------- grid */\n\n.jo-xl__host {\n flex: 1 1 auto;\n min-height: 0;\n position: relative;\n}\n\n.jo-xl__grid {\n display: grid;\n width: 100%;\n height: 100%;\n overflow: hidden;\n position: relative;\n background: var(--jo-xl-surface);\n}\n\n.jo-xl__grid--rtl { direction: rtl; }\n\n.jo-xl__corner,\n.jo-xl__colhead,\n.jo-xl__rowhead,\n.jo-xl__pane {\n position: relative;\n overflow: hidden;\n min-width: 0;\n min-height: 0;\n}\n\n.jo-xl__scroller {\n overflow: auto;\n /* Scrolling repaints the whole pane; telling the compositor in advance keeps\n a large sheet at one frame per scroll instead of one per repaint. */\n will-change: scroll-position;\n}\n\n.jo-xl__sizer {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n}\n\n.jo-xl__canvas {\n position: absolute;\n top: 0;\n left: 0;\n width: 0;\n height: 0;\n}\n\n.jo-xl__corner,\n.jo-xl__colhead,\n.jo-xl__rowhead {\n background: var(--jo-xl-header-bg);\n color: var(--jo-xl-header-fg);\n font-size: 11px;\n user-select: none;\n}\n\n.jo-xl__corner {\n border-right: 1px solid var(--jo-xl-line);\n border-bottom: 1px solid var(--jo-xl-line);\n cursor: pointer;\n}\n\n.jo-xl__colhead { border-bottom: 1px solid var(--jo-xl-line); }\n.jo-xl__rowhead { border-right: 1px solid var(--jo-xl-line); }\n\n/* The freeze line, drawn on the panes that border it. */\n.jo-xl__pane--tl,\n.jo-xl__pane--tr { border-bottom: 1px solid #a0a0a0; }\n.jo-xl__pane--tl,\n.jo-xl__pane--bl { border-right: 1px solid #a0a0a0; }\n\n.jo-xl__header {\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n box-sizing: border-box;\n border-right: 1px solid var(--jo-xl-line);\n border-bottom: 1px solid var(--jo-xl-line);\n overflow: hidden;\n cursor: default;\n}\n\n.jo-xl__header--selected {\n background: var(--jo-xl-selection);\n color: var(--jo-xl-accent);\n font-weight: 600;\n}\n\n/* ------------------------------------------------------------------- cells */\n\n.jo-xl__cell {\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n align-items: flex-end;\n box-sizing: border-box;\n padding: 0 3px;\n overflow: hidden;\n white-space: pre;\n line-height: 1.15;\n /* Layout containment only. Paint containment would clip the text of a cell\n that overflows into its empty neighbours, which is behaviour Excel has and\n a grid has to keep. */\n contain: layout;\n}\n\n/* Text that spills into empty neighbours, as Excel lets it. */\n.jo-xl__cell--overflow {\n overflow: visible;\n z-index: 2;\n max-width: var(--jo-xl-spill);\n}\n\n.jo-xl__cell--merged { z-index: 1; }\n\n.jo-xl__cell--link {\n color: #0563c1;\n text-decoration: underline;\n cursor: pointer;\n}\n\n/* A comment is marked the way Excel marks it: a triangle in the corner. */\n.jo-xl__cell--commented::after {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n border: 3px solid transparent;\n border-top-color: #d1343b;\n border-right-color: #d1343b;\n}\n\n.jo-xl__gridline {\n position: absolute;\n top: 0;\n left: 0;\n background: var(--jo-xl-line);\n pointer-events: none;\n}\n\n.jo-xl__gridline--h { height: 1px; }\n.jo-xl__gridline--v { width: 1px; }\n\n.jo-xl__selection {\n position: absolute;\n top: 0;\n left: 0;\n border: 2px solid var(--jo-xl-accent);\n background: var(--jo-xl-selection);\n pointer-events: none;\n z-index: 3;\n}\n\n/* --------------------------------------------------- conditional formatting */\n\n.jo-xl__bar {\n position: absolute;\n left: 0;\n top: 2px;\n bottom: 2px;\n border-radius: 1px;\n opacity: 0.75;\n pointer-events: none;\n}\n\n/* The value sits above its data bar, as it does in Excel. An absolutely\n positioned bar paints over ordinary flow content, so the text is given a\n position of its own rather than the bar a negative index — which would put\n it behind the cell's own background. */\n.jo-xl__value {\n position: relative;\n z-index: 1;\n min-width: 0;\n overflow: inherit;\n text-overflow: inherit;\n white-space: inherit;\n}\n\n.jo-xl__icon {\n font-size: 0.85em;\n margin-right: 3px;\n flex: 0 0 auto;\n}\n\n/* The button of a filter header or of a list validation. Excel draws it as\n part of the cell, at the right edge, over whatever the cell holds. */\n\n.jo-xl__button {\n position: absolute;\n right: 1px;\n top: 50%;\n transform: translateY(-50%);\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15px;\n height: 15px;\n border: 1px solid var(--jo-xl-line);\n border-radius: 2px;\n background: var(--jo-xl-header-bg);\n color: var(--jo-xl-header-fg);\n font-size: 9px;\n line-height: 1;\n pointer-events: none;\n}\n\n.jo-xl__button--filtering {\n color: var(--jo-xl-accent);\n font-weight: 700;\n}\n\n/* A shape's own outline, drawn under whatever text it holds. */\n\n.jo-xl__outline {\n position: absolute;\n inset: 0;\n overflow: visible;\n pointer-events: none;\n}\n\n/* The outline gutter: a bracket per group, a button on its summary row. */\n\n.jo-xl__outline-bracket {\n position: absolute;\n top: 0;\n left: 0;\n width: 1px;\n background: var(--jo-xl-header-fg);\n opacity: 0.55;\n}\n\n.jo-xl__outline-button {\n position: absolute;\n top: 0;\n left: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 11px;\n height: 11px;\n border: 1px solid var(--jo-xl-header-fg);\n background: var(--jo-xl-bg);\n color: var(--jo-xl-header-fg);\n font-size: 9px;\n line-height: 1;\n}\n\n/* The axis of a data bar: where zero falls, when it falls inside the cell. */\n\n.jo-xl__bar-axis {\n position: absolute;\n top: 0;\n bottom: 0;\n width: 1px;\n pointer-events: none;\n}\n\n/* A sparkline fills its cell and sits behind whatever the cell holds. */\n\n.jo-xl__sparkline {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n/* --------------------------------------------------------------- drawings */\n\n.jo-xl__drawing {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 4;\n pointer-events: none;\n overflow: hidden;\n}\n\n.jo-xl__drawing img {\n width: 100%;\n height: 100%;\n object-fit: contain;\n}\n\n.jo-xl__drawing--placeholder {\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px dashed var(--jo-xl-line);\n background: color-mix(in srgb, var(--jo-xl-header-bg) 70%, transparent);\n color: var(--jo-xl-header-fg);\n font-size: 11px;\n text-align: center;\n padding: 4px;\n}\n\n.jo-xl__drawing--shape {\n white-space: pre-wrap;\n font-size: 11px;\n}\n\n/* ------------------------------------------------------------------- tabs */\n\n.jo-xl__tabs {\n display: flex;\n gap: 1px;\n padding: 3px 6px 0;\n border-top: 1px solid var(--jo-xl-line);\n background: var(--jo-xl-header-bg);\n overflow-x: auto;\n flex: 0 0 auto;\n}\n\n.jo-xl__tab {\n appearance: none;\n border: 1px solid transparent;\n border-bottom: none;\n border-radius: 4px 4px 0 0;\n background: transparent;\n color: inherit;\n padding: 4px 12px;\n cursor: pointer;\n white-space: nowrap;\n font: inherit;\n font-size: 12px;\n border-bottom: 3px solid var(--jo-xl-tab-color, transparent);\n}\n\n.jo-xl__tab:hover { background: rgba(0, 0, 0, 0.04); }\n\n.jo-xl__tab[aria-selected='true'] {\n background: var(--jo-xl-surface);\n border-color: var(--jo-xl-line);\n border-bottom-color: var(--jo-xl-tab-color, var(--jo-xl-surface));\n font-weight: 600;\n color: var(--jo-xl-accent);\n}\n\n.jo-xl__tab--hidden { opacity: 0.55; font-style: italic; }\n\n.jo-xl__empty {\n padding: 32px;\n text-align: center;\n color: var(--jo-xl-header-fg);\n font-size: 13px;\n}\n\n@media (prefers-color-scheme: dark) {\n .jo-xl {\n --jo-xl-line: #3d444d;\n --jo-xl-header-bg: #21262d;\n --jo-xl-header-fg: #9198a1;\n --jo-xl-surface: #0d1117;\n --jo-xl-text: #e6edf3;\n --jo-xl-accent: #3fb950;\n --jo-xl-selection: rgba(63, 185, 80, 0.16);\n }\n\n .jo-xl__tab:hover { background: rgba(255, 255, 255, 0.06); }\n}\n\n@media print {\n .jo-xl__formula-bar,\n .jo-xl__tabs { display: none; }\n\n .jo-xl__scroller { overflow: visible; }\n}\n"

The renderer's own stylesheet. Only the chrome and the structure are here. Everything that comes from the workbook — fonts, fills, borders, alignment — is emitted as generated classes at render time, because it differs per file and there are a few dozen of them per sheet rather than a fixed set.

xlsxView
xlsxView: ViewPlugin<XlsxDocument>