Merge remote-tracking branch 'origin/dev' into sub_laptop_6
# Conflicts: # A00_Common/spreadsheet/spreadsheet_grid.ts # A00_Common/spreadsheet/spreadsheet_links.ts
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
|
||||
.ui-sheet__hint {
|
||||
margin: 0;
|
||||
color: var(--color-text-muted);
|
||||
color: var(--color-text-secondary); /* muted 는 작은 글에 대비 3:1 — 모자람 */
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
deleteColumn,
|
||||
deleteRow,
|
||||
isBlankPick,
|
||||
isBoundColumn,
|
||||
isCalcColumn,
|
||||
KEY_PRICE,
|
||||
KEY_UNIT,
|
||||
navRows,
|
||||
@@ -168,7 +170,8 @@ export function createSheet(host: HTMLElement, input: SheetDoc, opts: SheetOptio
|
||||
const col = state.sel ? colOf(state.sel.c) : undefined;
|
||||
if (opts.mode === "master") syncLevels(!!col);
|
||||
colDelete.disabled = !col || state.doc.열.length <= 1;
|
||||
colHand.disabled = !col;
|
||||
// 바인딩 · 계산 열은 손 입력이 안 먹음(프로젝트에서 잠김) — 켜지 못하게 · 이미 켜진 것은 끌 수 있게
|
||||
colHand.disabled = !col || (!col.손 && (isBoundColumn(col) || isCalcColumn(col)));
|
||||
colHand.classList.toggle("is-on", !!col?.손);
|
||||
};
|
||||
|
||||
|
||||
@@ -72,6 +72,8 @@ export interface InputFieldOptions {
|
||||
min?: number;
|
||||
max?: number;
|
||||
onInput?: (value: string) => void;
|
||||
/** 좁은 자리(표 칸·좌측 패널)용 작은 크기 — 작은 고르기칸(compact)과 높이가 같음 */
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export interface InputFieldHandle {
|
||||
@@ -90,7 +92,7 @@ export function createInputField(opts: InputFieldOptions): InputFieldHandle {
|
||||
}
|
||||
|
||||
const input = el("input", {
|
||||
className: "ui-input",
|
||||
className: opts.compact ? "ui-input ui-input--sm" : "ui-input",
|
||||
attrs: {
|
||||
type: opts.type ?? "text",
|
||||
...(opts.placeholder ? { placeholder: opts.placeholder } : {}),
|
||||
@@ -318,17 +320,26 @@ export function showConfirmDialog(message: string, confirmLabel = "확인"): Pro
|
||||
const panel = el("div", { className: "ui-confirm__panel" });
|
||||
const text = el("p", { className: "ui-confirm__message", text: message });
|
||||
const actions = el("div", { className: "ui-confirm__actions" });
|
||||
const back = document.activeElement as HTMLElement | null; // 닫힐 때 초점을 돌려놓을 자리
|
||||
const done = (result: boolean): void => {
|
||||
overlay.remove();
|
||||
if (back?.isConnected && back !== document.body) back.focus({ preventScroll: true });
|
||||
resolve(result);
|
||||
};
|
||||
const cancel = createButton({ label: "취소", variant: "ghost", onClick: () => done(false) });
|
||||
actions.append(
|
||||
createButton({ label: "취소", variant: "ghost", onClick: () => done(false) }),
|
||||
cancel,
|
||||
createButton({ label: confirmLabel, variant: "danger", onClick: () => done(true) }),
|
||||
);
|
||||
overlay.addEventListener("keydown", (ev) => {
|
||||
if (ev.key !== "Escape") return;
|
||||
ev.stopPropagation(); // 뒤 창 · 페이지 Esc 까지 닫지 않음
|
||||
done(false);
|
||||
});
|
||||
panel.append(text, actions);
|
||||
overlay.append(panel);
|
||||
document.body.append(overlay);
|
||||
cancel.focus(); // 창에 초점 — 뒤 칸에 글이 들어가지 않고 Esc 가 먹음
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,10 @@ const BASE_CSS = `
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
|
||||
}
|
||||
.ui-input::placeholder { color: var(--color-text-muted); }
|
||||
/* 입력칸 · 고르기칸 높이는 크기마다 한 값 — 같은 줄에 놓여도 위아래 · 글자 줄이 맞음(date 칸 포함) */
|
||||
.ui-input:not(textarea), .ui-select { height: var(--control-height); line-height: 20px; }
|
||||
.ui-input--sm:not(textarea), .ui-select--sm { height: var(--control-height-sm); line-height: 20px; }
|
||||
.ui-input--sm { padding: 4px 8px; }
|
||||
.ui-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-focus-ring);
|
||||
|
||||
@@ -23,6 +23,10 @@ export const ui_locales_m2 = {
|
||||
M02_Close: ["닫기", "Close"],
|
||||
M02_NameNeeded: ["이름을 적을 것", "Enter a name"],
|
||||
M02_DeleteConfirm: ["「{value}」 양식을 지울까?", "Delete template “{value}”?"],
|
||||
M02_InUse: [
|
||||
"쓰는 양식이라 못 지움 「{value}」 — {use}",
|
||||
"“{value}” is in use and cannot be deleted — {use}",
|
||||
],
|
||||
M02_Deleted: ["지웠음", "Deleted"],
|
||||
M02_Created: ["만들었음", "Created"],
|
||||
M02_Saved: ["저장했음", "Saved"],
|
||||
|
||||
@@ -13,11 +13,16 @@ export interface ModalOptions {
|
||||
mount: (body: HTMLElement, close: () => void) => void;
|
||||
dialogClass?: string;
|
||||
backdropClass?: string;
|
||||
/** 닫기 단추 · Esc · 바깥 누르기 전에 물음 — false 면 안 닫음(몸의 `close` 는 묻지 않고 닫음) */
|
||||
beforeClose?: () => boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
export function openModal(opts: ModalOptions): { close: () => void } {
|
||||
const body = el("div", { className: "ui-modal__body" });
|
||||
const close = (): void => backdrop.remove();
|
||||
const ask = async (): Promise<void> => {
|
||||
if (!opts.beforeClose || (await opts.beforeClose())) close();
|
||||
};
|
||||
const dialog = el("div", {
|
||||
className: `ui-modal ${opts.dialogClass ?? ""}`.trim(),
|
||||
attrs: { role: "dialog", "aria-label": opts.title },
|
||||
@@ -26,7 +31,7 @@ export function openModal(opts: ModalOptions): { close: () => void } {
|
||||
className: "ui-modal__top",
|
||||
children: [
|
||||
el("h3", { text: opts.title }),
|
||||
createButton({ label: opts.closeLabel, variant: "ghost", onClick: close }),
|
||||
createButton({ label: opts.closeLabel, variant: "ghost", onClick: () => void ask() }),
|
||||
],
|
||||
}),
|
||||
body,
|
||||
@@ -36,11 +41,12 @@ export function openModal(opts: ModalOptions): { close: () => void } {
|
||||
className: `ui-modal__backdrop ${opts.backdropClass ?? ""}`.trim(),
|
||||
children: [dialog],
|
||||
});
|
||||
backdrop.addEventListener("click", (ev) => ev.target === backdrop && close());
|
||||
backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && close());
|
||||
backdrop.addEventListener("click", (ev) => ev.target === backdrop && void ask());
|
||||
backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && void ask());
|
||||
opts.mount(body, close);
|
||||
document.body.append(backdrop);
|
||||
dialog.tabIndex = -1;
|
||||
dialog.focus();
|
||||
// 몸의 첫 입력 칸에 커서 — mount 안의 focus() 는 붙기 전이라 안 먹음
|
||||
(body.querySelector<HTMLElement>("input, textarea, select") ?? dialog).focus();
|
||||
return { close };
|
||||
}
|
||||
|
||||
@@ -154,6 +154,10 @@
|
||||
--radius-icons: var(--radius-lg);
|
||||
--radius-large: var(--radius-3xl);
|
||||
|
||||
/* 입력칸 · 고르기칸 높이 — 크기마다 한 값(같은 줄이면 위아래가 맞음) */
|
||||
--control-height: 42px;
|
||||
--control-height-sm: 30px;
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* 6. 그림자 (Shadows) — 블루틴트, 서브틀 (최대 32px blur / 6% opacity)
|
||||
* ------------------------------------------------------------------------ */
|
||||
|
||||
Reference in New Issue
Block a user