Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_2
This commit is contained in:
@@ -1,13 +1,5 @@
|
||||
/* 항목 고르기 모달 — 왼쪽 나무 · 오른쪽 폴더 모양 · 아래 단추 */
|
||||
/* 공용 모달(.ui-modal)의 폭 · 높이보다 앞서게 — 불러오는 차례에 따라 뒤집히지 않도록 두 클래스 */
|
||||
.ui-modal.ui-item-picker {
|
||||
width: min(1320px, 96vw);
|
||||
--modal-max: 94vh;
|
||||
}
|
||||
|
||||
.ui-item-picker .ui-modal__body {
|
||||
height: min(80vh, 800px);
|
||||
}
|
||||
/* 항목 고르기 모달 — 왼쪽 나무 · 오른쪽 폴더 모양 · 머리 단추(147-2) */
|
||||
/* 크기 = 공용 큰 창(곁값 wide · 96vw × 94vh · 147-2) */
|
||||
|
||||
.ui-item-picker__cols {
|
||||
display: grid;
|
||||
@@ -51,6 +43,7 @@
|
||||
|
||||
.ui-item-picker__cols[hidden],
|
||||
.ui-item-picker__actions[hidden],
|
||||
.ui-item-picker .ui-modal__actions[hidden],
|
||||
.ui-item-picker__edit-host[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,6 @@ const NEW_TEXT = {
|
||||
name: "이름",
|
||||
unit: "단위",
|
||||
ok: "만들기",
|
||||
cancel: "취소",
|
||||
empty: "{label} 을(를) 넣을 것",
|
||||
made: "새 항목을 만들어 지정했음 — 구조물 [저장]을 눌러야 이 구조물 복사본이 남음",
|
||||
failed: "새 항목을 만들지 못함 — {value}",
|
||||
@@ -289,7 +288,6 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
|
||||
{ key: "unit", label: NEW_TEXT.unit, placeholder: "m · ㎥ · 개소", required: true },
|
||||
],
|
||||
okLabel: NEW_TEXT.ok,
|
||||
cancelLabel: NEW_TEXT.cancel,
|
||||
closeLabel: t("M02_Close"),
|
||||
emptyText: NEW_TEXT.empty,
|
||||
});
|
||||
@@ -308,17 +306,16 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
|
||||
}
|
||||
}
|
||||
|
||||
// 단추 넷 = 머리 [닫기] 왼쪽(147-2) · 안내 글은 아래 남김
|
||||
const headButtons = el("div", {
|
||||
className: "ui-modal__actions",
|
||||
children: [copyButton, manageButton, clearButton, applyButton],
|
||||
});
|
||||
const actions = el("div", {
|
||||
className: "ui-item-picker__actions",
|
||||
children: [
|
||||
...(opts.projectId
|
||||
? [el("span", { className: "ui-item-picker__saveline", text: NEW_TEXT.saveLine })]
|
||||
: []),
|
||||
copyButton,
|
||||
manageButton,
|
||||
clearButton,
|
||||
applyButton,
|
||||
],
|
||||
children: opts.projectId
|
||||
? [el("span", { className: "ui-item-picker__saveline", text: NEW_TEXT.saveLine })]
|
||||
: [],
|
||||
});
|
||||
|
||||
/** 카드 [수정] — 모달 안에서 편집 화면으로 바뀜(나무 · 카드 · 단추는 숨김) */
|
||||
@@ -328,6 +325,7 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
|
||||
editHost.replaceChildren(view.root);
|
||||
cols.hidden = true;
|
||||
actions.hidden = true;
|
||||
headButtons.hidden = true;
|
||||
editHost.hidden = false;
|
||||
view.start();
|
||||
}
|
||||
@@ -338,6 +336,7 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
|
||||
editHost.hidden = true;
|
||||
cols.hidden = false;
|
||||
actions.hidden = false;
|
||||
headButtons.hidden = false;
|
||||
if (saved) detailView.markEdited();
|
||||
detailView.reload();
|
||||
}
|
||||
@@ -346,6 +345,8 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
|
||||
title: t("M02_PickerTitle"),
|
||||
closeLabel: t("M02_Close"),
|
||||
dialogClass: "ui-item-picker",
|
||||
wide: true,
|
||||
actions: [headButtons],
|
||||
// 닫기 단추 · Esc · 바깥 누르기 = 취소 — 편집 중 저장 안 한 고침이 있으면 물음
|
||||
beforeClose: async () => {
|
||||
if (editView && !(await editView.leave())) return false;
|
||||
@@ -354,7 +355,7 @@ export function openItemPicker(opts: ItemPickerOptions): Promise<ItemPickerResul
|
||||
return true;
|
||||
},
|
||||
mount: (body) => {
|
||||
body.append(cols, editHost, actions);
|
||||
body.append(cols, editHost, ...(opts.projectId ? [actions] : []));
|
||||
paintDetail();
|
||||
void load(null);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
/* =============================================================================
|
||||
* ui_template_item_picker_ask.ts
|
||||
* 고르기 모달에서 글을 물을 때 쓰는 작은 입력 창(계획서 26-3b) — [개인에 저장](경로 · 이름) ·
|
||||
* 나무 오른쪽 클릭 「새 항목」(이름 · 단위). 확인 = 넣은 글들 · 취소 · 닫기 = null.
|
||||
* 나무 오른쪽 클릭 「새 항목」(이름 · 단위). 확인 = 넣은 글들 · 닫기 = null.
|
||||
* [확인] = 머리 [닫기] 왼쪽 · [취소] 없음(머리 [닫기] = 취소 · 147-2).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, createInputField, el } from "./ui_template_elements";
|
||||
@@ -22,7 +23,6 @@ export interface AskOptions {
|
||||
note?: string;
|
||||
fields: AskField[];
|
||||
okLabel: string;
|
||||
cancelLabel: string;
|
||||
closeLabel: string;
|
||||
/** 빈 값 알림 — `{label}` 자리에 칸 이름 */
|
||||
emptyText: string;
|
||||
@@ -45,7 +45,7 @@ export function askFields(opts: AskOptions): Promise<Record<string, string> | nu
|
||||
finish(null);
|
||||
return true;
|
||||
},
|
||||
mount: (body, close) => {
|
||||
mount: (body, close, head) => {
|
||||
const inputs = opts.fields.map((field) => ({
|
||||
field,
|
||||
input: createInputField({
|
||||
@@ -76,21 +76,8 @@ export function askFields(opts: AskOptions): Promise<Record<string, string> | nu
|
||||
body.append(
|
||||
...(opts.note ? [el("p", { className: "ui-item-picker__where", text: opts.note })] : []),
|
||||
...inputs.map(({ input }) => input.root),
|
||||
el("div", {
|
||||
className: "ui-item-picker__actions",
|
||||
children: [
|
||||
createButton({
|
||||
label: opts.cancelLabel,
|
||||
variant: "ghost",
|
||||
onClick: () => {
|
||||
finish(null);
|
||||
close();
|
||||
},
|
||||
}),
|
||||
createButton({ label: opts.okLabel, variant: "filled", onClick: submit }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
head.append(createButton({ label: opts.okLabel, variant: "filled", onClick: submit }));
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -98,7 +98,6 @@ const TEXT = {
|
||||
saved: "개인 층에 저장했음",
|
||||
saveFailed: "개인에 저장하지 못함 — {value}",
|
||||
ok: "확인",
|
||||
cancel: "취소",
|
||||
close: "닫기",
|
||||
empty: "{label} 을(를) 넣을 것",
|
||||
importFailed: "이 구조물로 가져오지 못함 — {value}",
|
||||
@@ -267,7 +266,6 @@ export function createItemDetail(opts: ItemDetailOptions): ItemDetail {
|
||||
{ key: "name", label: TEXT.nameLabel, value: itemName(row), required: true },
|
||||
],
|
||||
okLabel: TEXT.ok,
|
||||
cancelLabel: TEXT.cancel,
|
||||
closeLabel: TEXT.close,
|
||||
emptyText: TEXT.empty,
|
||||
});
|
||||
|
||||
@@ -33,6 +33,32 @@
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 머리 단추 자리(곁값 actions) — 제목이 남는 폭을 먹고 단추는 [닫기] 바로 왼쪽 */
|
||||
.ui-modal__top > h3 {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.ui-modal__actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
/* 큰 창(곁값 wide) — 화면 거의 꽉 · 몸 넘침만 스크롤. 화면별 클래스보다 앞서게 두 클래스 */
|
||||
.ui-modal.ui-modal--wide {
|
||||
width: 96vw;
|
||||
height: 94vh;
|
||||
--modal-max: 94vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ui-modal--wide > .ui-modal__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ui-modal__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
* ui_template_modal.ts
|
||||
* 공용 모달 겉틀 — 배경 · 제목줄 · 닫기 · Esc · 포커스. 몸은 `mount` 가 채움.
|
||||
* M01 · M02 가 함께 씀 — `dialogClass` · `backdropClass` 로 화면별 겉모양을 덧붙임.
|
||||
* 곁값 — `actions`(머리 [닫기] 바로 왼쪽 단추 · mount 셋째 인자 자리에 넣어도 됨) · `wide`(큰 창 96vw × 94vh).
|
||||
* 안 넘긴 창은 지금 꼴 그대로.
|
||||
* ========================================================================== */
|
||||
|
||||
import "./ui_template_modal.css";
|
||||
@@ -10,8 +12,13 @@ import { createButton, el } from "./ui_template_elements";
|
||||
export interface ModalOptions {
|
||||
title: string;
|
||||
closeLabel: string;
|
||||
mount: (body: HTMLElement, close: () => void) => void;
|
||||
/** `actions` = 머리 단추 자리 — 몸에서 만든 단추([확인] 등)를 넣음 */
|
||||
mount: (body: HTMLElement, close: () => void, actions: HTMLElement) => void;
|
||||
dialogClass?: string;
|
||||
/** 머리 [닫기] 바로 왼쪽에 둘 단추(넘긴 창만) */
|
||||
actions?: HTMLElement[];
|
||||
/** 큰 창 — 96vw × 94vh · 몸 넘침만 스크롤 */
|
||||
wide?: boolean;
|
||||
backdropClass?: string;
|
||||
/** 닫기 단추 · Esc · 바깥 누르기 전에 물음 — false 면 안 닫음(몸의 `close` 는 묻지 않고 닫음) */
|
||||
beforeClose?: () => boolean | Promise<boolean>;
|
||||
@@ -19,23 +26,24 @@ export interface ModalOptions {
|
||||
|
||||
export function openModal(opts: ModalOptions): { close: () => void } {
|
||||
const body = el("div", { className: "ui-modal__body" });
|
||||
const actions = el("div", { className: "ui-modal__actions", children: opts.actions ?? [] });
|
||||
const closeButton = createButton({
|
||||
label: opts.closeLabel,
|
||||
variant: "ghost",
|
||||
onClick: () => void ask(),
|
||||
});
|
||||
const top = el("div", {
|
||||
className: "ui-modal__top",
|
||||
children: [el("h3", { text: opts.title }), closeButton],
|
||||
});
|
||||
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(),
|
||||
className: `ui-modal ${opts.wide ? "ui-modal--wide " : ""}${opts.dialogClass ?? ""}`.trim(),
|
||||
attrs: { role: "dialog", "aria-label": opts.title },
|
||||
children: [
|
||||
el("div", {
|
||||
className: "ui-modal__top",
|
||||
children: [
|
||||
el("h3", { text: opts.title }),
|
||||
createButton({ label: opts.closeLabel, variant: "ghost", onClick: () => void ask() }),
|
||||
],
|
||||
}),
|
||||
body,
|
||||
],
|
||||
children: [top, body],
|
||||
});
|
||||
const backdrop = el("div", {
|
||||
className: `ui-modal__backdrop ${opts.backdropClass ?? ""}`.trim(),
|
||||
@@ -43,7 +51,9 @@ export function openModal(opts: ModalOptions): { close: () => void } {
|
||||
});
|
||||
backdrop.addEventListener("click", (ev) => ev.target === backdrop && void ask());
|
||||
backdrop.addEventListener("keydown", (ev) => ev.key === "Escape" && void ask());
|
||||
opts.mount(body, close);
|
||||
opts.mount(body, close, actions);
|
||||
// 단추가 있을 때만 머리에 — [닫기] 는 머리 직속 그대로(B05 추천의 `.ui-modal__top > button` 갈아 끼우기)
|
||||
if (actions.childElementCount) closeButton.before(actions);
|
||||
document.body.append(backdrop);
|
||||
dialog.tabIndex = -1;
|
||||
// 몸의 첫 입력 칸에 커서 — mount 안의 focus() 는 붙기 전이라 안 먹음
|
||||
|
||||
Reference in New Issue
Block a user