Add QExcell XLSX page setup support
This commit is contained in:
@@ -32,7 +32,7 @@ import {
|
||||
Underline,
|
||||
WrapText
|
||||
} from "lucide-react";
|
||||
import type { CellFormat, SheetChart, SheetChartType, Workbook } from "../../types";
|
||||
import type { CellFormat, SheetChart, SheetChartType, SheetPageMarginPreset, SheetPageOrientation, SheetPageSetup, SheetPageSizeId, Workbook } from "../../types";
|
||||
import { cellId, columnLabel, columnLabelsForCount, evaluateCell, formatCellValueForDisplay, mergedCellInfo, parseCellId, summarizeColumn, usedSheetBounds } from "../../utils/spreadsheet";
|
||||
import { downloadBlobFile, downloadTextFile } from "../../utils/download";
|
||||
import { chartDataPoints, exportXlsxBlob, importXlsxFile } from "../../io/excellOffice";
|
||||
@@ -95,6 +95,20 @@ const chartTypeOptions: Array<{ value: SheetChartType; label: string }> = [
|
||||
{ value: "line", label: "Линейная" },
|
||||
{ value: "pie", label: "Круговая" }
|
||||
];
|
||||
const sheetPageSizeOptions: Array<{ id: SheetPageSizeId; label: string }> = [
|
||||
{ id: "a4", label: "A4" },
|
||||
{ id: "letter", label: "Letter" }
|
||||
];
|
||||
const sheetPageOrientationOptions: Array<{ id: SheetPageOrientation; label: string }> = [
|
||||
{ id: "portrait", label: "Книжная" },
|
||||
{ id: "landscape", label: "Альбомная" }
|
||||
];
|
||||
const sheetPageMarginOptions: Array<{ id: SheetPageMarginPreset; label: string }> = [
|
||||
{ id: "normal", label: "Обычные" },
|
||||
{ id: "narrow", label: "Узкие" },
|
||||
{ id: "wide", label: "Широкие" }
|
||||
];
|
||||
const defaultSheetPageSetup: SheetPageSetup = { size: "a4", orientation: "portrait", marginPreset: "normal" };
|
||||
|
||||
const horizontalAlignOptions = [
|
||||
{ value: "left", label: "По левому краю", icon: AlignLeft },
|
||||
@@ -159,6 +173,18 @@ function normalizedCellFormat(format: CellFormat): CellFormat | undefined {
|
||||
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
||||
}
|
||||
|
||||
function normalizedSheetPageSetup(setup?: SheetPageSetup): SheetPageSetup {
|
||||
const candidateSize = setup?.size;
|
||||
const candidateOrientation = setup?.orientation;
|
||||
const candidateMarginPreset = setup?.marginPreset;
|
||||
const size = candidateSize && sheetPageSizeOptions.some((option) => option.id === candidateSize) ? candidateSize : defaultSheetPageSetup.size;
|
||||
const orientation =
|
||||
candidateOrientation && sheetPageOrientationOptions.some((option) => option.id === candidateOrientation) ? candidateOrientation : defaultSheetPageSetup.orientation;
|
||||
const marginPreset =
|
||||
candidateMarginPreset && sheetPageMarginOptions.some((option) => option.id === candidateMarginPreset) ? candidateMarginPreset : defaultSheetPageSetup.marginPreset;
|
||||
return { size, orientation, marginPreset };
|
||||
}
|
||||
|
||||
function cellInputStyle(format?: CellFormat): CSSProperties {
|
||||
const textDecoration = [format?.underline ? "underline" : "", format?.strike ? "line-through" : ""].filter(Boolean).join(" ");
|
||||
return {
|
||||
@@ -276,6 +302,7 @@ export function QExcell({ workbook, onChange }: QExcellProps) {
|
||||
const activeHiddenColumns = activeSheet.hiddenColumns ?? emptyHiddenItems;
|
||||
const activeHiddenRows = activeSheet.hiddenRows ?? emptyHiddenItems;
|
||||
const activeCharts = activeSheet.charts ?? emptyCharts;
|
||||
const activeSheetPageSetup = normalizedSheetPageSetup(activeSheet.pageSetup);
|
||||
const selectedRawValue = activeSheet.cells[selectedCell] ?? "";
|
||||
const selectedHyperlink = activeHyperlinks[selectedCell] ?? "";
|
||||
const selectedComment = activeComments[selectedCell] ?? "";
|
||||
@@ -418,6 +445,15 @@ export function QExcell({ workbook, onChange }: QExcellProps) {
|
||||
});
|
||||
}
|
||||
|
||||
function updateActiveSheetPageSetup(pageSetupPatch: Partial<SheetPageSetup>) {
|
||||
onChange({
|
||||
...workbook,
|
||||
sheets: workbook.sheets.map((sheet) =>
|
||||
sheet.id === activeSheet.id ? { ...sheet, pageSetup: normalizedSheetPageSetup({ ...activeSheetPageSetup, ...pageSetupPatch }) } : sheet
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
function addSheet() {
|
||||
const id = `sheet-${workbook.sheets.length + 1}`;
|
||||
onChange({
|
||||
@@ -854,6 +890,39 @@ export function QExcell({ workbook, onChange }: QExcellProps) {
|
||||
onBlur={(event) => updateActiveSheetPrintArea(normalizedPrintAreaInput(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<label className="office-field">
|
||||
<span>Размер</span>
|
||||
<select value={activeSheetPageSetup.size} onChange={(event) => updateActiveSheetPageSetup({ size: event.target.value as SheetPageSizeId })}>
|
||||
{sheetPageSizeOptions.map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="office-field">
|
||||
<span>Ориентация</span>
|
||||
<select
|
||||
value={activeSheetPageSetup.orientation}
|
||||
onChange={(event) => updateActiveSheetPageSetup({ orientation: event.target.value as SheetPageOrientation })}
|
||||
>
|
||||
{sheetPageOrientationOptions.map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="office-field">
|
||||
<span>Поля</span>
|
||||
<select value={activeSheetPageSetup.marginPreset} onChange={(event) => updateActiveSheetPageSetup({ marginPreset: event.target.value as SheetPageMarginPreset })}>
|
||||
{sheetPageMarginOptions.map((option) => (
|
||||
<option key={option.id} value={option.id}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="office-tool-section">
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
buildColumnWidthsXml,
|
||||
buildHyperlinksXml,
|
||||
buildMergeCellsXml,
|
||||
buildPageMarginsXml,
|
||||
buildPageSetupXml,
|
||||
buildSheetPrXml,
|
||||
buildSheetViewsXml,
|
||||
createCellStyleRegistry,
|
||||
@@ -32,6 +34,7 @@ import {
|
||||
worksheetXmlToHyperlinks,
|
||||
worksheetCommentsXmlToComments,
|
||||
worksheetXmlToMergedCells,
|
||||
worksheetXmlToPageSetup,
|
||||
worksheetXmlToRowHeights,
|
||||
worksheetXmlToShowGridLines,
|
||||
worksheetXmlToTabColor,
|
||||
@@ -739,6 +742,45 @@ describe("excellOffice OOXML helpers", () => {
|
||||
expect(imported.sheets[1].printArea).toBe("B2:C4,E1:E3");
|
||||
});
|
||||
|
||||
it("сохраняет и импортирует параметры страницы XLSX через pageMargins и pageSetup", async () => {
|
||||
expect(
|
||||
worksheetXmlToPageSetup({
|
||||
worksheet: {
|
||||
pageMargins: { left: "0.25", right: "0.25", top: "0.75", bottom: "0.75", header: "0.3", footer: "0.3" },
|
||||
pageSetup: { paperSize: "1", orientation: "landscape" }
|
||||
}
|
||||
})
|
||||
).toEqual({ size: "letter", orientation: "landscape", marginPreset: "narrow" });
|
||||
expect(buildPageMarginsXml({ size: "a4", orientation: "portrait", marginPreset: "wide" })).toBe(
|
||||
'<pageMargins left="1" right="1" top="1" bottom="1" header="0.5" footer="0.5"/>'
|
||||
);
|
||||
expect(buildPageSetupXml({ size: "letter", orientation: "landscape", marginPreset: "normal" })).toBe(
|
||||
'<pageSetup paperSize="1" orientation="landscape"/>'
|
||||
);
|
||||
|
||||
const blob = await exportXlsxBlob({
|
||||
id: "book-1",
|
||||
title: "Page setup.xlsx",
|
||||
updatedAt: "2026-06-01T00:00:00.000Z",
|
||||
activeSheet: "sheet-1",
|
||||
sheets: [
|
||||
{
|
||||
id: "sheet-1",
|
||||
name: "Layout",
|
||||
pageSetup: { size: "letter", orientation: "landscape", marginPreset: "narrow" },
|
||||
cells: { A1: "Layout" }
|
||||
}
|
||||
]
|
||||
});
|
||||
const zip = await JSZip.loadAsync(await blob.arrayBuffer());
|
||||
const sheetXml = (await zip.file("xl/worksheets/sheet1.xml")?.async("string")) ?? "";
|
||||
const imported = await importXlsxFile(new File([blob], "Page setup.xlsx", { type: blob.type }));
|
||||
|
||||
expect(sheetXml).toContain('<pageMargins left="0.25" right="0.25" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>');
|
||||
expect(sheetXml).toContain('<pageSetup paperSize="1" orientation="landscape"/>');
|
||||
expect(imported.sheets[0].pageSetup).toEqual({ size: "letter", orientation: "landscape", marginPreset: "narrow" });
|
||||
});
|
||||
|
||||
it("сохраняет и импортирует скрытые листы XLSX через sheet state", async () => {
|
||||
const blob = await exportXlsxBlob({
|
||||
id: "book-1",
|
||||
|
||||
+115
-4
@@ -1,4 +1,15 @@
|
||||
import type { CellFormat, MergedCellRange, SheetChart, SheetChartType, SheetFrozenPane, Workbook } from "../types";
|
||||
import type {
|
||||
CellFormat,
|
||||
MergedCellRange,
|
||||
SheetChart,
|
||||
SheetChartType,
|
||||
SheetFrozenPane,
|
||||
SheetPageMarginPreset,
|
||||
SheetPageOrientation,
|
||||
SheetPageSetup,
|
||||
SheetPageSizeId,
|
||||
Workbook
|
||||
} from "../types";
|
||||
import { evaluateCell, expandRange } from "../utils/spreadsheet";
|
||||
import { officeTitleFromFileName, readOfficeFileAsArrayBuffer, XLSX_MIME_TYPE } from "./fileHelpers";
|
||||
|
||||
@@ -19,6 +30,7 @@ export interface ImportedExcellWorkbook {
|
||||
showGridLines?: boolean;
|
||||
zoomScale?: number;
|
||||
printArea?: string;
|
||||
pageSetup?: SheetPageSetup;
|
||||
cells: Record<string, string>;
|
||||
mergedCells?: MergedCellRange[];
|
||||
hyperlinks?: Record<string, string>;
|
||||
@@ -77,6 +89,7 @@ interface ParsedSheet {
|
||||
showGridLines?: boolean;
|
||||
zoomScale?: number;
|
||||
printArea?: string;
|
||||
pageSetup?: SheetPageSetup;
|
||||
cells: Record<string, string>;
|
||||
mergedCells?: MergedCellRange[];
|
||||
hyperlinks?: Record<string, string>;
|
||||
@@ -210,6 +223,16 @@ const builtinNumberFormats: Record<number, string> = {
|
||||
48: "##0.0E+0",
|
||||
49: "@"
|
||||
};
|
||||
const defaultSheetPageSetup: SheetPageSetup = { size: "a4", orientation: "portrait", marginPreset: "normal" };
|
||||
const xlsxPageSizePaperIds: Record<SheetPageSizeId, number> = {
|
||||
letter: 1,
|
||||
a4: 9
|
||||
};
|
||||
const xlsxPageMargins: Record<SheetPageMarginPreset, { left: number; right: number; top: number; bottom: number; header: number; footer: number }> = {
|
||||
normal: { left: 0.7, right: 0.7, top: 0.75, bottom: 0.75, header: 0.3, footer: 0.3 },
|
||||
narrow: { left: 0.25, right: 0.25, top: 0.75, bottom: 0.75, header: 0.3, footer: 0.3 },
|
||||
wide: { left: 1, right: 1, top: 1, bottom: 1, header: 0.5, footer: 0.5 }
|
||||
};
|
||||
|
||||
export async function importXlsxFile(file: File): Promise<ImportedExcellWorkbook> {
|
||||
if (!file.name.toLowerCase().endsWith(".xlsx")) {
|
||||
@@ -320,7 +343,8 @@ export async function exportXlsxBlob(workbook: Workbook): Promise<Blob> {
|
||||
sheet.hiddenRows,
|
||||
sheet.showGridLines,
|
||||
sheet.zoomScale,
|
||||
legacyDrawingRelationshipId
|
||||
legacyDrawingRelationshipId,
|
||||
sheet.pageSetup
|
||||
)
|
||||
);
|
||||
const relationshipsXml = buildWorksheetRelationshipsXml(
|
||||
@@ -371,6 +395,7 @@ export function parsedWorksheetToSheet(
|
||||
const hiddenRows = worksheetXmlToHiddenRows(worksheetXml);
|
||||
const showGridLines = worksheetXmlToShowGridLines(worksheetXml);
|
||||
const zoomScale = worksheetXmlToZoomScale(worksheetXml);
|
||||
const pageSetup = worksheetXmlToPageSetup(worksheetXml);
|
||||
|
||||
return {
|
||||
id: sheet.id || `sheet-${index + 1}`,
|
||||
@@ -385,6 +410,7 @@ export function parsedWorksheetToSheet(
|
||||
...(Object.keys(hiddenRows).length > 0 ? { hiddenRows } : {}),
|
||||
...(showGridLines === false ? { showGridLines } : {}),
|
||||
...(zoomScale ? { zoomScale } : {}),
|
||||
...(pageSetup ? { pageSetup } : {}),
|
||||
cells: worksheetXmlToCells(worksheetXml, sharedStrings, styleTable, date1904),
|
||||
mergedCells: worksheetXmlToMergedCells(worksheetXml),
|
||||
hyperlinks: worksheetXmlToHyperlinks(worksheetXml, relationshipsXml),
|
||||
@@ -540,6 +566,18 @@ export function worksheetXmlToZoomScale(worksheetXml: unknown) {
|
||||
return normalizedSheetZoomScale(sheetView.zoomScale);
|
||||
}
|
||||
|
||||
export function worksheetXmlToPageSetup(worksheetXml: unknown): SheetPageSetup | undefined {
|
||||
const worksheet = childRecord(worksheetXml, "worksheet");
|
||||
const pageSetup = childRecord(worksheet, "pageSetup");
|
||||
const pageMargins = childRecord(worksheet, "pageMargins");
|
||||
const size = sheetPageSizeFromPaperId(pageSetup.paperSize) ?? defaultSheetPageSetup.size;
|
||||
const orientation = normalizedSheetPageOrientation(pageSetup.orientation) ?? defaultSheetPageSetup.orientation;
|
||||
const marginPreset = sheetPageMarginPresetFromRecord(pageMargins) ?? defaultSheetPageSetup.marginPreset;
|
||||
const hasPageSetup = Object.keys(pageSetup).length > 0;
|
||||
const hasPageMargins = Object.keys(pageMargins).length > 0;
|
||||
return hasPageSetup || hasPageMargins ? { size, orientation, marginPreset } : undefined;
|
||||
}
|
||||
|
||||
export function worksheetXmlToAutoFilter(worksheetXml: unknown) {
|
||||
const worksheet = childRecord(worksheetXml, "worksheet");
|
||||
const autoFilter = childRecord(worksheet, "autoFilter");
|
||||
@@ -804,7 +842,8 @@ export function buildWorksheetXml(
|
||||
hiddenRows: Record<string, boolean> = {},
|
||||
showGridLines?: boolean,
|
||||
zoomScale?: number,
|
||||
legacyDrawingRelationshipId = ""
|
||||
legacyDrawingRelationshipId = "",
|
||||
pageSetup?: SheetPageSetup
|
||||
) {
|
||||
const normalizedRowHeights = normalizedSheetRowHeights(rowHeights);
|
||||
const normalizedHiddenRows = normalizedSheetHiddenRows(hiddenRows);
|
||||
@@ -845,9 +884,11 @@ export function buildWorksheetXml(
|
||||
const sheetPrXml = buildSheetPrXml(tabColor);
|
||||
const sheetViewsXml = buildSheetViewsXml(frozenPane, showGridLines, zoomScale);
|
||||
const colsXml = buildColumnWidthsXml(columnWidths, hiddenColumns);
|
||||
const pageMarginsXml = buildPageMarginsXml(pageSetup);
|
||||
const pageSetupXml = buildPageSetupXml(pageSetup);
|
||||
|
||||
return xmlDeclaration(
|
||||
`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${sheetPrXml}${sheetViewsXml}${colsXml}<sheetData>${rowXml}</sheetData>${autoFilterXml}${mergeCellsXml}${hyperlinksXml}${drawingXml}${legacyDrawingXml}</worksheet>`
|
||||
`<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">${sheetPrXml}${sheetViewsXml}${colsXml}<sheetData>${rowXml}</sheetData>${autoFilterXml}${mergeCellsXml}${hyperlinksXml}${pageMarginsXml}${pageSetupXml}${drawingXml}${legacyDrawingXml}</worksheet>`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -940,6 +981,24 @@ export function buildSheetPrXml(tabColor?: string) {
|
||||
return color ? `<sheetPr><tabColor rgb="FF${color}"/></sheetPr>` : "";
|
||||
}
|
||||
|
||||
export function buildPageMarginsXml(pageSetup?: SheetPageSetup) {
|
||||
if (!pageSetup) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const margins = xlsxPageMargins[normalizedSheetPageSetup(pageSetup).marginPreset];
|
||||
return `<pageMargins left="${formatSheetDimension(margins.left)}" right="${formatSheetDimension(margins.right)}" top="${formatSheetDimension(margins.top)}" bottom="${formatSheetDimension(margins.bottom)}" header="${formatSheetDimension(margins.header)}" footer="${formatSheetDimension(margins.footer)}"/>`;
|
||||
}
|
||||
|
||||
export function buildPageSetupXml(pageSetup?: SheetPageSetup) {
|
||||
if (!pageSetup) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const normalized = normalizedSheetPageSetup(pageSetup);
|
||||
return `<pageSetup paperSize="${xlsxPageSizePaperIds[normalized.size]}" orientation="${normalized.orientation}"/>`;
|
||||
}
|
||||
|
||||
export function buildColumnWidthsXml(columnWidths: Record<string, number> = {}, hiddenColumns: Record<string, boolean> = {}) {
|
||||
const columnsByNumber = new Map<number, { width?: number; hidden?: boolean }>();
|
||||
normalizedSheetColumnWidths(columnWidths).forEach(({ column, width }) => {
|
||||
@@ -1914,6 +1973,58 @@ function normalizedSheetZoomScale(value: unknown) {
|
||||
return zoomScale >= 10 && zoomScale <= 400 && zoomScale !== 100 ? zoomScale : 0;
|
||||
}
|
||||
|
||||
function normalizedSheetPageSetup(pageSetup?: SheetPageSetup): SheetPageSetup {
|
||||
const size = isSheetPageSizeId(pageSetup?.size) ? pageSetup.size : defaultSheetPageSetup.size;
|
||||
const orientation = normalizedSheetPageOrientation(pageSetup?.orientation) ?? defaultSheetPageSetup.orientation;
|
||||
const marginPreset = isSheetPageMarginPreset(pageSetup?.marginPreset) ? pageSetup.marginPreset : defaultSheetPageSetup.marginPreset;
|
||||
return { size, orientation, marginPreset };
|
||||
}
|
||||
|
||||
function normalizedSheetPageOrientation(value: unknown): SheetPageOrientation | undefined {
|
||||
return value === "portrait" || value === "landscape" ? value : undefined;
|
||||
}
|
||||
|
||||
function isSheetPageSizeId(value: unknown): value is SheetPageSizeId {
|
||||
return value === "a4" || value === "letter";
|
||||
}
|
||||
|
||||
function isSheetPageMarginPreset(value: unknown): value is SheetPageMarginPreset {
|
||||
return value === "normal" || value === "narrow" || value === "wide";
|
||||
}
|
||||
|
||||
function sheetPageSizeFromPaperId(value: unknown): SheetPageSizeId | undefined {
|
||||
const paperSize = positiveIntegerAttribute(value);
|
||||
return (Object.keys(xlsxPageSizePaperIds) as SheetPageSizeId[]).find((sizeId) => xlsxPageSizePaperIds[sizeId] === paperSize);
|
||||
}
|
||||
|
||||
function sheetPageMarginPresetFromRecord(record: XmlRecord): SheetPageMarginPreset | undefined {
|
||||
const left = positiveNumberAttribute(record.left);
|
||||
const right = positiveNumberAttribute(record.right);
|
||||
const top = positiveNumberAttribute(record.top);
|
||||
const bottom = positiveNumberAttribute(record.bottom);
|
||||
const header = positiveNumberAttribute(record.header);
|
||||
const footer = positiveNumberAttribute(record.footer);
|
||||
if (!left || !right || !top || !bottom || !header || !footer) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (Object.keys(xlsxPageMargins) as SheetPageMarginPreset[]).find((preset) => {
|
||||
const margins = xlsxPageMargins[preset];
|
||||
return (
|
||||
isCloseDimension(left, margins.left) &&
|
||||
isCloseDimension(right, margins.right) &&
|
||||
isCloseDimension(top, margins.top) &&
|
||||
isCloseDimension(bottom, margins.bottom) &&
|
||||
isCloseDimension(header, margins.header) &&
|
||||
isCloseDimension(footer, margins.footer)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function isCloseDimension(actual: number, expected: number) {
|
||||
return Math.abs(actual - expected) <= 0.01;
|
||||
}
|
||||
|
||||
function buildRowAttributes(height?: number, hidden?: boolean) {
|
||||
return `${height ? ` ht="${formatSheetDimension(height)}" customHeight="1"` : ""}${hidden ? ' hidden="1"' : ""}`;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ export type AppId = "word" | "sheets" | "slides" | "mail";
|
||||
export type WordPageSizeId = "a4" | "letter";
|
||||
export type WordPageOrientation = "portrait" | "landscape";
|
||||
export type WordPageMarginPreset = "normal" | "narrow" | "wide";
|
||||
export type SheetPageSizeId = "a4" | "letter";
|
||||
export type SheetPageOrientation = "portrait" | "landscape";
|
||||
export type SheetPageMarginPreset = "normal" | "narrow" | "wide";
|
||||
|
||||
export interface WordPageSetup {
|
||||
size: WordPageSizeId;
|
||||
@@ -10,6 +13,12 @@ export interface WordPageSetup {
|
||||
marginPreset: WordPageMarginPreset;
|
||||
}
|
||||
|
||||
export interface SheetPageSetup {
|
||||
size: SheetPageSizeId;
|
||||
orientation: SheetPageOrientation;
|
||||
marginPreset: SheetPageMarginPreset;
|
||||
}
|
||||
|
||||
export interface WordDocument {
|
||||
id: string;
|
||||
title: string;
|
||||
@@ -40,6 +49,7 @@ export interface Sheet {
|
||||
showGridLines?: boolean;
|
||||
zoomScale?: number;
|
||||
printArea?: string;
|
||||
pageSetup?: SheetPageSetup;
|
||||
cells: Record<string, string>;
|
||||
mergedCells?: MergedCellRange[];
|
||||
hyperlinks?: Record<string, string>;
|
||||
|
||||
Reference in New Issue
Block a user