diff --git a/mock/e2e/phy.spec.ts b/mock/e2e/phy.spec.ts index fed8635..320d971 100644 --- a/mock/e2e/phy.spec.ts +++ b/mock/e2e/phy.spec.ts @@ -637,3 +637,24 @@ test("クラスターを選んでキーを足すと、そのクラスターに expect(Math.abs(box.x - left!.x)).toBeLessThan(1); expect(Math.abs(box.y - (left!.y + unit))).toBeLessThan(1); }); + +test("バリアントグループを選んでキーを足すと、表示中のバリアントに新しい行を作る", async ({ page }) => { + await keysSelectVariantGroup(page); + const group = page.locator('[data-node-type="variantGroup"]').first(); + const groupId = await group.getAttribute("data-node-id"); + await page.locator(`[data-tree-id="${groupId}"]`).first().click(); + const groupKeys = group.locator('[data-node-type="key"]'); + const before = await groupKeys.evaluateAll((es) => es.map((e) => e.getAttribute("data-node-id"))); + const boxes = await Promise.all(before.map((id) => page.locator(`[data-node-id="${id}"]`).boundingBox())); + await page.getByRole("button", { name: "キーを足す" }).click(); + await expect(groupKeys).toHaveCount(before.length + 1); + const after = await groupKeys.evaluateAll((es) => es.map((e) => e.getAttribute("data-node-id"))); + const added = after.find((id) => !before.includes(id)); + await expect(page.locator(`[data-sel-id="${added}"]`)).toHaveCount(1); + const box = (await page.locator(`[data-node-id="${added}"]`).boundingBox())!; + // The variant holds two keys of the first row, 1U apart; the new row starts 1U below its left end. + const [left, right] = [...boxes].sort((a, b) => a!.x - b!.x); + const unit = right!.x - left!.x; + expect(Math.abs(box.x - left!.x)).toBeLessThan(1); + expect(Math.abs(box.y - (left!.y + unit))).toBeLessThan(1); +}); diff --git a/mock/src/ui/phy/usePhyCommands.ts b/mock/src/ui/phy/usePhyCommands.ts index c00d1ca..abdad0c 100644 --- a/mock/src/ui/phy/usePhyCommands.ts +++ b/mock/src/ui/phy/usePhyCommands.ts @@ -128,13 +128,16 @@ export function usePhyCommands(options: Options) { function keyAdd() { const chosen = options.node(options.selected.value.at(-1) || ""); - // A selected cluster receives a new row inside it, like an empty selection inside the focus. + // A selected cluster, or the shown variant of a selected group, receives a new row inside it, + // like an empty selection inside the focus. const parent = chosen?.type === "cluster" ? chosen.id - : chosen - ? options.selectedContainer(chosen.id) - : insertionParent(); + : chosen?.type === "variantGroup" + ? options.variants.value[chosen.id] || chosen.default + : chosen + ? options.selectedContainer(chosen.id) + : insertionParent(); const keys = nodesInContainer(parent).flatMap((node) => node.type === "key" ? [{ id: node.id, x: node.transform.x, y: node.transform.y, ...node.size }] : [], ); @@ -143,7 +146,7 @@ export function usePhyCommands(options: Options) { const { x, y } = chosen?.type === "key" ? rowEndPosition(keys, chosen.id)! - : chosen && chosen.type !== "cluster" + : chosen?.type === "component" ? { x: snap(chosen.transform.x + 1), y: snap(chosen.transform.y) } : (newRowPosition(keys) ?? { x: snap(center.x), y: snap(center.y) }); let id = ""; diff --git a/provisional/decisions.md b/provisional/decisions.md index f10bc99..700e2d9 100644 --- a/provisional/decisions.md +++ b/provisional/decisions.md @@ -84,7 +84,7 @@ T-29 のコードを作り直さず、[phy-editor-feedback.md](phy-editor-feedba | 3 | バリアント | ポップオーバーの印を「既定」と分かる見た目にする。バッジには名前を書かず、表示中のバリアント名はツールチップで示す。クラスター内のキーから作ったバリアントグループは、そのクラスターの中に作る | `NodeSvg.vue`、`usePhyCommands.ts` | | 4 | ドラッグ中の自動スクロール | キーを画面の端へ寄せると、キャンバスがスクロールする | `usePhyCanvasPointer.ts`、`usePhyCanvasView.ts` | | 5 | クラスターの原点 | 選択中のクラスターにだけ、原点に丸を出す | 1の層 | -| 6 | キー追加の位置 | 選択中のキー(複数なら最後に選んだキー)の行の右端に追加する。何も選んでいなければ、同じコンテナの全キーの下に新しい行を作り、その左端に置く。クラスターを選んでいれば、そのクラスターの中に同じように新しい行を作る。行の判定は `domain/` に純粋関数として実装してテストし、のちにIDX Editorでも使う。規則は [idx-editor-feedback.md](idx-editor-feedback.md)「行と列の判定」 | `domain/rows.ts`、`usePhyCommands.ts` | +| 6 | キー追加の位置 | 選択中のキー(複数なら最後に選んだキー)の行の右端に追加する。何も選んでいなければ、同じコンテナの全キーの下に新しい行を作り、その左端に置く。クラスターを選んでいればその中に、バリアントグループを選んでいれば表示中のバリアントの中に、同じように新しい行を作る。行の判定は `domain/` に純粋関数として実装してテストし、のちにIDX Editorでも使う。規則は [idx-editor-feedback.md](idx-editor-feedback.md)「行と列の判定」 | `domain/rows.ts`、`usePhyCommands.ts` | | 7a | 長押しの切り出し | 長押しの処理を composable に切り出す。挙動は変えない | `usePhyCanvasPointer.ts` | | 7b | 長押しの2方式 | ループ方式(間隔を延ばし、円形インジケータと選択先のリストを付ける)と、長押しでリストを出して選ぶ方式を入れ、切り替えて比べる | 7aの composable | | 8 | スイッチ・スタビライザーの表示 | 既定は非表示にし、トグルで色違いの十字を出す。プロパティ欄に `switchOffset` の入力欄を置く | 1の層、`PhyProperties.vue` |