Merge remote-tracking branch 'origin/sub_laptop_8' into sub_laptop_3

CAD 겉 겹침 = sub3 판 기준 · sub8 의 문서 탭 × · + 아이콘 단추 부품 · test_cad_host_tabs 고침 · 도각 편집 글 크기 살림 · main.tsx 의 injectBaseStyles 겹침 걷음(ui-kit 한 곳)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 22:37:45 +09:00
co-authored by Claude Opus 5.5
21 changed files with 140 additions and 299 deletions
+16 -56
View File
@@ -109,7 +109,7 @@
bottom: 40px;
z-index: 20;
padding: 6px 10px;
border-radius: 4px;
border-radius: var(--radius-buttons);
background: var(--color-primary);
color: var(--color-primary-text);
}
@@ -126,6 +126,10 @@
cursor: pointer;
}
.ss-brush:hover {
background: var(--color-hover);
}
.ss-brush.is-on {
background: color-mix(in srgb, var(--color-focus-ring) 20%, var(--color-surface-raised));
box-shadow: inset 0 0 0 2px var(--color-focus-ring);
@@ -135,19 +139,17 @@
cursor: copy;
}
.ss-find {
position: absolute;
/* 찾기 = 떠 있는 판 부품(머리 없음) · 칸 · 단추 = 부품 — 여기는 자리 · 줄 배치만(553-6) */
.ss-find.ui-float-panel {
top: 4px;
right: 16px;
z-index: 20;
}
.ss-find > .ui-float-panel__body {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-find[hidden],
@@ -166,32 +168,17 @@
.ss-find__input {
width: 180px;
padding: 2px 6px;
border: 1px solid var(--color-border);
background: var(--color-surface-raised);
color: var(--color-text-body);
font: inherit;
}
.ss-find__btn {
padding: 2px 8px;
border: 1px solid var(--color-border);
border-radius: 4px;
background: var(--color-surface);
color: var(--color-text-body);
font: inherit;
cursor: pointer;
}
.ss-find__opt {
display: flex;
align-items: center;
gap: 2px;
font-size: 12px;
font-size: var(--text-caption);
}
.ss-find__status {
font-size: 12px;
font-size: var(--text-caption);
color: var(--color-text-secondary);
}
@@ -223,19 +210,11 @@
gap: 2px;
padding: 4px;
border: 1px solid var(--color-border);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-pick > button {
padding: 2px 8px;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text-body);
font: inherit;
cursor: pointer;
}
/* 함수 자동 완성 · 인자 도움말 */
.ss-hint {
position: absolute;
@@ -265,22 +244,17 @@
}
.ss-hint__item.is-on {
background: color-mix(in srgb, var(--color-accent) 18%, var(--color-surface-raised));
background: var(--color-accent-soft);
}
/* 데이터 메뉴 — 패널 창 · 유효성 목록 ▼ */
.ss-dialog {
position: absolute;
/* 패널 창 = 떠 있는 판 부품 — 여기는 자리 · 크기만(553-6) */
.ss-dialog.ui-float-panel {
top: 4px;
right: 16px;
z-index: 21;
max-width: min(720px, calc(100% - 32px));
max-height: calc(100% - 8px);
overflow: auto;
border: 1px solid var(--color-border);
border-radius: 6px;
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-dialog[hidden],
@@ -288,20 +262,6 @@
display: none;
}
.ss-dialog__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 6px 8px;
border-bottom: 1px solid var(--color-border);
font-weight: bold;
}
.ss-dialog__body {
padding: 8px;
}
.ss-list-arrow {
position: absolute;
z-index: 8;
@@ -118,10 +118,15 @@ export function dialogKeys(overlay: HTMLElement, close: () => void, run?: () =>
});
}
/** body 에 뜬 창(.ui-confirm)이 다 닫히면 초점을 격자로 — 창을 연 바로 뒤에 부름 */
/** body 에 뜬 창(.ui-confirm · 모달)이 다 닫히면 초점을 격자로 — 창을 연 바로 뒤에 부름 */
export function watchClose(back: () => void): void {
const w = new MutationObserver(() => {
if (document.querySelector("body > .ui-confirm, body > .ui-confirm__panel")) return;
if (
document.querySelector(
"body > .ui-confirm, body > .ui-confirm__panel, body > .ui-modal__backdrop",
)
)
return;
w.disconnect();
back();
});
@@ -63,7 +63,7 @@
display: flex;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 18%);
background: var(--color-scrim);
}
.ss-steps__box {
@@ -115,18 +115,10 @@
overflow: visible;
}
.ss-errtip__btn {
/* ⚠ = 아이콘 단추 부품(선 · 18) — 여기는 자리 · 경고 색만(553-6) */
.ss-errtip__btn.ui-icon-btn {
position: absolute;
width: 18px;
height: 18px;
padding: 0;
font-size: 11px;
line-height: 16px;
cursor: pointer;
color: var(--color-warning);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
}
.ss-errtip__balloon {
@@ -183,5 +175,5 @@
}
.ss-errtip__item:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}
@@ -12,6 +12,7 @@
import "./spreadsheet_stage3.css"; // sub4 패널 스타일(잇는 쪽이 붙임)
import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu";
import { el, showToast } from "@ui/ui_template_elements";
import { createFloatPanel } from "@ui/ui_template_float_panel";
import { parseRange, rangeToA1, toA1 } from "./spreadsheet_address";
import { autoSumCells, autoSumRange } from "./spreadsheet_autosum";
import { isError } from "./spreadsheet_eval";
@@ -40,7 +41,6 @@ const TEXT = {
validation: "데이터 유효성…",
names: "이름 관리…",
grid: "눈금선 보이기 · 숨기기",
close: "닫기",
list: "값 목록(Alt+↓)",
sheets: "시트 숨기기 · 탭 색…",
audit: "수식 분석",
@@ -121,18 +121,14 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
};
// ── 패널 창(조건부 서식 · 유효성 · 이름) — 한 번에 하나 ─────────────────────
const title = el("span", { className: "ss-dialog__title" });
const body = el("div", { className: "ss-dialog__body" });
const shut = el("button", {
className: "ss-find__btn",
text: "×",
attrs: { type: "button", title: TEXT.close },
});
const dialog = el("div", {
className: "ss-dialog ui-popup",
attrs: { hidden: "" },
children: [el("div", { className: "ss-dialog__head", children: [title, shut] }), body],
// 떠 있는 판 부품(머리 · 닫기 ✕ · 몸 · 553-6) — 자리는 `.ss-dialog`
const { root: dialog, body } = createFloatPanel({
title: " ",
onClose: () => (closePanel(), back()),
});
dialog.classList.add("ss-dialog");
dialog.setAttribute("hidden", "");
const title = dialog.querySelector(".ui-float-panel__title") as HTMLElement;
ctx.root.append(dialog);
let panel: PartHandle | null = null;
const closePanel = (): void => {
@@ -141,7 +137,6 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
body.replaceChildren();
dialog.setAttribute("hidden", "");
};
shut.addEventListener("click", () => (closePanel(), back()));
dialog.addEventListener("keydown", (e) => {
if (e.key !== "Escape") return;
e.stopPropagation();
@@ -11,7 +11,7 @@
* 시트 전체를 훑지 말고 그리는 칸만 물음(값 하나 · 칸 하나 읽는 값싼 부름).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createIconButton, el } from "@ui/ui_template_elements";
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { toA1 } from "./spreadsheet_address";
import { isError } from "./spreadsheet_eval";
@@ -137,11 +137,9 @@ export function attachErrorTip(ctx: SpreadsheetContext, trace?: TraceHandle): Er
const g = ctx.grid.root.getBoundingClientRect();
layer.style.left = `${box.x - (a.left - g.left) - 20}px`;
layer.style.top = `${box.y - (a.top - g.top)}px`;
const btn = el("button", {
className: "ss-errtip__btn",
text: "⚠",
attrs: { type: "button", "aria-label": w.까닭 },
});
const btn = createIconButton({ glyph: "⚠", title: w.까닭, variant: "outline", size: 18 });
btn.classList.add("ss-errtip__btn");
btn.removeAttribute("title"); // 까닭은 옆 풍선이 보임
const balloon = el("div", { className: "ss-errtip__balloon", text: w.까닭 });
btn.addEventListener("mousedown", (ev) => ev.stopPropagation());
btn.addEventListener("click", () => (menu ? closeMenu() : openMenu(w, r, c)));
+3 -5
View File
@@ -6,7 +6,7 @@
* 빨간 세모는 C 격자 · 우클릭 [메모 …] 는 E 메뉴 몫(브레인 배정).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createButton, el } from "@ui/ui_template_elements";
import { watchClose } from "./spreadsheet_actions";
import { parseClipboard } from "./spreadsheet_clipboard";
import { attachComments, getComment, setComment } from "./spreadsheet_comments";
@@ -138,10 +138,8 @@ export function attachExtras(
: undefined;
picker.replaceChildren(
...(Object.keys(TEXT.modes) as PasteSpecialMode[]).map((mode) => {
const b = el("button", {
text: TEXT.modes[mode],
attrs: { type: "button", "data-mode": mode },
});
const b = createButton({ label: TEXT.modes[mode], variant: "ghost", size: 22 });
b.dataset.mode = mode;
b.addEventListener("click", () => {
picker.setAttribute("hidden", "");
// 늦게 — 크기 예산
@@ -4,7 +4,8 @@
* Enter = 다음 · Esc = 닫고 격자로. 바꾸기 = 활성 칸이 맞으면 바꾸고 다음으로(엑셀).
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import { createButton, createIconButton, el, setUiSize } from "@ui/ui_template_elements";
import { createFloatPanel } from "@ui/ui_template_float_panel";
import { findAll, findNext, replaceAll, replaceOne, type FindOptions } from "./spreadsheet_find";
import { selectCell } from "./spreadsheet_selection";
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
@@ -29,12 +30,14 @@ export interface FindPanelHandle extends PartHandle {
}
export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindPanelHandle {
const query = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.find } });
const repl = el("input", { className: "ss-find__input", attrs: { placeholder: TEXT.replace } });
const field = (placeholder: string): HTMLInputElement =>
setUiSize(el("input", { className: "ui-input ss-find__input", attrs: { placeholder } }), 24);
const query = field(TEXT.find);
const repl = field(TEXT.replace);
const status = el("span", { className: "ss-find__status" });
const button = (text: string, onClick: () => void): HTMLButtonElement => {
const b = el("button", { className: "ss-find__btn", text, attrs: { type: "button" } });
b.addEventListener("click", onClick);
const b = createButton({ label: text, variant: "ghost", size: 22, onClick });
b.classList.add("ss-find__btn");
return b;
};
const check = (text: string): HTMLInputElement => {
@@ -42,8 +45,8 @@ export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindP
options.append(el("label", { className: "ss-find__opt", children: [box, text] }));
return box;
};
const closeBtn = button("×", close);
closeBtn.title = TEXT.close;
const closeBtn = createIconButton({ glyph: "×", title: TEXT.close, size: 22, onClick: close });
closeBtn.classList.add("ss-find__btn");
const options = el("div", { className: "ss-find__row" });
const caseBox = check(TEXT.case);
const wholeBox = check(TEXT.whole);
@@ -52,19 +55,19 @@ export function mountFindPanel(ctx: SpreadsheetContext, back: () => void): FindP
className: "ss-find__row",
children: [repl, button(TEXT.one, one), button(TEXT.all, all)],
});
const root = el("div", {
className: "ss-find",
attrs: { hidden: "" },
children: [
el("div", {
className: "ss-find__row",
children: [query, button(TEXT.next, next), closeBtn],
}),
replaceRow,
options,
status,
],
});
// 떠 있는 판 부품(머리 없음 · 553-6) — 자리는 `.ss-find`
const { root, body } = createFloatPanel({ popover: true });
root.classList.add("ss-find");
root.setAttribute("hidden", "");
body.append(
el("div", {
className: "ss-find__row",
children: [query, button(TEXT.next, next), closeBtn],
}),
replaceRow,
options,
status,
);
ctx.root.append(root);
const opts = (): FindOptions => ({
@@ -1,50 +1,9 @@
/* =============================================================================
* spreadsheet_format_dialog.css (2단계 · 주인 sub_laptop_1)
* 셀 서식 대화상자 — 뿌리 배경(`.ui-confirm`)은 공용 확인 모달과 같은 것을 그대로 씀.
* 셀 서식 대화상자 — 창 · 탭 · 칸 = 부품 모달(40rem) · 탭(32) · 입력 · 고르기(28) · 여기는 안 배치만(553-6).
* 색 · 간격은 테마 토큰만(`ui_template_theme.css`).
* ========================================================================== */
.ss-fmtdlg__panel {
display: flex;
flex-direction: column;
width: min(560px, 92vw);
max-height: 84vh;
padding: var(--spacing-16);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-fmtdlg__title {
margin: 0 0 var(--spacing-8);
font-size: 1rem;
color: var(--color-text-body);
}
.ss-fmtdlg__tabs {
display: flex;
flex-wrap: wrap;
gap: 2px;
border-bottom: 1px solid var(--color-border);
}
.ss-fmtdlg__tabbtn {
padding: 6px 12px;
border: 1px solid var(--color-border);
border-bottom: 0;
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
background: var(--color-paper);
color: var(--color-text-secondary);
font-size: 0.82rem;
cursor: pointer;
}
.ss-fmtdlg__tabbtn.is-active {
background: var(--color-canvas);
color: var(--color-text-body);
font-weight: 600;
}
.ss-fmtdlg__body {
flex: 1;
min-height: 300px;
@@ -68,21 +27,7 @@
width: 140px;
flex-shrink: 0;
color: var(--color-text-secondary);
font-size: 0.82rem;
}
.ss-fmtdlg__row select,
.ss-fmtdlg__row input[type="text"],
.ss-fmtdlg__row input:not([type]),
.ss-fmtdlg__row input[type="number"],
.ss-fmtdlg__code {
height: 28px;
padding: 0 6px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: var(--color-text-body);
font-size: 0.82rem;
font-size: var(--text-body-sm);
}
.ss-fmtdlg__code {
@@ -135,7 +80,7 @@
.ss-fmtdlg__note {
margin: 0 0 var(--spacing-8);
color: var(--color-text-muted);
font-size: 0.8rem;
font-size: var(--text-caption);
}
.ss-fmtdlg__actions {
@@ -12,7 +12,9 @@
* 우클릭 메뉴 [셀 서식] 도 같은 함수를 부르면 됨(`spreadsheet_menu.ts`).
* ========================================================================== */
import { createButton, el } from "@ui/ui_template_elements";
import { createButton, el, setUiSize } from "@ui/ui_template_elements";
import { createTabs } from "@ui/ui_template_tabs";
import { openModal } from "@ui/ui_template_modal";
import { toFrac } from "@ui/sheet/ui_template_sheet_frac";
import { colName, rangeToA1, toA1 } from "./spreadsheet_address";
import {
@@ -39,6 +41,7 @@ const TEXT = {
tabProtect: "보호",
ok: "확인",
cancel: "취소",
close: "닫기",
category: "범주",
decimals: "소수 자릿수",
thousands: "천 단위 구분 기호 사용",
@@ -105,17 +108,9 @@ export function openFormatDialog(ctx: SpreadsheetContext): void {
const spansMany = range.r0 !== range.r1 || range.c0 !== range.c1;
let mergeWanted = (ctx.sheet().병합 ?? []).includes(rangeToA1(range));
const overlay = el("div", { className: "ui-confirm ss-fmtdlg" });
const panel = el("div", { className: "ss-fmtdlg__panel" });
const tabsBar = el("div", { className: "ss-fmtdlg__tabs" });
const body = el("div", { className: "ss-fmtdlg__body" });
overlay.append(panel);
panel.append(el("h2", { className: "ss-fmtdlg__title", text: TEXT.title }), tabsBar, body);
const close = (): void => overlay.remove();
overlay.addEventListener("click", (ev) => {
if (ev.target === overlay) close();
});
let close = (): void => {};
// ── 표시 형식 ────────────────────────────────────────────────────────
let category: Category = guessCategory(draft.형식);
@@ -464,22 +459,25 @@ export function openFormatDialog(ctx: SpreadsheetContext): void {
[TEXT.tabProtect, protectTab],
];
function showTab(index: number): void {
body.replaceChildren(tabs[index][1]());
tabsBar.querySelectorAll(".ss-fmtdlg__tabbtn").forEach((btn, i) => {
btn.classList.toggle("is-active", i === index);
});
}
tabs.forEach(([label], i) => {
const btn = el("button", {
className: "ss-fmtdlg__tabbtn",
text: label,
attrs: { type: "button" },
});
btn.addEventListener("click", () => showTab(i));
tabsBar.append(btn);
const tabBar = createTabs({
tabs: tabs.map(([label], i) => ({ key: String(i), label })),
value: "0",
size: 32,
onChange: (key) => showTab(Number(key)),
});
tabsBar.append(tabBar.root);
function showTab(index: number): void {
const tab = tabs[index][1]();
// 칸 = 부품 입력 · 고르기 28(553-6)
const fields = "select, input:not([type]), input[type=text], input[type=number]";
for (const f of tab.querySelectorAll<HTMLElement>(fields)) {
f.classList.add(f.tagName === "SELECT" ? "ui-select" : "ui-input");
setUiSize(f, 28);
}
body.replaceChildren(tab);
tabBar.select(String(index));
}
function apply(): void {
ctx.dispatch({ 종류: "서식", 시트: ctx.selection.시트, 범위: ctx.selection.범위, 바꿀: draft });
@@ -497,13 +495,21 @@ export function openFormatDialog(ctx: SpreadsheetContext): void {
const actions = el("div", {
className: "ss-fmtdlg__actions",
children: [
createButton({ label: TEXT.cancel, variant: "ghost", onClick: close }),
createButton({ label: TEXT.cancel, variant: "ghost", onClick: () => close() }),
createButton({ label: TEXT.ok, variant: "filled", onClick: apply }),
],
});
panel.append(actions);
showTab(0);
document.body.append(overlay);
dialogKeys(overlay, close, apply);
openModal({
title: TEXT.title,
closeLabel: TEXT.close,
width: "40rem",
dialogClass: "ss-fmtdlg",
mount: (box, shut) => {
close = shut;
box.append(tabsBar, body, actions);
dialogKeys(box.parentElement ?? box, shut, apply);
},
});
}
+2 -11
View File
@@ -120,20 +120,11 @@
width: 320px;
padding: var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
.ss-links__search {
padding: 4px 6px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: var(--color-text-body);
font: inherit;
}
.ss-links__list {
display: flex;
flex-direction: column;
@@ -155,7 +146,7 @@
}
.ss-links__row:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}
.ss-links__note {
+2 -2
View File
@@ -585,8 +585,8 @@ export function mountLinks(
function openPick(anchor: HTMLElement): void {
closePick();
const input = el("input", {
className: "ss-links__search",
attrs: { type: "search", placeholder: TEXT.search },
className: "ui-input ss-links__search",
attrs: { type: "search", placeholder: TEXT.search, "data-size": "28" },
}) as HTMLInputElement;
const list = el("div", { className: "ss-links__list" });
const note = (text: string) => el("div", { className: "ss-links__note", text });
+4 -4
View File
@@ -59,15 +59,15 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle {
/** 메모 글 입력 — 작은 오버레이(native prompt 안 씀, 자동화 창이 native 창을 자동 취소하는
* 문제는 `showConfirmDialog` 머리 주석과 같은 까닭). 저장을 누르면 `onSave`. */
function openCommentEditor(initial: string, onSave: (text: string) => void): void {
const overlay = el("div", { className: "ss-comment-editor" });
const panel = el("div", { className: "ss-comment-editor__panel" });
const overlay = el("div", { className: "ui-confirm ss-comment-editor" });
const panel = el("div", { className: "ui-confirm__panel" });
const textarea = el("textarea", {
className: "ss-comment-editor__input",
className: "ui-input ss-comment-editor__input",
}) as HTMLTextAreaElement;
textarea.value = initial;
const done = (): void => overlay.remove();
const actions = el("div", {
className: "ss-comment-editor__actions",
className: "ui-confirm__actions",
children: [
createButton({ label: "취소", variant: "ghost", onClick: done }),
createButton({
@@ -100,12 +100,17 @@ export function openSortDialog(ctx: SpreadsheetContext): () => void {
function drawKeys() {
list.replaceChildren(
...keys.map((k, i) => {
const col = el("select", { attrs: { "aria-label": t("Column") } });
const col = el("select", {
className: "ui-select",
attrs: { "aria-label": t("Column"), "data-size": "28" },
});
for (let c = rg.c0; c <= rg.c1; c++)
col.append(el("option", { text: columnLabel(c), attrs: { value: String(c) } }));
col.value = String(k.열);
col.addEventListener("change", () => (k.열 = Number(col.value)));
const dir = el("select", {
className: "ui-select",
attrs: { "data-size": "28" },
children: [
el("option", { text: t("Asc"), attrs: { value: "asc" } }),
el("option", { text: t("Desc"), attrs: { value: "desc" } }),
@@ -7,7 +7,7 @@
display: flex;
align-items: center;
justify-content: center;
background: rgb(0 0 0 / 18%);
background: var(--color-scrim);
}
.ss-sort__box,
@@ -48,11 +48,6 @@
.ss-sort__row select {
flex: 1;
padding: var(--spacing-4);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: inherit;
}
.ss-sort__row option {
@@ -92,8 +87,8 @@
}
.ss-filter__btn.is-on {
color: var(--color-royal-amethyst);
border-color: var(--color-royal-amethyst);
color: var(--color-accent);
border-color: var(--color-accent);
}
.ss-filter__panel {
@@ -130,5 +125,5 @@
}
.ss-filter__link:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}
@@ -14,7 +14,7 @@
padding: var(--spacing-8);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 8px;
border-radius: var(--radius-cards);
min-width: 280px;
}
@@ -89,7 +89,7 @@
}
.ss-validation__dropdown-item:hover {
background: var(--color-mist-violet);
background: var(--color-hover);
}
.ss-statusbar {
+5 -34
View File
@@ -215,42 +215,13 @@
/* ── 메모 편집 창(칸 우클릭 [메모 삽입 · 편집]) ──────────────────────────────── */
.ss-comment-editor {
position: fixed;
inset: 0;
z-index: var(--z-modal);
display: flex;
align-items: center;
justify-content: center;
background: var(--color-scrim);
}
.ss-comment-editor__panel {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
width: 280px;
padding: var(--spacing-16);
border-radius: var(--radius-cards);
background: var(--color-surface-raised);
box-shadow: var(--shadow-lg);
}
/* 창 = 공용 확인창(.ui-confirm) · 칸 = 부품 입력(여러 줄) — 여기는 칸 크기만(553-6) */
.ss-comment-editor__input {
display: block;
box-sizing: border-box;
width: 100%;
min-height: 80px;
padding: var(--spacing-8);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-canvas);
color: var(--color-text-body);
font: inherit;
resize: vertical;
}
.ss-comment-editor__actions {
display: flex;
justify-content: flex-end;
gap: var(--spacing-8);
margin-bottom: var(--spacing-16);
}
/* ⋯ 칸 메뉴(기기대응 3-5) — 터치 모드에서만 */
+3 -3
View File
@@ -81,7 +81,7 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template
out.fixedX = tabNode("a").kids.length; // 고정 탭 = 글만(지우기 없음)
// ③ 지우기 — 물음 뒤 · 지금 탭이면 다음 탭을 실음
await tabs.select("c");
const xc = tabNode("c").kids.find((k) => k.className === "cad-tabs__x");
const xc = tabNode("c").kids.find((k) => k.className.split(" ").includes("cad-tabs__x"));
await xc.on.click({ stopPropagation() {} });
out.afterRemove = { ids: tabs.tabs().map((t) => t.id), active: tabs.active().id, confirms: confirms.length };
// ④ [+] — create 없으면 아무것도 안 함 · 닫기 기능(B07) = 미저장 탭만 물음
@@ -97,7 +97,7 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template
await closer.open({ id: "d1", name: "종단" });
const before = confirms.length;
const d2 = closer.element.kids.find((k) => k.dataset.tabId === "d2");
await d2.kids.find((k) => k.className === "cad-tabs__x").on.click({ stopPropagation() {} });
await d2.kids.find((k) => k.className.split(" ").includes("cad-tabs__x")).on.click({ stopPropagation() {} });
const last = closer.element.kids.find((k) => k.dataset.tabId === "d1").kids.length; // 마지막 탭 = 닫기 없음
out.b07 = { log: b07, asked: confirms.length - before, ids: closer.tabs().map((t) => t.id), last };
@@ -137,7 +137,7 @@ const tabsMod = load("ui_template/cad_host/cad_host_tabs.ts", { "@ui/ui_template
});
await new Promise((r) => setTimeout(r, 5));
const bar = host.kids[0].kids.find((k) => k.className === "cad-tabs");
const plus = bar.kids.find((k) => k.className === "cad-tabs__add");
const plus = bar.kids.find((k) => k.className.split(" ").includes("cad-tabs__add"));
await plus.on.click();
await new Promise((r) => setTimeout(r, 5));
inCad = { ...inCad, entities: [...inCad.entities, { type: "Circle" }] }; // 새 장에 하나 그림
+1 -1
View File
@@ -86,7 +86,7 @@
}
.cad-frame-edit__label {
font-size: 0.78rem;
font-size: var(--text-caption);
line-height: 1.4;
color: var(--color-text);
}
+2 -27
View File
@@ -1,5 +1,5 @@
/* ui_template/cad_host/cad_host_tabs — CAD 를 감싸는 공용 탭 줄(82-1 · M02 구조물 도면 · B07).
문서 탭 = 꼴 그대로 · 공통 값 · 크기 단만(탭 24 · × 18 · + 20 · 553-6). */
문서 탭 = 꼴 그대로 · 공통 값 · 크기 단만(탭 24 · × 18 · + 22 = 아이콘 단추 부품 · 553-6). */
.cad-tabs {
display: flex;
@@ -69,35 +69,10 @@
font: inherit;
}
.cad-tabs__x,
.cad-tabs__add {
display: inline-grid;
flex: none;
place-items: center;
width: 18px;
height: 18px;
padding: 0;
border: none;
border-radius: 4px;
background: transparent;
color: var(--color-text-secondary);
font-size: 14px;
line-height: 1;
cursor: pointer;
}
.cad-tabs__x:hover,
.cad-tabs__add:hover {
background: var(--color-hover);
color: var(--color-text);
}
/* × · + = 아이콘 단추 부품(18 · 22) — 여기는 자리만(553-6) */
.cad-tabs__add {
align-self: center;
width: 20px;
height: 20px;
margin-left: 2px;
font-size: 16px;
}
.cad-tabs[data-busy="true"] .cad-tabs__tab {
+4 -2
View File
@@ -233,7 +233,8 @@ export function createCadTabs<D>(options: CadTabsOptions<D>): CadTabs<D> {
if ((features.remove || (features.close && list.length > 1)) && !tab.fixed) {
const x = document.createElement("button");
x.type = "button";
x.className = "cad-tabs__x";
x.className = "ui-icon-btn ui-icon-btn--ghost cad-tabs__x"; // 아이콘 단추 부품 꼴(553-6)
x.dataset.size = "18";
x.textContent = "×";
x.title = x.ariaLabel = features.remove ? text.remove : text.close;
x.addEventListener("click", (event) => {
@@ -272,7 +273,8 @@ export function createCadTabs<D>(options: CadTabsOptions<D>): CadTabs<D> {
if (features.add) {
const plus = document.createElement("button");
plus.type = "button";
plus.className = "cad-tabs__add";
plus.className = "ui-icon-btn ui-icon-btn--ghost cad-tabs__add";
plus.dataset.size = "22";
plus.textContent = "+";
plus.title = plus.ariaLabel = text.add;
plus.disabled = busy;
+2 -2
View File
@@ -18,13 +18,13 @@
}
.ui-sheet__toolbar .ui-btn.is-on {
background: var(--color-mist-violet);
background: var(--color-accent-soft);
}
.ui-sheet__hint {
margin: 0;
color: var(--color-text-secondary); /* muted 는 작은 글에 대비 3:1 — 모자람 */
font-size: 0.8rem;
font-size: var(--text-caption);
}
.ui-sheet__scroll {