상호작용 확장
드로잉 툴과 paneMaximize는 코어가 아니라 코어 위에 얹힌 확장이다 — 둘 다 지표·툴을 패키지로 짓는다와 같은 플러그인 계약을 쓴다. 이 페이지는 그 확장들이 사용자에게 어떤 상호작용 어휘를 주는지 보여준다.
드로잉 툴
import { drawingTools } from "@finchart/tools";
// pane에 매단다 — 드로잉은 그 pane의 값 축으로 산다. `plot`은 x·입력 스택을
// 쓰기 위해 따로 받는다.
const tools = plot.mainPane.use(drawingTools({ plot }));
tools.add({ type: "horizontal", price: 105 });
tools.select(handle); // 프로그램으로 선택
tools.select(null); // 선택 해제
tools.clear(); // 전부 지우기실행되는 전체 예제(선택 해제·삭제·전체 지우기 버튼까지)는 드로잉 툴에 있다.
paneMaximize — pane 하나를 채운다
import { paneMaximize } from "@finchart/core";
const maximize = plot.use(paneMaximize({ gestures: true }));gestures: true는 옵트인이다 — pane 더블클릭이 이미 doubleClickReset(기본 켜짐, Plot 계약의 인터랙션 옵션 표)에 걸려 있어 전체 보기 리셋으로 쓰인다. 최대화가 기본으로 같은 제스처를 먹으면 그 리셋이 모든 pane 클릭에서 조용히 죽는다 — 그래서 명시로 켜야 한다.
드로잉 툴과 같이 쓴다면 — 더블클릭의 임자는 설치 순서가 정한다
gestures: true인 paneMaximize와 드로잉 툴은 둘 다 dblclick을 본다. 도형 위 더블클릭이 도형을 지목할지 pane을 최대화할지는 우선순위가 같을 때 나중에 등록된 쪽이 이긴다(입력 스택은 동률에서 나중 등록을 먼저 묻는다).
즉 드로잉 툴을 나중에 얹으면 도형이 이기고, 먼저 얹으면 최대화가 이긴다. Esc(선택 해제 vs 최대화 복원)도 같은 순서로 갈린다. 지금은 paneMaximize가 우선순위 옵션을 받지 않지만 도구함 쪽에는 있다 — drawingTools({ plot, priority: 1 })을 주면 설치 순서와 무관하게 도구가 먼저 받는다(실측). 즉 레버는 둘이고, paneMaximize가 이기게 하려면 아직 설치 순서뿐이다.
키보드 계약
컨테이너 요소(당신이 build()에 준 것)가 언제나 포커스를 받는다 (tabIndex 0) — 캔버스가 아니다. keyboard: false가 끄는 것은 아래 표의 "코어" 두 줄뿐이다. 탭 순서에서 빼려면 요소에 tabindex="-1"을 직접 준다.
그리고 그 이름은 당신의 것이다. 우리가 당신의 컨테이너를 탭 정지점으로 만들지만 이름도 role도 안 준다 — 차트가 무엇을 그리는지는 우리가 모르기 때문이다. 안 달면 스크린 리더 사용자가 도달하는 것은 이름 없는 컨테이너다.
<div id="chart" role="img" aria-label="AAPL 일봉, 2024년 1월 ~ 6월"></div>전체 계약은 plot-contract의 키보드 조작(접근성)에 있다 — 이 쪽은 그 절의 요약이다. 두 벌로 두었더니 이쪽만 낡았다 (46차 디자인 리뷰 지적 4: keyboard: false가 tabIndex도 끈다고 적혀 있었는데, pointer.ts는 옵션 분기 밖에서 붙인다).
| 키 | 하는 일 | 소유 |
|---|---|---|
← → | 화면 폭의 5%씩 pan | 코어 |
+ − | 중앙 기준 줌 | 코어 |
Esc | 작도 취소 → 드래그 취소 → 선택 해제 (그 순서) | 드로잉 툴 |
Esc | (드로잉 툴이 놓을 게 없으면) 최대화된 pane 복원 | paneMaximize |
Delete Backspace | 선택된 드로잉 삭제 | 드로잉 툴 |
] [ | 드로잉 선택 순회 (다음/이전, 끝에서 감긴다) | 드로잉 툴 |
| pane 더블클릭 | 그 pane 최대화 토글 | paneMaximize({ gestures: true }) |
임자는 커서 아래 겨루는 자다. 드로잉 툴을 pane마다 하나씩 달면 Delete· ]·[는 커서가 마지막으로 지난 pane의 것이다. 아무도 안 겨루는 자리(축·여백·도구함 없는 지표 pane) 위에서는 마지막 임자가 그대로다 — 가격 라벨을 읽으러 축으로 갔다가 Delete를 누르는 것이 정상 동선이기 때문이다. Esc만 이 판정을 안 묻는다(탈출 키라 어디서 눌러도 지금 하던 것이 끝난다).
우클릭은 고르되 먹지 않는다 — 선 위면 그 선을 선택하고 빈 곳이면 선택을 푼다(왼쪽 버튼과 같은 규칙). 그리고 false를 돌려주므로 contextmenu 이벤트가 그대로 울린다. 앱은 그 안에서 tools.selection()을 읽어 메뉴를 만든다:
plot.on("contextmenu", ({ position }) => {
const target = tools.selection(); // 커서 아래 드로잉, 없으면 null
openMenu(position, target);
});네이티브 메뉴는 우리가 안 막는다. @finchart/dom은 소비자가 이벤트를 먹었을 때만 preventDefault하는데 도구함은 일부러 안 먹으므로, 앱이 자기 컨테이너에 contextmenu 리스너를 달아 막아야 한다. 안 막으면 앱 메뉴 위에 브라우저 메뉴가 같이 뜬다.
키는 입력 스택을 한 번 지난다 — 소비자가 안 먹으면 다음으로 넘어간다. Esc의 임자도 위 경고와 같은 규칙이 정한다(나중에 얹은 쪽이 먼저 받는다). 안 먹은 키는 코어의 pan/zoom으로 간다. Tab은 아무도 안 뺏는다 — 포커스 이동은 브라우저의 것이다.
프로그램으로 같은 걸 하고 싶으면 실제 키 입력을 흉내 낸다 — routeInput이 입력 스택을 그대로 지나가므로 진짜 키보드 이벤트와 같은 경로다.
plot.routeInput({ type: "keydown", key: "Delete" });왜 두 어휘가 다른 확장인가
드로잉 툴과 paneMaximize는 서로 몰라도 된다 — 둘 다 Esc 하나를 두고 체인으로만 협상한다. 코어 커밋 없이 붙었다는 것 자체가 이 협상이 확장 쪽 배선만으로 충분하다는 증거다. 능력 인터페이스로 요구를 좁히는 설계 근거는 지표·툴을 패키지로 짓는다에 있다.