SpreadJS supports rounded worksheet ranges with Range.setBorderRadius, Range.borderRadius, and Style.borderRadius. Border radius can be used to create card-like cell areas, tab headers, status steps, and other worksheet UI elements with rounded corners.
Border radius can be applied to a range in different ways:
Outer applies the radius only to the outside corners of the target range. This is useful when multiple cells should look like one rounded block.
All applies the radius to every cell in the target range. This is useful when each cell should keep its own rounded shape.
Passing undefined clears the border radius from the target range.
The borderRadius value can be a string with one to four space-separated tokens. The tokens represent the corners in this order: top-left, top-right, bottom-right, and bottom-left.
One token applies the same radius to all four corners: "14" means 14 14 14 14.
Two tokens apply the first value to top-left and bottom-right, and the second value to top-right and bottom-left: "14 8" means 14 8 14 8.
Three tokens apply the first value to top-left, the second value to top-right and bottom-left, and the third value to bottom-right: "14 8 4" means 14 8 4 8.
Four tokens set each corner separately in top-left, top-right, bottom-right, bottom-left order: "14 8 4 0" means top-left is 14, top-right is 8, bottom-right is 4, and bottom-left is 0.
You can also set border radius through a style when you want reusable cell formatting:
Border radius clips the rendered cell shape, including the cell background and diagonal borders.
import * as React from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
import { AppFunc } from './app-func';
createRoot(document.getElementById('app')).render(<AppFunc />);
import * as React from 'react';
import { SpreadSheets, Worksheet } from '@mescius/spread-sheets-react';
import GC from '@mescius/spread-sheets';
import './styles.css';
const spreadNS = GC.Spread.Sheets;
const SheetArea = spreadNS.SheetArea.viewport;
const LineStyle = spreadNS.LineStyle;
const useState = React.useState;
const useRef = React.useRef;
export const initialSettings = {
backgroundColor: "#f1f6ea",
backgroundNoFill: false,
borderStyle: "medium",
borderColor: "#6f8f45",
radiusOption: "outer",
topLeft: 14,
topRight: 14,
bottomRight: 14,
bottomLeft: 14
};
export function AppFunc() {
const [spread, setSpread] = useState(null);
const [settings, setSettings] = useState(initialSettings);
const optionsContainerRef = useRef(null);
const initSpread = (value) => {
setSpread(value);
value.options.newTabVisible = false;
ensureSheetCount(value, 2);
const sheet = value.getSheet(0);
const showcaseSheet = value.getSheet(1);
value.suspendPaint();
try {
setupSheet(sheet);
buildSamples(sheet);
setupShowcaseSheet(showcaseSheet);
buildShowcase(showcaseSheet);
sheet.setSelection(3, 2, 7, 3);
value.setActiveSheetIndex(0);
setSettings(settings => getSettingsFromSelection(sheet, settings));
updateOptionsPanelVisibility(value, optionsContainerRef.current);
sheet.bind(spreadNS.Events.SelectionChanged, () => {
if (value.getActiveSheetIndex() === 0) {
setSettings(settings => getSettingsFromSelection(sheet, settings));
}
});
value.bind(spreadNS.Events.ActiveSheetChanged, () => {
const isShowcaseSheet = updateOptionsPanelVisibility(value, optionsContainerRef.current);
if (!isShowcaseSheet) {
setSettings(settings => getSettingsFromSelection(sheet, settings));
}
});
} finally {
value.resumePaint();
}
refreshSpreadLayout(value);
};
const updateSettings = (changes) => {
setSettings(settings => ({ ...settings, ...changes }));
};
const withSelectedRange = (callback) => {
if (!spread) {
return;
}
const sheet = spread.getSheet(0);
const selection = getActiveSelection(sheet);
const range = sheet.getRange(selection.row, selection.col, selection.rowCount, selection.colCount);
sheet.suspendPaint();
try {
callback(sheet, range, selection);
sheet.setSelection(selection.row, selection.col, selection.rowCount, selection.colCount);
} finally {
sheet.resumePaint();
}
spread.focus();
};
const applyBackgroundColorSettings = (changes) => {
const nextSettings = { ...settings, ...changes };
updateSettings({
backgroundColor: nextSettings.backgroundColor,
backgroundNoFill: nextSettings.backgroundNoFill
});
withSelectedRange((sheet, range) => {
applyBackgroundColor(sheet, range, nextSettings);
});
};
const applyBorderStyleSettings = (borderStyle) => {
updateSettings({ borderStyle });
withSelectedRange((sheet, range) => {
applyBorderStyle(sheet, range, borderStyle, settings.borderColor);
});
};
const applyBorderColorSettings = (borderColor) => {
updateSettings({ borderColor });
withSelectedRange((sheet, range) => {
applyBorderColor(sheet, range, borderColor, settings.borderStyle);
});
};
const applyRadiusSettings = () => {
withSelectedRange((sheet, range) => {
applyRadius(sheet, range, settings);
});
};
return (
<div className="sample-tutorial">
<div className="sample-spreadsheets">
<SpreadSheets workbookInitialized={spread => initSpread(spread)}>
<Worksheet></Worksheet>
<Worksheet></Worksheet>
</SpreadSheets>
</div>
<PanelFunc
optionsContainerRef={optionsContainerRef}
settings={settings}
updateSettings={updateSettings}
applyBackgroundColorSettings={applyBackgroundColorSettings}
applyBorderStyleSettings={applyBorderStyleSettings}
applyBorderColorSettings={applyBorderColorSettings}
applyRadiusSettings={applyRadiusSettings}
></PanelFunc>
</div>
);
}
export function PanelFunc(props) {
const settings = props.settings;
return (
<div id="optionsContainer" className="options-container" ref={props.optionsContainerRef}>
<div className="panel-section">
<div className="panel-section-title">
<span>Style</span>
</div>
<div className="panel-section-description">Synced with the selected area. Changes apply instantly.</div>
<div className="panel-card">
<div className="form-group-inline back-color-field">
<label className="form-label" htmlFor="backgroundColor">Back Color</label>
<div className="back-color-controls">
<label className="no-fill-option"><input type="checkbox" id="backgroundNoFill" checked={settings.backgroundNoFill}
onChange={(e) => props.applyBackgroundColorSettings({ backgroundNoFill: e.target.checked })} /> No Fill</label>
<input type="color" id="backgroundColor" value={settings.backgroundColor} aria-label="Back color"
disabled={settings.backgroundNoFill}
onChange={(e) => props.applyBackgroundColorSettings({ backgroundColor: e.target.value, backgroundNoFill: false })} />
</div>
</div>
<div className="form-group-inline border-field">
<label className="form-label" htmlFor="borderStyle">Border</label>
<div className="border-controls">
<select id="borderStyle" className="form-select" value={settings.borderStyle}
onChange={(e) => props.applyBorderStyleSettings(e.target.value)}>
<option value="none">None</option>
<option value="thin">Thin</option>
<option value="medium">Medium</option>
<option value="dashed">Dashed</option>
<option value="dotted">Dotted</option>
<option value="thick">Thick</option>
<option value="double">Double</option>
<option value="hair">Hair</option>
<option value="mediumDashed">Medium Dashed</option>
<option value="dashDot">Dash Dot</option>
<option value="mediumDashDot">Medium Dash Dot</option>
<option value="dashDotDot">Dash Dot Dot</option>
<option value="mediumDashDotDot">Medium Dash Dot Dot</option>
<option value="slantedDashDot">Slanted Dash Dot</option>
</select>
<input type="color" id="borderColor" value={settings.borderColor} aria-label="Border color"
disabled={settings.borderStyle === "none"}
onChange={(e) => props.applyBorderColorSettings(e.target.value)} />
</div>
</div>
<div className="field-hint">Border is applied to the selected range outline by default.</div>
</div>
</div>
<div className="panel-section">
<div className="panel-section-title">
<span>Radius</span>
</div>
<div className="panel-section-description">Set radius values, then click Apply Radius.</div>
<div className="panel-card">
<div className="form-group">
<label className="form-label">Apply To</label>
<div className="radio-group">
<label><input type="radio" name="radiusOption" value="outer" checked={settings.radiusOption === "outer"}
onChange={(e) => props.updateSettings({ radiusOption: e.target.value })} /> Outer</label>
<label><input type="radio" name="radiusOption" value="all" checked={settings.radiusOption === "all"}
onChange={(e) => props.updateSettings({ radiusOption: e.target.value })} /> All</label>
<label><input type="radio" name="radiusOption" value="none" checked={settings.radiusOption === "none"}
onChange={(e) => props.updateSettings({ radiusOption: e.target.value })} /> None</label>
</div>
</div>
<div className="corner-grid">
<label className="form-label" htmlFor="topLeft">Top Left <input className="form-input" type="number" id="topLeft" min="0" max="40" value={settings.topLeft}
onChange={(e) => props.updateSettings({ topLeft: e.target.value })} /></label>
<label className="form-label" htmlFor="topRight">Top Right <input className="form-input" type="number" id="topRight" min="0" max="40" value={settings.topRight}
onChange={(e) => props.updateSettings({ topRight: e.target.value })} /></label>
<label className="form-label" htmlFor="bottomLeft">Bottom Left <input className="form-input" type="number" id="bottomLeft" min="0" max="40" value={settings.bottomLeft}
onChange={(e) => props.updateSettings({ bottomLeft: e.target.value })} /></label>
<label className="form-label" htmlFor="bottomRight">Bottom Right <input className="form-input" type="number" id="bottomRight" min="0" max="40" value={settings.bottomRight}
onChange={(e) => props.updateSettings({ bottomRight: e.target.value })} /></label>
</div>
<div className="panel-actions">
<button id="applyRadius" className="btn btn-primary" type="button" onClick={props.applyRadiusSettings}>Apply Radius</button>
</div>
</div>
</div>
</div>
);
}
function ensureSheetCount(spread, count) {
while (spread.getSheetCount() < count) {
spread.addSheet(spread.getSheetCount(), new spreadNS.Worksheet());
}
}
export function updateOptionsPanelVisibility(spread, optionsContainer) {
const isShowcaseSheet = spread.getActiveSheetIndex() === 1;
if (optionsContainer) {
optionsContainer.style.display = isShowcaseSheet ? "none" : "block";
}
refreshSpreadLayout(spread);
return isShowcaseSheet;
}
function refreshSpreadLayout(spread) {
spread.refresh();
setTimeout(function () {
spread.refresh();
}, 0);
}
export function setupSheet(sheet) {
sheet.name("Border Radius");
sheet.defaults.rowHeight = 24;
sheet.setRowCount(28);
sheet.setColumnCount(16);
sheet.setRowHeight(1, 36);
for (let col = 1; col <= 14; col++) {
sheet.setColumnWidth(col, 48);
}
}
export function buildSamples(sheet) {
addTitle(sheet);
addCardSample(sheet);
addTabsSample(sheet);
addStatusStepsSample(sheet);
}
function addTitle(sheet) {
sheet.getCell(1, 1)
.value("Select a range, then adjust its style in the panel.")
.font("13px Calibri")
.foreColor("#64748b")
.vAlign(spreadNS.VerticalAlign.center);
}
function addCardSample(sheet) {
addSectionLabel(sheet, 3, "Card");
addSpanCard(sheet, 3, 2);
addRangeCard(sheet, 3, 6);
addBackgroundOnlyCard(sheet, 3, 10);
}
function addSpanCard(sheet, row, col) {
const range = sheet.getRange(row, col, 7, 3);
range.backColor("#f1f6ea");
range.setBorder(new spreadNS.LineBorder("#6f8f45", LineStyle.medium), { outline: true });
setRangeBorderRadius(sheet, range, "14", { outer: true });
centerBlock(sheet, row, col, 7, 3, "Outer Radius");
range.foreColor("#2f3a25");
}
function addRangeCard(sheet, row, col) {
const range = sheet.getRange(row, col, 7, 3);
range.backColor("#f3eff8");
range.setBorder(new spreadNS.LineBorder("#9a86b8", LineStyle.medium), { outline: true });
setRangeBorderRadius(sheet, range, "14", { all: true });
range.hAlign(spreadNS.HorizontalAlign.center);
range.vAlign(spreadNS.VerticalAlign.center);
range.font("600 12px Calibri");
range.foreColor("#4b3f5c");
sheet.getCell(row + 3, col + 1).value("All Cell Radius");
}
function addBackgroundOnlyCard(sheet, row, col) {
const range = sheet.getRange(row, col, 7, 3);
range.backColor("#DCEFEF");
setRangeBorderRadius(sheet, range, "20", { outer: true });
centerBlock(sheet, row, col, 7, 3, "Fill Only");
range.foreColor("#46514a");
}
function addTabsSample(sheet) {
addSectionLabel(sheet, 12, "Tabs");
addTab(sheet, 12, 2, "Overview", false);
addTab(sheet, 12, 4, "Details", false);
addTab(sheet, 12, 6, "Data", true);
addTab(sheet, 12, 8, "Settings", false);
addTab(sheet, 12, 10, "History", false);
}
function addTab(sheet, row, col, text, active) {
const range = sheet.getRange(row, col, 1, 2);
range.backColor(active ? "#F6ECE8" : "#FCFAF9");
range.setBorder(new spreadNS.LineBorder("#E7DAD6", LineStyle.medium), { outline: true });
setRangeBorderRadius(sheet, range, "0 10 0 0", { all: true });
centerBlock(sheet, row, col, 1, 2, text);
range.foreColor(active ? "#6B4E47" : "#8A756E");
if (active) {
range.borderBottom(new spreadNS.LineBorder("#B67868", LineStyle.medium));
}
}
function addStatusStepsSample(sheet) {
addSectionLabel(sheet, 15, "Steps");
addStatusStep(sheet, 15, 2, "1 Plan", "#EEF3F8", "#8CA3BE", "#3E5168", "14");
addStatusStep(sheet, 15, 6, "2 Build", "#f8f3e8", "#b89b5e", "#5c4a22", "14");
addStatusStep(sheet, 15, 10, "3 Ship", "#eef1ee", "#8a958d", "#3f4942", "14");
}
function addStatusStep(sheet, row, col, text, backColor, borderColor, foreColor, radius) {
const range = sheet.getRange(row, col, 2, 3);
range.backColor(backColor);
range.setBorder(new spreadNS.LineBorder(borderColor, LineStyle.medium), { outline: true });
setRangeBorderRadius(sheet, range, radius, { outer: true });
centerBlock(sheet, row, col, 2, 3, text);
range.foreColor(foreColor);
}
function centerBlock(sheet, row, col, rowCount, colCount, text) {
const range = sheet.getRange(row, col, rowCount, colCount);
range.hAlign(spreadNS.HorizontalAlign.center);
range.vAlign(spreadNS.VerticalAlign.center);
range.font("600 12px Calibri");
sheet.getCell(row, col).value(text);
if (rowCount > 1 || colCount > 1) {
sheet.addSpan(row, col, rowCount, colCount);
}
}
function addSectionLabel(sheet, row, text) {
sheet.getCell(row, 1)
.value(text)
.font("600 12px Calibri")
.foreColor("#64748b")
.hAlign(spreadNS.HorizontalAlign.left)
.vAlign(spreadNS.VerticalAlign.center);
}
export function setupShowcaseSheet(targetSheet) {
targetSheet.name("CRM Layout");
targetSheet.options.gridline = { showVerticalGridline: false, showHorizontalGridline: false };
targetSheet.options.rowHeaderVisible = false;
targetSheet.options.colHeaderVisible = false;
targetSheet.defaults.rowHeight = 20;
targetSheet.setRowCount(34);
targetSheet.setColumnCount(39);
for (let col = 0; col < 39; col++) {
targetSheet.setColumnWidth(col, 34);
}
for (let row = 0; row < 34; row++) {
targetSheet.setRowHeight(row, 20);
}
}
export function buildShowcase(targetSheet) {
targetSheet.suspendPaint();
try {
targetSheet.getRange(0, 0, 34, 39).backColor("#ffffff");
addInfoPanel(targetSheet, 1, 1, 9, 10, [
["Company no", "20"],
["Company name", "Land of Toys Inc."],
["Type", ""],
["Phone", "2125557818"],
["Fax", ""],
["Email", "kwai@landoftoysinc.com"],
["Web", "www.landoftoysinc.com"],
["Our rep", "Joe Bloggs"],
["Inactive", ""]
]);
addInfoPanel(targetSheet, 1, 12, 9, 10, [
["Address", "897 Long Airport Avenue"],
["", ""],
["", ""],
["Town / City", "NYC"],
["County / State", "NY"],
["Zip / Postcode", "10022"],
["Country", "USA"],
["Area", ""],
["Identity", "Classic Models"]
]);
addNotesPanel(targetSheet, 1, 23, 9, 15);
addTabStrip(targetSheet, 11, 1);
addEnquiriesTable(targetSheet, 13, 1, 15, 37);
targetSheet.setSelection(30, 1, 1, 1);
} finally {
targetSheet.resumePaint();
}
}
function addInfoPanel(targetSheet, row, col, rowCount, colCount, fields) {
const labelCols = Math.floor(colCount * 0.36);
const valueCols = colCount - labelCols;
const panelRange = targetSheet.getRange(row, col, rowCount, colCount);
panelRange.backColor("#eff5fb");
for (let i = 0; i < fields.length; i++) {
const currentRow = row + i;
targetSheet.getRange(currentRow, col, 1, labelCols)
.backColor("#d8e3ee")
.foreColor("#315b83")
.font("600 12px Calibri")
.hAlign(spreadNS.HorizontalAlign.right)
.vAlign(spreadNS.VerticalAlign.center)
.cellPadding("0 5");
targetSheet.getCell(currentRow, col + labelCols - 1).value(fields[i][0]);
targetSheet.getRange(currentRow, col + labelCols, 1, valueCols)
.backColor("#ffffff")
.foreColor("#1f2937")
.font("600 12px Calibri")
.vAlign(spreadNS.VerticalAlign.center)
.cellPadding("0 5");
targetSheet.getCell(currentRow, col + labelCols).value(fields[i][1]);
targetSheet.getCell(currentRow, col + labelCols - 1).borderRight(new spreadNS.LineBorder("#dbe4ee", LineStyle.thin));
if (i < fields.length - 1) {
setRowBottomBorder(targetSheet, currentRow, col, colCount, new spreadNS.LineBorder("#dbe4ee", LineStyle.thin));
}
}
applyRoundedOutline(panelRange, "12", "#9fbddd", targetSheet);
}
function addNotesPanel(targetSheet, row, col, rowCount, colCount) {
const panelRange = targetSheet.getRange(row, col, rowCount, colCount);
panelRange.backColor("#edf4fb");
const headerRange = targetSheet.getRange(row, col, 1, colCount);
headerRange.backColor("#84a9ca").foreColor("#ffffff").font("600 12px Calibri").cellPadding("0 5");
targetSheet.addSpan(row, col, 1, colCount);
targetSheet.getCell(row, col).value("Communications / notes");
const headers = ["Type", "Date", "From", "To", "Details"];
const widths = [2, 2, 5, 4, 2];
let offset = 0;
for (let i = 0; i < headers.length; i++) {
targetSheet.addSpan(row + 1, col + offset, 1, widths[i]);
targetSheet.getRange(row + 1, col + offset, 1, widths[i])
.backColor("#f5f7fa")
.foreColor("#4f6f91")
.font("600 12px Calibri")
.hAlign(spreadNS.HorizontalAlign.left)
.vAlign(spreadNS.VerticalAlign.center)
.cellPadding("0 5");
targetSheet.getCell(row + 1, col + offset).value(headers[i]);
offset += widths[i];
}
setSegmentRowInteriorBorders(targetSheet, row + 1, col, widths, new spreadNS.LineBorder("#bed0e3", LineStyle.thin));
const records = [
["Call", "26/05", "Kwai Lee", "Joe Bloggs", "Quote"],
["Email", "27/05", "Joe Bloggs", "Kwai Lee", "Info"],
["Note", "28/05", "Kwai Lee", "Support", "Specs"],
["Call", "30/05", "Joe Bloggs", "Kwai Lee", "Follow"]
];
for (let dataRow = row + 2; dataRow < row + rowCount - 1; dataRow++) {
const backColor = dataRow % 2 === 0 ? "#f4f8fc" : "#ffffff";
targetSheet.getRange(dataRow, col, 1, colCount)
.backColor(backColor)
.foreColor("#24364a")
.font("600 11px Calibri")
.vAlign(spreadNS.VerticalAlign.center)
.cellPadding("0 5")
.setBorder(new spreadNS.LineBorder("#c2d5e8", LineStyle.thin), { bottom: true });
addNotesRecord(targetSheet, dataRow, col, widths, records[dataRow - row - 2] || ["", "", "", "", ""]);
}
const footerRange = targetSheet.getRange(row + rowCount - 1, col, 1, colCount);
footerRange.backColor("#84a9ca").foreColor("#ffffff").font("600 12px Calibri");
targetSheet.addSpan(row + rowCount - 1, col, 1, colCount);
targetSheet.getCell(row + rowCount - 1, col).value("");
applyRoundedOutline(panelRange, "12", "#9fbddd", targetSheet);
}
function addNotesRecord(targetSheet, row, col, widths, values) {
let currentCol = col;
for (let i = 0; i < widths.length; i++) {
targetSheet.addSpan(row, currentCol, 1, widths[i]);
targetSheet.getCell(row, currentCol).value(values[i]);
currentCol += widths[i];
}
}
function addTabStrip(targetSheet, row, col) {
const tabs = ["Contacts", "Addresses", "Enquiries", "Jobs", "Tasks", "Products", "Quotes", "Orders", "Shipping", "Account", "Documents"];
const widths = [3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3];
let startCol = col;
const stripRange = targetSheet.getRange(row, col, 1, 37);
stripRange.backColor("#f7fbff").setBorder(new spreadNS.LineBorder("#bdd2e6", LineStyle.medium), { outline: true });
setRangeBorderRadius(targetSheet, stripRange, "8", { outer: true });
for (let i = 0; i < tabs.length; i++) {
const active = tabs[i] === "Enquiries";
targetSheet.addSpan(row, startCol, 1, widths[i]);
const tabRange = targetSheet.getRange(row, startCol, 1, widths[i]);
tabRange.backColor(active ? "#84a9ca" : "#f7fbff")
.foreColor(active ? "#ffffff" : "#4f6f91")
.font("600 12px Calibri")
.hAlign(spreadNS.HorizontalAlign.center)
.vAlign(spreadNS.VerticalAlign.center)
.setBorder(new spreadNS.LineBorder("#bdd2e6", LineStyle.thin), { outline: true });
setRangeBorderRadius(targetSheet, tabRange, "4", { outer: true });
targetSheet.getCell(row, startCol).value(tabs[i]);
startCol += widths[i];
}
}
function addEnquiriesTable(targetSheet, row, col, rowCount, colCount) {
const panelRange = targetSheet.getRange(row, col, rowCount, colCount);
panelRange.backColor("#f8fbfe");
const titleRange = targetSheet.getRange(row, col, 1, colCount);
titleRange.backColor("#84a9ca").foreColor("#ffffff").font("600 12px Calibri");
setRangeBorderRadius(targetSheet, titleRange, "10 10 0 0", { outer: true });
targetSheet.addSpan(row, col, 1, colCount);
targetSheet.getCell(row, col).value("Enquiries from this company").cellPadding("0 5");
const headers = [
{ text: "", width: 1 },
{ text: "Ref no", width: 2 },
{ text: "Date", width: 2 },
{ text: "Status", width: 2 },
{ text: "Contact", width: 7 },
{ text: "Our rep", width: 6 },
{ text: "Referred by", width: 7 },
{ text: "Enquiry value", width: 4 },
{ text: "Requirements", width: 6 }
];
let currentCol = col;
for (let i = 0; i < headers.length; i++) {
targetSheet.addSpan(row + 1, currentCol, 1, headers[i].width);
targetSheet.getRange(row + 1, currentCol, 1, headers[i].width)
.backColor("#f5f7fa")
.foreColor("#4f6f91")
.font("600 12px Calibri")
.hAlign(spreadNS.HorizontalAlign.left)
.vAlign(spreadNS.VerticalAlign.center)
.cellPadding("0 5");
targetSheet.getCell(row + 1, currentCol).value(headers[i].text);
currentCol += headers[i].width;
}
setHeaderInteriorBorders(targetSheet, row + 1, col, headers, new spreadNS.LineBorder("#bed0e3", LineStyle.thin));
const records = [
[">", "1", "26/05/16", "Hot", "Kwai Lee", "Joe Bloggs", "Google Adwords", "250.00", "1929 Model T Ford"],
[">", "3", "26/05/16", "Hot", "Kwai Lee", "Joe Bloggs", "Customer", "2,300.00", "Rolls Royce Flying Spur (1: 15 scale)"],
[">", "4", "26/05/16", "Cold", "Kwai Lee", "Joe Bloggs", "Google Adwords", "195.00", "Chevy Nova 1972 (plinth mounted)"],
[">", "2", "26/05/16", "Deal", "Kwai Lee", "Joe Bloggs", "Web site", "495.00", "1968 Camaro with softtop (rare)"]
];
for (let recordIndex = 0; recordIndex < records.length; recordIndex++) {
addTableRecord(targetSheet, row + 2 + recordIndex, col, headers, records[recordIndex]);
}
for (let dataRow = row + 2; dataRow < row + rowCount - 1; dataRow++) {
targetSheet.getRange(dataRow, col, 1, colCount)
.backColor(dataRow % 2 === 0 ? "#eef5fc" : "#ffffff")
.foreColor("#24364a")
.font("600 11px Calibri")
.vAlign(spreadNS.VerticalAlign.center)
.cellPadding("0 5")
.setBorder(new spreadNS.LineBorder("#c2d5e8", LineStyle.thin), { bottom: true });
}
const footerRange = targetSheet.getRange(row + rowCount - 1, col, 1, colCount);
footerRange.backColor("#84a9ca").foreColor("#ffffff").font("600 12px Calibri");
setRangeBorderRadius(targetSheet, footerRange, "0 0 10 10", { outer: true });
targetSheet.addSpan(row + rowCount - 1, col, 1, colCount);
targetSheet.getCell(row + rowCount - 1, col).value("");
applyRoundedOutline(panelRange, "10", "#9fbddd", targetSheet);
}
function addTableRecord(targetSheet, row, col, headers, values) {
let currentCol = col;
for (let i = 0; i < headers.length; i++) {
targetSheet.addSpan(row, currentCol, 1, headers[i].width);
targetSheet.getCell(row, currentCol).value(values[i]);
currentCol += headers[i].width;
}
}
function applyRoundedOutline(range, radius, borderColor, targetSheet) {
range.setBorder(new spreadNS.LineBorder(borderColor, LineStyle.medium), { outline: true });
setRangeBorderRadius(targetSheet, range, radius, { outer: true });
}
function setSegmentRowInteriorBorders(targetSheet, row, col, widths, border) {
setRowBottomBorder(targetSheet, row, col, sumWidths(widths), border);
setInternalVerticalBorders(targetSheet, row, col, widths, border);
}
function setHeaderInteriorBorders(targetSheet, row, col, headers, border) {
const widths = [];
for (let i = 0; i < headers.length; i++) {
widths.push(headers[i].width);
}
setSegmentRowInteriorBorders(targetSheet, row, col, widths, border);
}
function setRowBottomBorder(targetSheet, row, col, colCount, border) {
targetSheet.getRange(row, col, 1, colCount).setBorder(border, { bottom: true });
}
function setInternalVerticalBorders(targetSheet, row, col, widths, border) {
let currentCol = col;
for (let i = 0; i < widths.length - 1; i++) {
currentCol += widths[i];
targetSheet.getCell(row, currentCol - 1).borderRight(border);
}
}
function sumWidths(widths) {
let total = 0;
for (let i = 0; i < widths.length; i++) {
total += widths[i];
}
return total;
}
export function getSettingsFromSelection(sheet, currentSettings) {
const selection = getActiveSelection(sheet);
const style = getSelectionStartStyle(sheet, selection);
const border = getSelectionOutlineBorder(sheet, selection);
const backgroundColor = normalizeHexColor(style && style.backColor);
const nextSettings = { ...currentSettings };
if (backgroundColor) {
nextSettings.backgroundColor = backgroundColor;
nextSettings.backgroundNoFill = false;
} else {
nextSettings.backgroundNoFill = true;
}
if (border) {
nextSettings.borderStyle = getLineStyleName(border.style);
if (normalizeHexColor(border.color)) {
nextSettings.borderColor = normalizeHexColor(border.color);
}
} else {
nextSettings.borderStyle = "none";
}
return nextSettings;
}
function getSelectionStartStyle(sheet, selection) {
return sheet.getActualStyle(selection.row, selection.col, SheetArea)
|| sheet.getStyle(selection.row, selection.col, SheetArea);
}
function getSelectionOutlineBorder(sheet, selection) {
const lastRow = selection.row + selection.rowCount - 1;
const lastCol = selection.col + selection.colCount - 1;
const borderNames = ["borderTop", "borderRight", "borderBottom", "borderLeft"];
const samples = [
[selection.row, selection.col],
[selection.row, lastCol],
[lastRow, lastCol],
[lastRow, selection.col]
];
for (let index = 0; index < samples.length; index++) {
const style = sheet.getStyle(samples[index][0], samples[index][1], SheetArea);
if (!style) {
continue;
}
for (let borderIndex = 0; borderIndex < borderNames.length; borderIndex++) {
const border = style[borderNames[borderIndex]];
if (border) {
return border;
}
}
}
return null;
}
function getLineStyleName(styleValue) {
if (styleValue === undefined || styleValue === null || styleValue === LineStyle.none || styleValue === LineStyle.empty) {
return "none";
}
for (const name in LineStyle) {
if (Object.prototype.hasOwnProperty.call(LineStyle, name) && LineStyle[name] === styleValue) {
return name;
}
}
return "none";
}
function normalizeHexColor(color) {
if (!color) {
return "";
}
const hex = String(color).toLowerCase();
if (/^#[0-9a-f]{6}$/.test(hex)) {
return hex;
}
if (/^#[0-9a-f]{3}$/.test(hex)) {
return "#" + hex.charAt(1) + hex.charAt(1) + hex.charAt(2) + hex.charAt(2) + hex.charAt(3) + hex.charAt(3);
}
return "";
}
export function applyBackgroundColor(sheet, range, settings) {
if (settings.backgroundNoFill) {
clearRangeBackColor(sheet, range);
return;
}
range.backColor(settings.backgroundColor);
}
function clearRangeBackColor(sheet, range) {
forEachCell(range, function (row, col) {
const style = sheet.getStyle(row, col, SheetArea) || new spreadNS.Style();
delete style.backColor;
sheet.setStyle(row, col, style, SheetArea);
});
}
export function applyBorderStyle(sheet, range, borderStyle, borderColor) {
const lineStyle = LineStyle[borderStyle];
updateOutlineBorder(sheet, range, function (border) {
return new spreadNS.LineBorder(getBorderColor(border, borderColor), lineStyle);
});
}
export function applyBorderColor(sheet, range, borderColor, borderStyle) {
updateOutlineBorder(sheet, range, function (border) {
return new spreadNS.LineBorder(borderColor, getBorderStyle(border, borderStyle));
});
}
function updateOutlineBorder(sheet, range, updateBorder) {
const lastRow = range.row + range.rowCount - 1;
const lastCol = range.col + range.colCount - 1;
for (let col = range.col; col <= lastCol; col++) {
updateCellBorder(sheet, range.row, col, "borderTop", updateBorder);
updateCellBorder(sheet, lastRow, col, "borderBottom", updateBorder);
}
for (let row = range.row; row <= lastRow; row++) {
updateCellBorder(sheet, row, range.col, "borderLeft", updateBorder);
updateCellBorder(sheet, row, lastCol, "borderRight", updateBorder);
}
}
function updateCellBorder(sheet, row, col, borderName, updateBorder) {
const style = sheet.getStyle(row, col, SheetArea) || new spreadNS.Style();
style[borderName] = updateBorder(style[borderName]);
sheet.setStyle(row, col, style, SheetArea);
}
function getBorderColor(border, borderColor) {
return border && border.color ? border.color : borderColor;
}
function getBorderStyle(border, borderStyle) {
return border && border.style !== undefined ? border.style : LineStyle[borderStyle];
}
export function applyRadius(sheet, range, settings) {
const option = settings.radiusOption;
setRangeBorderRadius(sheet, range, undefined, { all: true });
if (option === "none") {
return;
}
const setting = {};
setting[option] = true;
setRangeBorderRadius(sheet, range, getRadiusValue(settings), setting);
}
export function getActiveSelection(sheet) {
const selection = sheet.getSelections()[0];
const row = selection.row < 0 ? 0 : selection.row;
const col = selection.col < 0 ? 0 : selection.col;
const rowCount = selection.row < 0 ? sheet.getRowCount() : selection.rowCount;
const colCount = selection.col < 0 ? sheet.getColumnCount() : selection.colCount;
return {
row: row,
col: col,
rowCount: rowCount,
colCount: colCount
};
}
export function setRangeBorderRadius(targetSheet, range, value, options) {
if (typeof range.setBorderRadius === "function") {
range.setBorderRadius(value, options);
return;
}
if (options && options.outer) {
setOuterBorderRadius(targetSheet, range, value);
return;
}
setAllBorderRadius(targetSheet, range, value);
}
function setAllBorderRadius(targetSheet, range, value) {
if (typeof range.borderRadius === "function") {
range.borderRadius(value);
return;
}
forEachCell(range, function (row, col) {
setCellBorderRadius(targetSheet, row, col, value);
});
}
function setOuterBorderRadius(targetSheet, range, value) {
setAllBorderRadius(targetSheet, range, undefined);
if (value === undefined || value === null || value === "") {
return;
}
const radii = parseRadiusValue(value);
const lastRow = range.row + range.rowCount - 1;
const lastCol = range.col + range.colCount - 1;
const cells = {};
addOuterCorner(cells, range.row, range.col, 0, radii[0]);
addOuterCorner(cells, range.row, lastCol, 1, radii[1]);
addOuterCorner(cells, lastRow, lastCol, 2, radii[2]);
addOuterCorner(cells, lastRow, range.col, 3, radii[3]);
for (const key in cells) {
if (Object.prototype.hasOwnProperty.call(cells, key)) {
const parts = key.split(":");
setCellBorderRadius(targetSheet, parseInt(parts[0], 10), parseInt(parts[1], 10), cells[key].join(" "));
}
}
}
function addOuterCorner(cells, row, col, cornerIndex, radius) {
const key = row + ":" + col;
if (!cells[key]) {
cells[key] = ["0", "0", "0", "0"];
}
cells[key][cornerIndex] = String(radius);
}
function parseRadiusValue(value) {
let parts = String(value).split(/\s+/).filter(function (item) {
return item !== "";
});
if (parts.length === 0) {
parts = ["0"];
}
if (parts.length === 1) {
return [parts[0], parts[0], parts[0], parts[0]];
}
if (parts.length === 2) {
return [parts[0], parts[1], parts[0], parts[1]];
}
if (parts.length === 3) {
return [parts[0], parts[1], parts[2], parts[1]];
}
return [parts[0], parts[1], parts[2], parts[3]];
}
function forEachCell(range, callback) {
const rowEnd = range.row + range.rowCount;
const colEnd = range.col + range.colCount;
for (let row = range.row; row < rowEnd; row++) {
for (let col = range.col; col < colEnd; col++) {
callback(row, col);
}
}
}
function setCellBorderRadius(targetSheet, row, col, value) {
const style = targetSheet.getStyle(row, col, SheetArea) || new spreadNS.Style();
style.borderRadius = value;
targetSheet.setStyle(row, col, style, SheetArea);
}
function getRadiusValue(settings) {
return [
normalizeCornerValue(settings.topLeft),
normalizeCornerValue(settings.topRight),
normalizeCornerValue(settings.bottomRight),
normalizeCornerValue(settings.bottomLeft)
].join(" ");
}
function normalizeCornerValue(value) {
let normalizedValue = parseInt(value, 10);
if (isNaN(normalizedValue) || normalizedValue < 0) {
normalizedValue = 0;
}
return normalizedValue;
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="$DEMOROOT$/en/react/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<!-- SystemJS -->
<script src="$DEMOROOT$/en/react/node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('$DEMOROOT$/en/lib/react/license.js').then(function () {
System.import('./src/app');
});
</script>
</head>
<body>
<div id="app"></div>
</body>
</html>
html,
body {
position: absolute;
inset: 0;
margin: 0;
}
#app {
height: 100%;
}
.sample-tutorial {
position: relative;
display: flex;
height: 100%;
overflow: hidden;
background: #ffffff;
}
.sample-spreadsheets {
flex: 1 1 auto;
min-width: 0;
width: auto;
height: 100%;
overflow: hidden;
}
.options-container {
flex: 0 0 400px;
width: 400px;
height: 100%;
box-sizing: border-box;
background: #ffffff;
overflow-y: auto;
overflow-x: hidden;
border-left: 1px solid #eeeeee;
color: #333;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 13px;
}
.panel-section {
padding: 12px 16px;
border-bottom: 1px solid #f0f0f0;
}
.panel-section:last-child {
border-bottom: none;
}
.panel-section-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
font-weight: 600;
font-size: 13px;
color: #555;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.panel-section-description {
margin-bottom: 10px;
color: #64748b;
font-size: 12px;
line-height: 1.35;
}
.panel-card {
background: #fafafa;
border: 1px solid #e8e8e8;
border-radius: 8px;
padding: 14px;
}
.form-group {
margin-bottom: 12px;
}
.form-group:last-child {
margin-bottom: 0;
}
.form-group-inline {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.form-group-inline:last-child {
margin-bottom: 0;
}
.form-group-inline .form-label {
width: 135px;
min-width: 135px;
margin-bottom: 0;
padding: 0 4px;
box-sizing: border-box;
}
.form-group-inline .form-select {
flex: 1;
margin-left: 8px;
}
.form-label {
display: block;
font-size: 12px;
font-weight: 500;
color: #666;
margin-bottom: 4px;
}
.form-select,
.form-input {
width: 100%;
height: 32px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid #d0d0d0;
border-radius: 6px;
background: #fff;
color: #333;
font-size: 13px;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form-select {
cursor: pointer;
}
.form-select:focus,
.form-input:focus {
border-color: #4a7a00;
box-shadow: 0 0 0 2px rgba(74, 122, 0, 0.15);
}
.back-color-field .form-label {
margin-bottom: 0;
}
.back-color-controls {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 12px;
flex: 1;
margin-left: 8px;
}
.no-fill-option {
display: flex;
align-items: center;
gap: 5px;
color: #333;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
}
.back-color-controls input[type="color"] {
width: 44px;
height: 30px;
padding: 2px;
border: 1px solid #d0d0d0;
border-radius: 6px;
background: #ffffff;
cursor: pointer;
}
.back-color-controls input[type="color"]:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.border-field .form-label {
margin-bottom: 0;
}
.border-controls {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
margin-left: 8px;
}
.border-controls .form-select {
flex: 1;
margin-left: 0;
}
.border-controls input[type="color"] {
width: 44px;
height: 30px;
padding: 2px;
border: 1px solid #d0d0d0;
border-radius: 6px;
background: #ffffff;
cursor: pointer;
}
.border-controls input[type="color"]:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.field-hint {
margin: -6px 0 12px 143px;
color: #64748b;
font-size: 12px;
line-height: 1.35;
}
.radio-group {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 6px;
}
.radio-group label {
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
min-height: 32px;
margin: 0;
border: 1px solid #d0d0d0;
border-radius: 6px;
background: #fff;
color: #333;
font-size: 13px;
font-weight: 500;
cursor: pointer;
}
.options-container input[type="radio"],
.options-container input[type="checkbox"] {
accent-color: #4a7a00;
}
.corner-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
}
.corner-grid label {
font-size: 12px;
font-weight: 500;
}
.panel-actions {
margin-top: 14px;
display: flex;
gap: 8px;
}
.options-container .btn {
height: 34px;
padding: 0 16px;
border: none;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.options-container .btn-primary {
background: #4a7a00;
color: #fff;
flex: 1;
}
.options-container .btn-primary:hover {
background: #3d6b00;
}
#applyRadius {
width: 100%;
}
(function (global) {
System.config({
transpiler: 'plugin-babel',
babelOptions: {
es2015: true,
react: true
},
meta: {
'*.css': { loader: 'css' }
},
paths: {
'npm:': 'node_modules/',
'cdn:': 'https://cdn.mescius.io/demoapps/packages/spreadjs/19.2.2-master-2026-09-02-2133/'
},
map: {
'@mescius/spread-sheets': 'cdn:@mescius/spread-sheets/index.js',
'@mescius/spread-sheets-react': 'cdn:@mescius/spread-sheets-react/index.js',
'@grapecity/jsob-test-dependency-package/react-components': 'npm:@grapecity/jsob-test-dependency-package/react-components/index.js',
'react': 'npm:react/cjs/react.production.js',
'react-dom': 'npm:react-dom/cjs/react-dom.production.js',
'react-dom/client': 'npm:react-dom/cjs/react-dom-client.production.js',
'scheduler': 'npm:scheduler/cjs/scheduler.production.js',
'css': 'npm:systemjs-plugin-css/css.js',
'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js'
},
packages: {
src: {
defaultExtension: 'jsx'
},
"node_modules": {
defaultExtension: 'js'
},
}
});
})(this);