180 lines
17 KiB
Markdown
180 lines
17 KiB
Markdown
# OrgChart Framework Integration
|
|
|
|
상태: 설계 계약·사용 예시 제안. **현재 package/Wrapper/HTML bundle은 존재하지 않으며 예시 API·경로도 승인 전이다.** 이 문서는 코드를 생성하거나 지원 완료를 주장하지 않는다. [Architecture](architecture.md)의 facade와 [Data Contract](data-contract.md)를 공통 기준으로 한다.
|
|
|
|
## 1. 지원 범위와 책임
|
|
|
|
1차 공식 개발 대상은 **Vanilla HTML/JavaScript와 React만**이다. 현재 두 환경 모두 구현 미착수·실행 **NOT VERIFIED**다. Vue·Angular·기타 framework는 이번 개발 범위에서 제외하며 Adapter 구현·배포·지원 검증을 계획에 포함하지 않는다. 최초 추가 요청의 Vue 설계 항목은 최신 지원 범위 확정으로 대체했다. 향후 Core facade를 연결할 수 있도록 framework 독립성만 유지한다.
|
|
|
|
우선 검토 구성은 `@hm-design/org-chart`(Core), `@hm-design/org-chart-react`(React Wrapper)다. 실제 경로·두 패키지 분리는 workspace/build/dependency 검토 후 최종 승인한다. React Wrapper는 Engine Adapter와 다르다: Wrapper는 React와 Core 사이, Engine Adapter는 Core와 dabeng 사이에 있다. Data Source Adapter는 외부 형식과 normalized 계약 사이에 있다.
|
|
|
|
- Core: 표준 DOM, 공통 계약·Validation·ViewModel·집계·engine·노드 UI·Theme CSS·인터랙션·요청 취소·생명주기.
|
|
- Vanilla 호스트: container 제공, Source·options 전달, Theme/CSS/script 로딩, 페이지 제거 시 Destroy.
|
|
- React Wrapper: ref/effect로 Core mount, Props 변경을 update/reload 또는 설정 재생성에 연결, 언마운트 시 Destroy. 공통 업무 로직·렌더링 엔진·노드 디자인을 재구현하지 않는다.
|
|
- 상위 앱: Layout·Navigation·Routing·API 인증·권한·Theme 공급. Core에 React context나 인증 hook을 요구하지 않는다.
|
|
|
|
두 환경은 동일 JSON 입력 → 동일 Validation → 동일 계층·집계 → 동일 Core 노드·Theme·인터랙션을 사용한다. React 외부 HM Component는 페이지 조합이며 Core 내부 노드의 다른 디자인을 만드는 근거가 아니다.
|
|
|
|
## 2. 생명주기 계약 초안
|
|
|
|
메서드 이름은 확정 생명주기 요구를 구체화한 API 제안이다. engine 자체의 destroy 지원과 혼동하지 않는다.
|
|
|
|
1. `createOrgChart(options)`: DOM 생성·요청 없이 Handle을 만든다. framework import와 전역 window 접근 부작용을 ESM import 시 피하는 안을 검토한다.
|
|
2. `mount(container)`: 연결된 HTMLElement의 전용 자식 root를 소유하고 첫 Source를 읽는다. 호스트의 sibling/기존 콘텐츠를 지우지 않는다. 한 Handle에 한 container이며 중복 mount는 명시적 호출 오류로 거부한다.
|
|
3. `update(input)`: mounted 상태에서 normalized 스냅샷 후보를 검증·집계·렌더한다. Source fetch 없이 스냅샷만 바꾼다. 현재 Source 객체는 유지하므로 이후 reload는 그 Source를 다시 읽는다.
|
|
4. `reload(source?)`: mounted 상태에서 현재/교체 Source를 읽고 같은 반영 경로를 사용한다. JSON → Mock API/실제 API 전환의 공개 지점이다.
|
|
5. `destroy()`: 동기·반복 안전한 최종 정리. 진행 요청 abort·revision 무효화·콜백 중단, Core/engine 이벤트·Observer·frame/timer·DOM·인스턴스 참조를 정리한다. destroyed Handle은 재사용하지 않고 새 Handle을 만든다.
|
|
|
|
mount/update/reload는 Promise<void> 제안이다. 데이터·Source 실패는 Error state로 알리고 Promise를 정상 완료하는 방식을 우선 검토한다. 잘못된 호출 순서·사용법은 명시적 오류로 reject한다. abort/새 revision에 의해 폐기된 작업은 unhandled rejection 없이 종료하고 DOM/상태를 바꾸지 않는다. mount 진행 중에도 이미 확보한 container의 update/reload는 이전 작업을 무효화한다. mount 전·destroy 후 update/reload는 오류다. Promise 완료는 해당 revision의 반영 또는 controlled error 전환까지이며 이미지/폰트의 모든 로드 완료를 뜻하지 않는다.
|
|
|
|
상태 전이는 loading → ready/empty/error다. update는 동기 validation 중에도 같은 state 의미를 유지한다. Empty 정의와 갱신 중/실패 시 직전 화면 유지 정책은 TBD다. 같은 Metadata 버전이라도 새 입력을 무시하지 않는다. reload/update 경쟁은 마지막 시작 revision이 우선하도록 제안한다.
|
|
|
|
엔진 정리 가능성은 최초 PoC에서 입증해야 한다. document 이벤트·관찰자를 해제할 경로를 찾지 못하면 Destroy 완료를 PASS로 표시하지 않는다. core가 소유하지 않은 engine 자원을 `innerHTML = ""`만으로 정리했다고 판단하지 않는다. 제한/통합 대안을 승인받기 전 기능 개발을 확대하지 않는다.
|
|
|
|
engine 재생성 전 이전 chart가 살아 있는 동안 기존 pan/zoom·minimap 등의 해제 가능성을 확인한다. update/reload 반복 후에도 이벤트/Observer가 증가하지 않는지 검사한다. Core와 데이터 계층의 DOM 없는 import는 upstream 직접 import와 구별하며 지연 engine 로딩/변환 entry를 검증한다.
|
|
|
|
## 3. 데이터 Source 사용 예시
|
|
|
|
아래 `createJSONSource`·`createExternalJSONSource`도 제안 API다. 모든 반환값은 같은 validator를 통과한다. 가상 데이터 `data`는 Data Contract §3 스냅샷을 뜻한다.
|
|
|
|
```ts
|
|
// 제안 API; 아직 설치 가능한 공개 함수가 아니다.
|
|
const jsonSource = createJSONSource(data);
|
|
const externalSource = createExternalJSONSource({ url: configuredJsonUrl });
|
|
// URL은 호스트 설정. Core에 회사 서버 주소를 하드코딩하지 않는다.
|
|
```
|
|
|
|
JSON Mock은 객체 스냅샷을 공급하고 consumer 입력을 수정하지 않는다. External JSON은 AbortSignal·HTTP 오류·JSON 구문 오류를 처리한다. source가 JSON을 반환했다는 이유만으로 Validation을 생략하지 않는다.
|
|
|
|
JSON Source는 생성 시점 입력을 캡처하는 안, update는 호출 시점 입력을 확보하는 안을 제안한다. 같은 객체의 외부 mutation을 자동 변경 알림으로 취급하지 않는다. 검증·집계·DOM 반영은 Core 소유 불변 스냅샷에서 수행하고 최종 state/DOM commit 직전 revision·destroy 상태를 다시 확인한다.
|
|
|
|
API가 마련됐을 때의 공통 계약 예시:
|
|
|
|
```ts
|
|
// OrgChartDataSource, OrgChartData는 문서의 초안 type이다.
|
|
function createApiSource(
|
|
request: (signal: AbortSignal) => Promise<unknown>,
|
|
mapResponse: (raw: unknown) => OrgChartData
|
|
): OrgChartDataSource {
|
|
return {
|
|
async load({ signal }) {
|
|
return mapResponse(await request(signal));
|
|
}
|
|
};
|
|
}
|
|
const apiSource = createApiSource(hostApiRequest, mapApiResponse);
|
|
await handle.reload(apiSource); // Core/노드 UI/집계/React Wrapper는 유지
|
|
```
|
|
|
|
실제 request·mapping은 명세 확정 후 호스트에서 작성한다. mapResponse type 선언은 보안 경계가 아니며 Core가 결과를 재검증한다. Endpoint·인증·재시도·캐시를 임의로 예시에 넣지 않는다. Mock API는 같은 fixture를 Promise로 반환하고 지연/오류를 주입하여 실제 서버 없이 전환을 검증한다.
|
|
|
|
## 4. Vanilla HTML 사용 예시
|
|
|
|
아래 asset 이름·`window.HMOrgChart` namespace는 미래 browser bundle **제안**이며 파일을 제작한 것이 아니다. React 설치 없이 classic script + 별도 CSS + inline JSON으로 `file://` 직접 실행을 검증할 예시다. 포장된 Theme CSS·font·Core CSS·번들이 모두 상대 경로로 제공돼야 한다.
|
|
|
|
```html
|
|
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>가상 조직도 검증</title>
|
|
<link rel="stylesheet" href="./assets/hm-theme.css">
|
|
<link rel="stylesheet" href="./assets/orgchart.css">
|
|
</head>
|
|
<body>
|
|
<div id="orgchart-host"></div>
|
|
<script id="orgchart-data" type="application/json">
|
|
{
|
|
"organizations": [{"id":"org-a","parentId":null,"name":"가상 조직","type":"root","leaderId":"emp-a","displayOrder":0}],
|
|
"employees": [{"id":"emp-a","organizationId":"org-a","affiliationId":"aff-a","name":"가상직원 가","position":"책임","jobTitle":"조직장","profileImage":null,"workStatus":"active"}],
|
|
"affiliations": [{"id":"aff-a","name":"가상 관계사"}],
|
|
"relations": [],
|
|
"presentation": {"title":"가상 조직도","description":null,"hubText":null,"groups":{"showEmployees":true,"showSummary":false,"showDispatched":false,"showOnLeave":false,"showAffiliations":false},"organizationOrder":[]},
|
|
"metadata": {"schemaVersion":"0.1-draft","asOfDate":"2026-10-12","dataVersion":"html-mock-1","updatedAt":null,"updateNote":null}
|
|
}
|
|
</script>
|
|
<script src="./assets/orgchart.browser.js"></script>
|
|
<script>
|
|
const data = JSON.parse(document.getElementById("orgchart-data").textContent);
|
|
const { createOrgChart, createJSONSource } = window.HMOrgChart;
|
|
const handle = createOrgChart({ dataSource: createJSONSource(data) });
|
|
handle.mount(document.getElementById("orgchart-host"))
|
|
.catch(() => console.error("OrgChart lifecycle error"));
|
|
window.addEventListener("pagehide", () => handle.destroy(), { once: true });
|
|
</script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
위 예시는 실행 검증 전 설계용이다. inline JSON은 가상 데이터만 사용한다. pagehide 이후 bfcache 복귀가 필요한 앱은 새 instance의 재mount를 호스트에서 계획한다. SPA 콘텐츠 제거에서는 pagehide에만 의존하지 않고 해당 제거 시점에 Destroy한다.
|
|
|
|
ESM 비교 예시(HTTP(S) static server에서 우선 검증):
|
|
|
|
```js
|
|
import { createOrgChart, createJSONSource } from "./assets/orgchart.esm.js";
|
|
const handle = createOrgChart({ dataSource: createJSONSource(data) });
|
|
await handle.mount(document.getElementById("orgchart-host"));
|
|
```
|
|
|
|
bare npm specifier는 일반 HTML에서 자동 해석되지 않는다. file://의 module/fetch 제한과 서버 기반 ESM 실행을 구별한다. `file://`에서는 inline JSON + classic bundle 경로를 검증하고 외부 JSON fetch가 가능하다고 보장하지 않는다. External JSON/API는 HTTP(S)·CORS·호스트 권한 조건에서 별도로 검증한다.
|
|
|
|
## 5. React 사용 예시와 Wrapper 계약
|
|
|
|
아래 package names·Component/Prop은 우선 분리안의 예시이며 실제 Export가 아니다. 동일 Core CSS·Theme를 사용한다. React 호스트의 기존 CSS entry 순서를 지키고 HM 공개 Component를 사용하는 주변 영역은 해당 HM Tailwind source/Theme 규칙을 유지한다.
|
|
|
|
```tsx
|
|
import { useMemo } from "react";
|
|
import { createJSONSource } from "@hm-design/org-chart";
|
|
import { OrgChart } from "@hm-design/org-chart-react";
|
|
import "@hm-design/org-chart/style.css";
|
|
// 기존 앱 Theme entry에서 @hm-design/ui/theme.css를 연결한다.
|
|
|
|
function OrgChartContent({ data }: { data: OrgChartData }) {
|
|
const dataSource = useMemo(() => createJSONSource(data), [data]);
|
|
return <OrgChart dataSource={dataSource} />;
|
|
}
|
|
```
|
|
|
|
Props 후보는 dataSource·aggregationPolicy·interaction·onStateChange다. title/description/group/order는 공통 데이터의 PresentationConfig를 사용한다. React 전용 표현·집계 옵션을 따로 만들지 않는다. 직접 스냅샷 Prop 추가는 public API 승인 후 검토한다.
|
|
|
|
Wrapper 동작: 최초 ref container 확보 → Core 생성·mount; dataSource 교체 → 기존 Handle.reload(newSource); 동일 Source의 내부 값 변경은 명시적 reload 또는 새 immutable Source로 전달한다. 새 객체를 매 렌더 생성해 불필요한 reload를 유발하지 않는다. options 중 집계·인터랙션 변경은 Core의 갱신 API 확장 또는 cleanup 후 instance 재생성 중 PoC로 결정한다. 미확정 옵션을 조용히 무시하지 않는다.
|
|
|
|
언마운트는 destroy; effect cleanup→setup이 반복되는 React StrictMode에서도 새 instance만 mount한다. promise 경쟁·늦은 callback·변경되지 않은 props에 의한 중복 engine 생성·callback identity로 인한 무한 reload를 검사한다. Core가 DOM을 소유하는 전용 container 자식에 React가 동시에 노드를 render하지 않는다. Framework wrapper는 engine/node/Validation/집계 CSS를 복제하지 않는다.
|
|
|
|
SSR/Server Components의 DOM 렌더링은 이번 지원 완료 범위가 아니다. import 시 브라우저 global 부작용·React client boundary·hydrate 안전성은 검토하고 미확인 기능은 NOT VERIFIED로 남긴다.
|
|
|
|
## 6. CSS·Theme·배포 방식
|
|
|
|
같은 Core CSS와 HM Variables mapping을 모든 배포가 사용한다. 호스트는 Theme를 로드하고 Core는 컨테이너 내부 전용 selector만 사용한다. Theme 정책과 실제 선언 근거는 [Integration §3](integration.md#3-hm-theme-연결)을 따른다.
|
|
|
|
- npm: Core ESM + type + CSS, Wrapper ESM + type 후보. exports/types/files/sideEffects를 설계하고 package tarball 안에서 실제 import·CSS·font/asset 존재를 검증한다. 실제 publish는 별도 승인이다.
|
|
- ESM: npm bundler import와 브라우저 상대 URL import를 구별한다. Core에 React/React DOM 또는 unresolved bare dependency가 섞이지 않도록 검증한다.
|
|
- browser bundle: IIFE/UMD 중 직접 script 사용 형식을 선택한다. `window.HMOrgChart` 후보, 엔진 포함 여부·외부 dependency·license·크기·CSP를 검토한다. inline page 예시는 CSP 승인 완료를 뜻하지 않는다.
|
|
- CSS: Core 스타일을 별도 `style.css` export/상대 asset로 제공하고 Theme를 중복 주입하지 않는다. upstream CSS의 container 밖 누출도 포함해 scope를 검증한다.
|
|
- TypeScript: Core 선언에는 React type이 없어야 하고 Wrapper만 React Props를 가진다. JS consumer에 TS transpilation을 요구하지 않는다.
|
|
- Theme 미연결: 승인된 scope 기본 스타일 제공 vs Theme 필수 연결 중 **TBD**다. 어느 정책이든 HTML/React가 같아야 한다. approved 기본값 없이 fallback 팔레트를 새로 만들지 않는다.
|
|
|
|
Root의 기존 build script는 두 패키지만 처리하므로 새 Core·Wrapper build 연결 범위는 승인 후 확정한다. 엔진 CommonJS/UMD를 ESM·browser bundle로 변환할 수 있는지, export 기능 dependencies가 필수로 포함되는지, DOM-only engine와 adapter의 tree-shaking 가능성은 **NOT VERIFIED**다. 단순히 빌드 설정을 제안했다는 이유로 배포 가능 PASS를 내지 않는다.
|
|
|
|
Vanilla Theme를 위해 Core dependency/peer에 React peer를 가진 `@hm-design/ui` 전체 package를 필수로 추가하지 않는다. 승인된 호스트 asset/별도 Theme 산출 경로에서 React 미설치 consumer 동작을 확인한다. Core/engine CSS 누출, 호스트 Theme의 전역 영향, 전달된 font의 상대 URL은 서로 다른 검증 대상이다.
|
|
|
|
Theme owner는 호스트 하나이며 Core는 scope를 상속한다. 별도 Scoped Theme는 기존 `[data-hm-theme]` mapping 재계산·Scoped partial Default 의미를 따르고 Core가 자동 주입/reset하지 않는다. Global A/Scoped B의 두 instance가 서로 영향을 주지 않는지 확인한다.
|
|
|
|
## 7. 최소 통합 검증 계획
|
|
|
|
동일 가상 fixture·공통 설정·Theme로 두 환경을 비교한다. 테스트가 확인해야 할 차이를 명시하고 같은 검사 반복을 피한다.
|
|
|
|
1. React 설치 없는 HTML 단독 페이지에서 표시. `file://` classic bundle과 HTTP(S) ESM의 asset/console 결과를 각각 기록.
|
|
2. React에서 같은 Core로 동일 계층·노드 UI·인터랙션 표시. 새 렌더링 엔진이 없는지 dependency/code 검토.
|
|
3. 같은 JSON의 조직 ID·parent·순서·leader·직원 그룹과 공통 집계 결과 일치.
|
|
4. 조직 추가/삭제/이동·직원 이동/상태/관계사/사진·title/기준일 변경 후 재검증과 갱신. consumer 원본 객체를 바꿔도 이미 캡처/반영한 스냅샷과 집계가 암묵적으로 바뀌지 않음.
|
|
5. JSON → Mock API Source → JSON 전환, API 매핑 계약·loading/error·응답 경쟁·abort 확인.
|
|
6. HM Theme 연결 및 승인된 Theme 미연결 정책. computed Variables·node CSS·font·scope가 두 환경에서 일치. Global A + Scoped B + 두 instance의 독립성·Theme owner와 cleanup도 확인.
|
|
7. mount/update/reload/destroy·연속 remount·StrictMode·다중 instance에서 이벤트/Observer/timer/DOM 잔존과 늦은 callback 확인.
|
|
8. CSS 전역 충돌과 390/768/1280/1440px Overflow·키보드·focus·읽기 가능성 검증.
|
|
9. Core build·npm/ESM/browser bundle·CSS/type/license와 React dependency 미포함 검증.
|
|
10. 공식 운영 main·기존 UI/Theme/Storybook과 무관 파일 보존 증거. 코드 중복·공통 Core 재사용 검토.
|
|
|
|
Vue/Angular 실행 검증은 여기 포함하지 않는다. 기술 제약·승인 게이트는 [Implementation Plan](implementation-plan.md)을 따른다.
|