SpreadJS는 Range.setBorderRadius, Range.borderRadius 및 Style.borderRadius를 사용한 둥근 워크시트 범위를 지원합니다. 테두리 반경을 사용해 카드 형태의 셀 영역, 탭 머리글, 상태 단계 및 모서리가 둥근 워크시트 UI 요소를 만들 수 있습니다.
테두리 반경은 다음과 같은 방식으로 범위에 적용할 수 있습니다.
Outer는 대상 범위의 바깥쪽 모서리에만 반경을 적용합니다. 여러 셀을 하나의 둥근 블록처럼 표시할 때 유용합니다.
All은 대상 범위의 모든 셀에 반경을 적용합니다. 각 셀이 고유한 둥근 모양을 유지해야 할 때 유용합니다.
undefined를 전달하면 대상 범위의 테두리 반경이 지워집니다.
borderRadius 값은 공백으로 구분된 1~4개의 토큰 문자열일 수 있습니다. 토큰은 왼쪽 위, 오른쪽 위, 오른쪽 아래, 왼쪽 아래 순서로 모서리를 나타냅니다.
토큰 하나는 네 모서리에 같은 반경을 적용합니다. "14"는 14 14 14 14를 의미합니다.
토큰 두 개는 첫 번째 값을 왼쪽 위와 오른쪽 아래에, 두 번째 값을 오른쪽 위와 왼쪽 아래에 적용합니다. "14 8"은 14 8 14 8을 의미합니다.
토큰 세 개는 첫 번째 값을 왼쪽 위, 두 번째 값을 오른쪽 위와 왼쪽 아래, 세 번째 값을 오른쪽 아래에 적용합니다. "14 8 4"는 14 8 4 8을 의미합니다.
토큰 네 개는 왼쪽 위, 오른쪽 위, 오른쪽 아래, 왼쪽 아래 순서로 각 모서리를 설정합니다. "14 8 4 0"은 각각 14, 8, 4, 0을 의미합니다.
재사용 가능한 셀 서식이 필요한 경우 스타일을 통해 테두리 반경을 설정할 수도 있습니다.
테두리 반경은 셀 배경과 대각선 테두리를 포함한 렌더링된 셀 모양을 자릅니다.
var spreadNS = GC.Spread.Sheets;
var SheetArea = spreadNS.SheetArea.viewport;
var LineStyle = spreadNS.LineStyle;
var spread;
var sheet;
var showcaseSheet;
// Initializes the workbook, builds both sheets, and wires the style panel.
window.onload = function () {
spread = new spreadNS.Workbook(document.getElementById("ss"), { sheetCount: 2 });
spread.options.newTabVisible = false;
sheet = spread.getSheet(0);
showcaseSheet = spread.getSheet(1);
spread.suspendPaint();
try {
setupSheet();
buildSamples();
setupShowcaseSheet(showcaseSheet);
buildShowcase(showcaseSheet);
bindPanel();
bindSheetPanelVisibility();
sheet.setSelection(3, 2, 7, 3);
spread.setActiveSheetIndex(0);
updatePanelFromSelection();
updatePanelVisibility();
} finally {
spread.resumePaint();
}
};
// Builds the editable border-radius sample sheet.
function setupSheet() {
sheet.name("테두리 반경");
sheet.defaults.rowHeight = 24;
sheet.setRowCount(28);
sheet.setColumnCount(16);
sheet.setRowHeight(1, 36);
for (var col = 1; col <= 14; col++) {
sheet.setColumnWidth(col, 48);
}
}
function buildSamples() {
addTitle();
addCardSample();
addTabsSample();
addStatusStepsSample();
}
function addTitle() {
sheet.getCell(1, 1)
.value("범위를 선택한 다음 패널에서 스타일을 조정합니다.")
.font("13px Calibri")
.foreColor("#64748b")
.vAlign(spreadNS.VerticalAlign.center);
}
function addCardSample() {
addSectionLabel(3, "카드");
addSpanCard(3, 2);
addRangeCard(3, 6);
addBackgroundOnlyCard(3, 10);
}
function addSpanCard(row, col) {
var range = sheet.getRange(row, col, 7, 3);
range.backColor("#f1f6ea");
range.setBorder(new spreadNS.LineBorder("#6f8f45", LineStyle.medium), { outline: true });
setRangeBorderRadius(range, "14", { outer: true });
centerBlock(row, col, 7, 3, "바깥쪽 반경");
range.foreColor("#2f3a25");
}
function addRangeCard(row, col) {
var range = sheet.getRange(row, col, 7, 3);
range.backColor("#f3eff8");
range.setBorder(new spreadNS.LineBorder("#9a86b8", LineStyle.medium), { outline: true });
setRangeBorderRadius(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("모든 셀 반경");
}
function addBackgroundOnlyCard(row, col) {
var range = sheet.getRange(row, col, 7, 3);
range.backColor("#DCEFEF");
setRangeBorderRadius(range, "20", { outer: true });
centerBlock(row, col, 7, 3, "채우기만");
range.foreColor("#46514a");
}
function addTabsSample() {
addSectionLabel(12, "탭");
addTab(12, 2, "개요", false);
addTab(12, 4, "세부 정보", false);
addTab(12, 6, "데이터", true);
addTab(12, 8, "설정", false);
addTab(12, 10, "기록", false);
}
function addTab(row, col, text, active) {
var range = sheet.getRange(row, col, 1, 2);
range.backColor(active ? "#F6ECE8" : "#FCFAF9");
range.setBorder(new spreadNS.LineBorder("#E7DAD6", LineStyle.medium), { outline: true });
setRangeBorderRadius(range, "0 10 0 0", { all: true });
centerBlock(row, col, 1, 2, text);
range.foreColor(active ? "#6B4E47" : "#8A756E");
if(active){
range.borderBottom(new spreadNS.LineBorder("#B67868", LineStyle.medium));
}
}
function addStatusStepsSample() {
addSectionLabel(15, "단계");
addStatusStep(15, 2, "1 계획", "#EEF3F8", "#8CA3BE", "#3E5168", "14");
addStatusStep(15, 6, "2 빌드", "#f8f3e8", "#b89b5e", "#5c4a22", "14");
addStatusStep(15, 10, "3 출시", "#eef1ee", "#8a958d", "#3f4942", "14");
}
function addStatusStep(row, col, text, backColor, borderColor, foreColor, radius) {
var range = sheet.getRange(row, col, 2, 3);
range.backColor(backColor);
range.setBorder(new spreadNS.LineBorder(borderColor, LineStyle.medium), { outline: true });
setRangeBorderRadius(range, radius, { outer: true });
centerBlock(row, col, 2, 3, text);
range.foreColor(foreColor);
}
function centerBlock(row, col, rowCount, colCount, text) {
var 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(row, text) {
sheet.getCell(row, 1)
.value(text)
.font("600 12px Calibri")
.foreColor("#64748b")
.hAlign(spreadNS.HorizontalAlign.left)
.vAlign(spreadNS.VerticalAlign.center);
}
// Builds the read-only CRM layout sheet used to showcase rounded worksheet UI.
function setupShowcaseSheet(targetSheet) {
targetSheet.name("CRM 레이아웃");
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 (var col = 0; col < 39; col++) {
targetSheet.setColumnWidth(col, 34);
}
for (var row = 0; row < 34; row++) {
targetSheet.setRowHeight(row, 20);
}
}
function buildShowcase(targetSheet) {
targetSheet.suspendPaint();
try {
targetSheet.getRange(0, 0, 34, 39).backColor("#ffffff");
addInfoPanel(targetSheet, 1, 1, 9, 10, [
["회사 번호", "20"],
["회사명", "Land of Toys Inc."],
["유형", ""],
["전화", "2125557818"],
["팩스", ""],
["이메일", "kwai@landoftoysinc.com"],
["웹", "www.landoftoysinc.com"],
["담당자", "Joe Bloggs"],
["비활성", ""]
]);
addInfoPanel(targetSheet, 1, 12, 9, 10, [
["주소", "897 Long Airport Avenue"],
["", ""],
["", ""],
["도시", "NYC"],
["도/주", "NY"],
["우편번호", "10022"],
["국가", "USA"],
["지역", ""],
["ID", "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) {
var labelCols = Math.floor(colCount * 0.36);
var valueCols = colCount - labelCols;
var panelRange = targetSheet.getRange(row, col, rowCount, colCount);
panelRange.backColor("#eff5fb");
for (var i = 0; i < fields.length; i++) {
var 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) {
var panelRange = targetSheet.getRange(row, col, rowCount, colCount);
panelRange.backColor("#edf4fb");
var 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("연락 / 메모");
var headers = ["유형", "날짜", "보낸 사람", "받는 사람", "세부 정보"];
var widths = [2, 2, 5, 4, 2];
var offset = 0;
for (var 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));
var records = [
["전화", "26/05", "Kwai Lee", "Joe Bloggs", "견적"],
["이메일", "27/05", "Joe Bloggs", "Kwai Lee", "정보"],
["메모", "28/05", "Kwai Lee", "지원", "사양"],
["전화", "30/05", "Joe Bloggs", "Kwai Lee", "후속"]
];
for (var dataRow = row + 2; dataRow < row + rowCount - 1; dataRow++) {
var 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] || ["", "", "", "", ""]);
}
var 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) {
var currentCol = col;
for (var 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) {
var tabs = ["연락처", "주소", "문의", "작업", "할 일", "제품", "견적", "주문", "배송", "계정", "문서"];
var widths = [3, 3, 4, 3, 3, 4, 3, 3, 4, 4, 3];
var startCol = col;
var stripRange = targetSheet.getRange(row, col, 1, 37);
stripRange.backColor("#f7fbff").setBorder(new spreadNS.LineBorder("#bdd2e6", LineStyle.medium), { outline: true });
setRangeBorderRadius(stripRange, "8", { outer: true }, targetSheet);
for (var i = 0; i < tabs.length; i++) {
var active = tabs[i] === "문의";
targetSheet.addSpan(row, startCol, 1, widths[i]);
var 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(tabRange, "4", { outer: true }, targetSheet);
targetSheet.getCell(row, startCol).value(tabs[i]);
startCol += widths[i];
}
}
function addEnquiriesTable(targetSheet, row, col, rowCount, colCount) {
var panelRange = targetSheet.getRange(row, col, rowCount, colCount);
panelRange.backColor("#f8fbfe");
var titleRange = targetSheet.getRange(row, col, 1, colCount);
titleRange.backColor("#84a9ca").foreColor("#ffffff").font("600 12px Calibri");
setRangeBorderRadius(titleRange, "10 10 0 0", { outer: true }, targetSheet);
targetSheet.addSpan(row, col, 1, colCount);
targetSheet.getCell(row, col).value("이 회사의 문의").cellPadding("0 5");
var headers = [
{ text: "", width: 1 },
{ text: "참조 번호", width: 2 },
{ text: "날짜", width: 2 },
{ text: "상태", width: 2 },
{ text: "연락처", width: 7 },
{ text: "담당자", width: 6 },
{ text: "추천 경로", width: 7 },
{ text: "문의 금액", width: 4 },
{ text: "요구 사항", width: 6 }
];
var currentCol = col;
for (var 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));
var records = [
[">", "1", "26/05/16", "긴급", "Kwai Lee", "Joe Bloggs", "Google Adwords", "250.00", "1929 Model T Ford"],
[">", "3", "26/05/16", "긴급", "Kwai Lee", "Joe Bloggs", "고객", "2,300.00", "Rolls Royce Flying Spur (1: 15 scale)"],
[">", "4", "26/05/16", "보류", "Kwai Lee", "Joe Bloggs", "Google Adwords", "195.00", "Chevy Nova 1972 (plinth mounted)"],
[">", "2", "26/05/16", "계약", "Kwai Lee", "Joe Bloggs", "웹 사이트", "495.00", "1968 Camaro with softtop (rare)"]
];
for (var recordIndex = 0; recordIndex < records.length; recordIndex++) {
addTableRecord(targetSheet, row + 2 + recordIndex, col, headers, records[recordIndex]);
}
for (var 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 });
}
var footerRange = targetSheet.getRange(row + rowCount - 1, col, 1, colCount);
footerRange.backColor("#84a9ca").foreColor("#ffffff").font("600 12px Calibri");
setRangeBorderRadius(footerRange, "0 0 10 10", { outer: true }, targetSheet);
targetSheet.addSpan(row + rowCount - 1, col, 1, colCount);
targetSheet.getCell(row + rowCount - 1, col).value("");
applyRoundedOutline(panelRange, "10", "#9fbddd", targetSheet);
}
// Shared helpers for rendering table-like rows and rounded panel outlines.
function addTableRecord(targetSheet, row, col, headers, values) {
var currentCol = col;
for (var 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(range, radius, { outer: true }, targetSheet);
}
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) {
var widths = [];
for (var 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) {
var currentCol = col;
for (var i = 0; i < widths.length - 1; i++) {
currentCol += widths[i];
targetSheet.getCell(row, currentCol - 1).borderRight(border);
}
}
function sumWidths(widths) {
var total = 0;
for (var i = 0; i < widths.length; i++) {
total += widths[i];
}
return total;
}
// Wires the right-side style panel to the active selection.
function bindPanel() {
getElement("backgroundNoFill").addEventListener("change", function () {
updateBackColorState();
applyBackgroundColorSettings();
});
getElement("backgroundColor").addEventListener("change", function () {
getElement("backgroundNoFill").checked = false;
updateBackColorState();
applyBackgroundColorSettings();
});
getElement("borderStyle").addEventListener("change", function () {
updateBorderColorState();
applyBorderStyleSettings();
});
getElement("borderColor").addEventListener("change", applyBorderColorSettings);
getElement("applyRadius").addEventListener("click", applyRadiusSettings);
sheet.bind(spreadNS.Events.SelectionChanged, updatePanelFromSelection);
updateBackColorState();
updateBorderColorState();
}
function bindSheetPanelVisibility() {
spread.bind(spreadNS.Events.ActiveSheetChanged, updatePanelVisibility);
}
function updatePanelVisibility() {
var optionsContainer = getElement("optionsContainer");
if (!optionsContainer) {
return;
}
var isShowcaseSheet = spread.getActiveSheetIndex() === 1;
optionsContainer.style.display = isShowcaseSheet ? "none" : "block";
refreshSpreadLayout();
if (!isShowcaseSheet) {
updatePanelFromSelection();
}
}
function refreshSpreadLayout() {
spread.refresh();
setTimeout(function () {
spread.refresh();
}, 0);
}
function updateBackColorState() {
getElement("backgroundColor").disabled = getElement("backgroundNoFill").checked;
}
function updateBorderColorState() {
getElement("borderColor").disabled = getElement("borderStyle").value === "none";
}
// Reads the selected range and mirrors its style back into the panel controls.
function updatePanelFromSelection() {
var selection = getActiveSelection();
var style = getSelectionStartStyle(selection);
var border = getSelectionOutlineBorder(selection);
var backgroundColor = normalizeHexColor(style && style.backColor);
if (backgroundColor) {
getElement("backgroundNoFill").checked = false;
getElement("backgroundColor").value = backgroundColor;
} else {
getElement("backgroundNoFill").checked = true;
}
if (border) {
getElement("borderStyle").value = getLineStyleName(border.style);
if (normalizeHexColor(border.color)) {
getElement("borderColor").value = normalizeHexColor(border.color);
}
} else {
getElement("borderStyle").value = "none";
}
updateBackColorState();
updateBorderColorState();
}
function getSelectionStartStyle(selection) {
return sheet.getActualStyle(selection.row, selection.col, SheetArea)
|| sheet.getStyle(selection.row, selection.col, SheetArea);
}
function getSelectionOutlineBorder(selection) {
var lastRow = selection.row + selection.rowCount - 1;
var lastCol = selection.col + selection.colCount - 1;
var borderNames = ["borderTop", "borderRight", "borderBottom", "borderLeft"];
var samples = [
[selection.row, selection.col],
[selection.row, lastCol],
[lastRow, lastCol],
[lastRow, selection.col]
];
for (var index = 0; index < samples.length; index++) {
var style = sheet.getStyle(samples[index][0], samples[index][1], SheetArea);
if (!style) {
continue;
}
for (var borderIndex = 0; borderIndex < borderNames.length; borderIndex++) {
var 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 (var name in LineStyle) {
if (LineStyle.hasOwnProperty(name) && LineStyle[name] === styleValue) {
return name;
}
}
return "none";
}
function normalizeHexColor(color) {
if (!color) {
return "";
}
var 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 "";
}
// Applies panel changes to the current selection.
function applyBackgroundColorSettings(focusSpread) {
var selection = getActiveSelection();
var range = sheet.getRange(selection.row, selection.col, selection.rowCount, selection.colCount);
sheet.suspendPaint();
try {
if (getElement("backgroundNoFill").checked) {
clearRangeBackColor(range);
} else {
range.backColor(getElement("backgroundColor").value);
}
sheet.setSelection(selection.row, selection.col, selection.rowCount, selection.colCount);
} finally {
sheet.resumePaint();
}
if (focusSpread !== false) {
spread.focus();
}
}
function clearRangeBackColor(range) {
forEachCell(range, function (row, col) {
var style = sheet.getStyle(row, col, SheetArea) || new spreadNS.Style();
delete style.backColor;
sheet.setStyle(row, col, style, SheetArea);
});
}
function applyBorderStyleSettings() {
var selection = getActiveSelection();
var range = sheet.getRange(selection.row, selection.col, selection.rowCount, selection.colCount);
sheet.suspendPaint();
try {
applyBorderStyle(range);
sheet.setSelection(selection.row, selection.col, selection.rowCount, selection.colCount);
} finally {
sheet.resumePaint();
}
spread.focus();
}
function applyBorderColorSettings(focusSpread) {
var selection = getActiveSelection();
var range = sheet.getRange(selection.row, selection.col, selection.rowCount, selection.colCount);
sheet.suspendPaint();
try {
applyBorderColor(range);
sheet.setSelection(selection.row, selection.col, selection.rowCount, selection.colCount);
} finally {
sheet.resumePaint();
}
if (focusSpread !== false) {
spread.focus();
}
}
function applyRadiusSettings() {
var selection = getActiveSelection();
var range = sheet.getRange(selection.row, selection.col, selection.rowCount, selection.colCount);
sheet.suspendPaint();
try {
applyRadius(range);
sheet.setSelection(selection.row, selection.col, selection.rowCount, selection.colCount);
} finally {
sheet.resumePaint();
}
spread.focus();
}
function applyBorderStyle(range) {
var lineStyle = LineStyle[getElement("borderStyle").value];
updateOutlineBorder(range, function (border) {
return new spreadNS.LineBorder(getBorderColor(border), lineStyle);
});
}
function applyBorderColor(range) {
var borderColor = getElement("borderColor").value;
updateOutlineBorder(range, function (border) {
return new spreadNS.LineBorder(borderColor, getBorderStyle(border));
});
}
function updateOutlineBorder(range, updateBorder) {
var lastRow = range.row + range.rowCount - 1;
var lastCol = range.col + range.colCount - 1;
for (var col = range.col; col <= lastCol; col++) {
updateCellBorder(range.row, col, "borderTop", updateBorder);
updateCellBorder(lastRow, col, "borderBottom", updateBorder);
}
for (var row = range.row; row <= lastRow; row++) {
updateCellBorder(row, range.col, "borderLeft", updateBorder);
updateCellBorder(row, lastCol, "borderRight", updateBorder);
}
}
function updateCellBorder(row, col, borderName, updateBorder) {
var style = sheet.getStyle(row, col, SheetArea) || new spreadNS.Style();
style[borderName] = updateBorder(style[borderName]);
sheet.setStyle(row, col, style, SheetArea);
}
function getBorderColor(border) {
return border && border.color ? border.color : getElement("borderColor").value;
}
function getBorderStyle(border) {
return border && border.style !== undefined ? border.style : LineStyle[getElement("borderStyle").value];
}
// Applies radius values using the current API when available, with a style fallback for older builds.
function applyRadius(range) {
var option = getSelectedRadiusOption();
setRangeBorderRadius(range, undefined, { all: true });
if (option === "none") {
return;
}
var setting = {};
setting[option] = true;
setRangeBorderRadius(range, getRadiusValue(), setting);
}
function getActiveSelection() {
var selection = sheet.getSelections()[0];
var row = selection.row < 0 ? 0 : selection.row;
var col = selection.col < 0 ? 0 : selection.col;
var rowCount = selection.row < 0 ? sheet.getRowCount() : selection.rowCount;
var colCount = selection.col < 0 ? sheet.getColumnCount() : selection.colCount;
return {
row: row,
col: col,
rowCount: rowCount,
colCount: colCount
};
}
// Border-radius compatibility helpers.
function setRangeBorderRadius(range, value, options, targetSheet) {
targetSheet = targetSheet || sheet;
if (typeof range.setBorderRadius === "function") {
range.setBorderRadius(value, options);
return;
}
if (options && options.outer) {
setOuterBorderRadius(range, value, targetSheet);
return;
}
setAllBorderRadius(range, value, targetSheet);
}
function setAllBorderRadius(range, value, targetSheet) {
targetSheet = targetSheet || sheet;
if (typeof range.borderRadius === "function") {
range.borderRadius(value);
return;
}
forEachCell(range, function (row, col) {
setCellBorderRadius(row, col, value, targetSheet);
});
}
function setOuterBorderRadius(range, value, targetSheet) {
targetSheet = targetSheet || sheet;
setAllBorderRadius(range, undefined, targetSheet);
if (value === undefined || value === null || value === "") {
return;
}
var radii = parseRadiusValue(value);
var lastRow = range.row + range.rowCount - 1;
var lastCol = range.col + range.colCount - 1;
var 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 (var key in cells) {
if (cells.hasOwnProperty(key)) {
var parts = key.split(":");
setCellBorderRadius(parseInt(parts[0], 10), parseInt(parts[1], 10), cells[key].join(" "), targetSheet);
}
}
}
function addOuterCorner(cells, row, col, cornerIndex, radius) {
var key = row + ":" + col;
if (!cells[key]) {
cells[key] = ["0", "0", "0", "0"];
}
cells[key][cornerIndex] = String(radius);
}
function parseRadiusValue(value) {
var 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) {
var rowEnd = range.row + range.rowCount;
var colEnd = range.col + range.colCount;
for (var row = range.row; row < rowEnd; row++) {
for (var col = range.col; col < colEnd; col++) {
callback(row, col);
}
}
}
function setCellBorderRadius(row, col, value, targetSheet) {
targetSheet = targetSheet || sheet;
var style = targetSheet.getStyle(row, col, SheetArea) || new spreadNS.Style();
style.borderRadius = value;
targetSheet.setStyle(row, col, style, SheetArea);
}
function getRadiusValue() {
return [
normalizeCornerValue("topLeft"),
normalizeCornerValue("topRight"),
normalizeCornerValue("bottomRight"),
normalizeCornerValue("bottomLeft")
].join(" ");
}
function normalizeCornerValue(id) {
var value = parseInt(getElement(id).value, 10);
if (isNaN(value) || value < 0) {
value = 0;
}
return value;
}
function getSelectedRadiusOption() {
var checked = document.querySelector("input[name='radiusOption']:checked");
return checked ? checked.value : "outer";
}
function getElement(id) {
return document.getElementById(id);
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta charset="utf-8" />
<meta name="spreadjs culture" content="ko-kr" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css" href="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<script src="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets/dist/gc.spread.sheets.all.min.js" type="text/javascript"></script>
<script src="$DEMOROOT$/ko/purejs/node_modules/@mescius/spread-sheets-resources-ko/dist/gc.spread.sheets.resources.ko.min.js" type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="sample-tutorial">
<div id="ss" class="sample-spreadsheets"></div>
<div id="optionsContainer" class="options-container">
<div class="panel-section">
<div class="panel-section-title">
<span>스타일</span>
</div>
<div class="panel-section-description">선택한 영역과 동기화되며 변경 사항이 즉시 적용됩니다.</div>
<div class="panel-card">
<div class="form-group-inline back-color-field">
<label class="form-label" for="backgroundColor">배경색</label>
<div class="back-color-controls">
<label class="no-fill-option"><input type="checkbox" id="backgroundNoFill"> No Fill</label>
<input type="color" id="backgroundColor" value="#f1f6ea" aria-label="Back color">
</div>
</div>
<div class="form-group-inline border-field">
<label class="form-label" for="borderStyle">테두리</label>
<div class="border-controls">
<select id="borderStyle" class="form-select">
<option value="none">없음</option>
<option value="thin">얇게</option>
<option value="medium" selected>중간</option>
<option value="dashed">파선</option>
<option value="dotted">점선</option>
<option value="thick">굵게</option>
<option value="double">이중선</option>
<option value="hair">가는 선</option>
<option value="mediumDashed">중간 파선</option>
<option value="dashDot">쇄선</option>
<option value="mediumDashDot">중간 쇄선</option>
<option value="dashDotDot">일점쇄선</option>
<option value="mediumDashDotDot">중간 일점쇄선</option>
<option value="slantedDashDot">기울어진 쇄선</option>
</select>
<input type="color" id="borderColor" value="#6f8f45" aria-label="Border color">
</div>
</div>
<div class="field-hint">기본적으로 선택한 범위의 윤곽선에 테두리가 적용됩니다.</div>
</div>
</div>
<div class="panel-section">
<div class="panel-section-title">
<span>반경</span>
</div>
<div class="panel-section-description">반경 값을 설정한 다음 반경 적용을 클릭합니다.</div>
<div class="panel-card">
<div class="form-group">
<label class="form-label">적용 대상</label>
<div class="radio-group">
<label><input type="radio" name="radiusOption" value="outer" checked> Outer</label>
<label><input type="radio" name="radiusOption" value="all"> All</label>
<label><input type="radio" name="radiusOption" value="none"> None</label>
</div>
</div>
<div class="corner-grid">
<label class="form-label" for="topLeft">왼쪽 위 <input class="form-input" type="number" id="topLeft" min="0" max="40" value="14"></label>
<label class="form-label" for="topRight">오른쪽 위 <input class="form-input" type="number" id="topRight" min="0" max="40" value="14"></label>
<label class="form-label" for="bottomLeft">왼쪽 아래 <input class="form-input" type="number" id="bottomLeft" min="0" max="40" value="14"></label>
<label class="form-label" for="bottomRight">오른쪽 아래 <input class="form-input" type="number" id="bottomRight" min="0" max="40" value="14"></label>
</div>
<div class="panel-actions">
<button id="applyRadius" class="btn btn-primary" type="button">반경 적용</button>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
html,
body {
position: absolute;
inset: 0;
margin: 0;
}
.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%;
}