테두리 반경

테두리 반경을 사용해 둥근 워크시트 범위를 만듭니다. 범위의 바깥쪽 가장자리나 범위의 모든 셀에 반경을 적용하거나 범위 및 스타일 API를 사용할 수 있습니다.

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%; }