feat(spreadsheet): D 잇기 — 진짜 A 엔진과 재검증 · inspect 선택(window.__aisloSheet · 임시) · 시험 틀 한 번 시나리오
- spreadsheet.ts — `inspect` 선택이면 문맥을 window.__aisloSheet 에 걺(M02 [스프레드시트 시험] 단추 전용 · destroy 때 뗌 · 1단계 검증 뒤 지움) - 시험 틀 — ssScenario()(페이지 안 한 번에 한글 · 가리키기 · 재계산 · 되돌리기 · Delete · Ctrl+Enter · 수식 입력줄 · F2) · 가짜 격자 data-addr · 진짜 분수 값 보임 · 오류 모음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6KXAabDTenEU7hrDQmCKY
This commit is contained in:
@@ -48,6 +48,16 @@ export interface SpreadsheetOptions {
|
||||
onChange?: (book: Workbook) => void;
|
||||
/** 고름이 바뀜 — 시트 id · 활성 칸 A1 · 범위 A1 */
|
||||
onSelect?: (selection: { 시트: string; 칸: string; 범위: string }) => void;
|
||||
/** ⚠ 임시 — 브레인 브라우저 검증용: 문맥을 `window.__aisloSheet` 에 걺(book · 고름 · 명령 읽기).
|
||||
* M02 [스프레드시트 시험] 단추만 켬 · 산출근거 카드는 안 켬 · 1단계 검증 뒤 지움. */
|
||||
inspect?: boolean;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
/** ⚠ 임시 — `inspect` 로 연 표의 문맥(브레인 검증용) */
|
||||
__aisloSheet?: SpreadsheetContext;
|
||||
}
|
||||
}
|
||||
|
||||
export interface SpreadsheetHandle {
|
||||
@@ -139,6 +149,7 @@ export function createSpreadsheet(
|
||||
|
||||
grid.render();
|
||||
select(ctx.selection);
|
||||
if (opts.inspect) window.__aisloSheet = ctx; // ⚠ 임시 — 브레인 검증용
|
||||
|
||||
function notify(message: string): void {
|
||||
toast.textContent = message;
|
||||
@@ -244,6 +255,7 @@ export function createSpreadsheet(
|
||||
return ctx.engine.snapshot();
|
||||
},
|
||||
destroy() {
|
||||
if (window.__aisloSheet === ctx) delete window.__aisloSheet;
|
||||
clearTimeout(toastTimer);
|
||||
detachKeys();
|
||||
detachMouse();
|
||||
|
||||
@@ -10,8 +10,116 @@ window.ssSample = JSON.parse(JSON.stringify(sample));
|
||||
window.ss = createSpreadsheet(host, JSON.parse(JSON.stringify(sample)), {
|
||||
onChange: () => window.ssChanges++,
|
||||
onSelect: (s) => (window.ssSelect = s),
|
||||
inspect: true,
|
||||
});
|
||||
window.ssCreate = createSpreadsheet;
|
||||
/** 칸 입력 값(A1) */
|
||||
window.ssCell = (a1, sheet = "s1") =>
|
||||
window.ss.getDoc().시트.find((s) => s.id === sheet).칸[a1] ?? null;
|
||||
|
||||
/* 한 번에 도는 시나리오 — 탭이 새로 읽혀도 끊기지 않게 페이지 안에서 합성 사건으로 조작하고 수치를 모음.
|
||||
* (믿음 입력 — 진짜 키 · 마우스 · 한글 — 은 ORCA keypress · type · mouse 로 따로 잼) */
|
||||
window.ssScenario = () => {
|
||||
const ta = () => document.querySelector(".ss-input");
|
||||
const at = (a1) => {
|
||||
const d = document.querySelector(`[data-a1="${a1}"]`).getBoundingClientRect();
|
||||
return {
|
||||
clientX: d.left + d.width / 2,
|
||||
clientY: d.top + d.height / 2,
|
||||
bubbles: true,
|
||||
button: 0,
|
||||
};
|
||||
};
|
||||
const grid = () => document.querySelector(".ss-grid-host");
|
||||
const click = (a1, extra = {}) => {
|
||||
grid().dispatchEvent(new MouseEvent("mousedown", { ...at(a1), ...extra }));
|
||||
window.dispatchEvent(new MouseEvent("mouseup", at(a1)));
|
||||
};
|
||||
const dragTo = (from, to) => {
|
||||
grid().dispatchEvent(new MouseEvent("mousedown", from));
|
||||
window.dispatchEvent(new MouseEvent("mousemove", to));
|
||||
window.dispatchEvent(new MouseEvent("mouseup", to));
|
||||
};
|
||||
const key = (k, o = {}) =>
|
||||
document.activeElement.dispatchEvent(
|
||||
new KeyboardEvent("keydown", { key: k, bubbles: true, cancelable: true, ...o }),
|
||||
);
|
||||
const type = (text) => {
|
||||
const t = document.activeElement;
|
||||
t.setRangeText(text, t.selectionStart, t.selectionEnd, "end");
|
||||
t.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
};
|
||||
const shown = (a1) => document.querySelector(`[data-a1="${a1}"]`).textContent;
|
||||
const sel = () => `${ssSelect.칸}|${ssSelect.범위}`;
|
||||
const out = {};
|
||||
// 한글 첫 글자 — compositionstart 로 편집 시작 · 같은 textarea 그대로
|
||||
click("L2");
|
||||
ta().dispatchEvent(new CompositionEvent("compositionstart", { data: "" }));
|
||||
out.imeEditing =
|
||||
document.querySelector(".ss").classList.contains("ss--editing") &&
|
||||
document.activeElement === ta();
|
||||
type("한");
|
||||
key("Enter");
|
||||
out.ime = [ssCell("L2"), sel()];
|
||||
// 수 · 식 · 참조 가리키기(방향키 + 누르기) · 재계산
|
||||
click("L3");
|
||||
type("2");
|
||||
key("Enter");
|
||||
type("=");
|
||||
key("ArrowUp");
|
||||
out.pointKey = ta().value;
|
||||
type("*");
|
||||
click("J5");
|
||||
out.pointClick = [
|
||||
ta().value,
|
||||
[...document.querySelectorAll(".fg-ref")].map((x) => x.dataset.ref),
|
||||
];
|
||||
key("Enter");
|
||||
out.calc = [ssCell("L4"), shown("L4")];
|
||||
click("L3");
|
||||
type("5");
|
||||
key("Enter");
|
||||
out.recalc = shown("L4");
|
||||
// 되돌리기 · 다시
|
||||
key("z", { ctrlKey: true });
|
||||
out.undo = [ssCell("L3"), shown("L4")];
|
||||
key("y", { ctrlKey: true });
|
||||
out.redo = [ssCell("L3"), shown("L4")];
|
||||
// Delete(서식 남김) · 되돌림
|
||||
click("B4");
|
||||
key("Delete");
|
||||
out.del = ssCell("B4");
|
||||
key("z", { ctrlKey: true });
|
||||
out.delUndo = ssCell("B4");
|
||||
// 병합 · Shift/Ctrl 방향
|
||||
click("A4");
|
||||
key("ArrowRight", { shiftKey: true });
|
||||
key("ArrowRight", { shiftKey: true });
|
||||
out.shift = sel();
|
||||
click("A3");
|
||||
key("ArrowDown", { ctrlKey: true });
|
||||
out.ctrl = sel();
|
||||
// 끌어 고르기 · Ctrl+Enter
|
||||
dragTo(at("P2"), at("Q3"));
|
||||
out.drag = sel();
|
||||
type("=L3+1");
|
||||
key("Enter", { ctrlKey: true });
|
||||
out.ctrlEnter = ["P2", "Q2", "P3", "Q3"].map((k) => ssCell(k)?.식);
|
||||
// 수식 입력줄로 고치기
|
||||
click("N2");
|
||||
const bar = document.querySelector(".ss-formula");
|
||||
bar.focus();
|
||||
bar.value = "=J4*10";
|
||||
bar.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
key("Enter");
|
||||
out.bar = [ssCell("N2"), shown("N2"), sel()];
|
||||
// F2 — 방향키가 글자 자리만
|
||||
click("N2");
|
||||
key("F2");
|
||||
const before = sel();
|
||||
key("ArrowLeft");
|
||||
out.f2 = [before === sel(), document.querySelector(".ss").classList.contains("ss--editing")];
|
||||
key("Escape");
|
||||
out.errors = window.ssErrors;
|
||||
return JSON.stringify(out);
|
||||
};
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
<body>
|
||||
<div id="fakes"></div>
|
||||
<div id="host"></div>
|
||||
<script>
|
||||
window.ssErrors = [];
|
||||
window.addEventListener("error", (e) =>
|
||||
window.ssErrors.push(String(e.error?.stack || e.message)),
|
||||
);
|
||||
</script>
|
||||
<script src="../../../tmp/spreadsheet_d/harness.js"></script>
|
||||
<script>
|
||||
document.getElementById("fakes").textContent =
|
||||
|
||||
@@ -326,6 +326,7 @@ function createGrid(ctx) {
|
||||
});
|
||||
function show(v) {
|
||||
if (v === null || v === undefined) return "";
|
||||
if (typeof v === "object" && "n" in v) return String(Number(v.n) / Number(v.d)); // 진짜 엔진 분수
|
||||
if (typeof v === "object") return v.error;
|
||||
if (typeof v === "boolean") return v ? "TRUE" : "FALSE";
|
||||
return String(v);
|
||||
@@ -351,13 +352,15 @@ function createGrid(ctx) {
|
||||
if (m && (m.r0 !== r || m.c0 !== c)) continue;
|
||||
const d = el("div", {
|
||||
className: "fg-cell",
|
||||
attrs: { "data-a1": toA1(r, c) },
|
||||
attrs: { "data-a1": toA1(r, c), "data-addr": toA1(r, c) }, // data-addr = C 격자와 같은 꼴
|
||||
text: show(ctx.engine.value(sheet.id, r, c)),
|
||||
});
|
||||
place(d, cellBox(r, c));
|
||||
d.style.cssText +=
|
||||
";border-right:1px solid #eee;border-bottom:1px solid #eee;padding:2px 3px;overflow:hidden;white-space:nowrap";
|
||||
if (typeof ctx.engine.value(sheet.id, r, c) === "number") d.style.textAlign = "right";
|
||||
const v = ctx.engine.value(sheet.id, r, c);
|
||||
if (typeof v === "number" || (v && typeof v === "object" && "n" in v))
|
||||
d.style.textAlign = "right";
|
||||
cellLayer.append(d);
|
||||
}
|
||||
renderSelection();
|
||||
@@ -461,6 +464,8 @@ function mountTabs(ctx) {
|
||||
|
||||
module.exports = {
|
||||
el: { el },
|
||||
// B 함수 — 없음(진짜 엔진이 부르면 #NAME?)
|
||||
functions: { getFunction: () => undefined, functionNames: () => [] },
|
||||
address,
|
||||
parser,
|
||||
refshift,
|
||||
|
||||
Reference in New Issue
Block a user