Files
HMDesign/docs/org-chart/org-chart-usage-guide.md
T

77 KiB
Raw Blame History

HMDesign OrgChart 공통 사용 가이드

개발자·Codex·Claude가 공개 API로 조직 데이터를 연결하기 위한 가이드다. 2026-10-13 P10.7의 검증 기준 Commit은 d56d587fd20316f19554105f33c200060394962b(feature/orgchart-package)이다. 이 SHA는 당시 검증한 버전이며 향후 최신 버전을 뜻하지 않는다. 실제 받은 버전은 git rev-parse HEAD로 확인한다. 기능 검증 근거는 최종 Feature 통합 보고서, 승인 대기 항목은 P10.7 정식 통합 승인 준비를 따른다. 과거 P-AI1/P10.2의 제한을 현재 지원 여부로 사용하지 않는다.

빠른 시작은 아래 데이터 파일과 React 또는 Vanilla 진입점을 함께 복사한다. 예제는 Vite의 TypeScript 환경을 전제로 한다. 기본 예제는 제목·요약·툴바가 있는 Auto 화면이다. Auto/Expanded Chart Only는 구현 및 기존 브라우저 검증이 완료됐으며, 외부 UI 없이 삽입할 때는 Chart Only 공개 API를 사용한다. Fixed Full Composition은 지원하지만 Fixed Chart Only는 미지원이다.

P11 외부 CSS 테마 (2026-10-13)

Core와 React는 같은 Shadow DOM CSS 정책을 사용한다. 공개 JS API·데이터 계약은 바뀌지 않는다. 외부 입력 --hm-orgchart-*는 호스트에서 상속되며, 내부 resolved-*/current-*는 실제 사용 요소에서 기본값을 계산한다. 회사색과 Fixed 부서색을 JS inline으로 덮어쓰지 않는다. 기본 파일만 연결하면 기존 회사색·카드·디자인을 유지한다.

Shadow DOM 안에 외부 파일 연결

stylesheetUrls.core는 기본 스타일 전체를 포함하는 한 개의 CSS URL이다. 테마 변수만 있는 파일로 교체하면 기본 레이아웃이 누락된다. engine에는 기존 엔진 CSS URL을 유지한다. 일반 HTTP 서버에서는 공개 style.css를 복사한 경로로 import한다.

/* /themes/external-theme.css — 기본 CSS를 먼저, 컬러 override를 뒤에 */
@import url('/assets/org-chart/style.css');
:host {
  --hm-orgchart-bg-surface: #edf4ff;
  --hm-orgchart-bg-content: #dce8f8;
  --hm-orgchart-company-hanmac: #2457a7;
  --hm-orgchart-company-saman: #385ca8;
  --hm-orgchart-group-title-color: #fff4d6;
  --hm-orgchart-card-background: #f4f8ff;
  --hm-orgchart-card-border: #527aa9;
  --hm-orgchart-expanded-line-color: #249a79;
}

Vite에서는 @import '@hm-design/org-chart/style.css';를 사용하고 CSS URL로 import한다. 실행 가능한 전체 예제는 external-theme.css, React·Vanilla 소비 fixture다. !important가 필요하지 않다. :host에 선언한 값은 내부 기본값의 fallback보다 우선한다. 동일 속성의 CSS 규칙을 직접 재작성할 때는 보통의 specificity·source order가 적용된다.

React:

import { OrgChart } from '@hm-design/org-chart-react';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import externalThemeUrl from './external-theme.css?url';

<OrgChart dataSource={dataSource} layoutMode="auto"
  stylesheetUrls={{ engine: engineUrl, core: externalThemeUrl }} />

Vanilla (공개 ESM을 서빙하는 환경):

import { createOrgChart } from '/assets/org-chart/index.js';
const container = document.getElementById('chart');
if (!container) throw new Error('chart 요소가 없습니다.');
const chart = createOrgChart({
  container, dataSource,
  layoutMode: 'auto',
  stylesheetUrls: {
    engine: '/assets/org-chart/engine.css',
    core: '/themes/external-theme.css',
  },
});
await chart.mount();

호스트 HTML CSS 상속 방식

호스트에서 변수만 설정할 때는 기본 stylesheetUrls.core를 유지한다. 다음 파일을 호스트 HTML의 <link rel="stylesheet">로 연결하고, React의 className="orgchart-custom-theme" 또는 Vanilla 컨테이너의 같은 class를 사용한다.

.orgchart-custom-theme {
  --hm-orgchart-company-hanmac: #2457a7;
  --hm-orgchart-company-saman: #385ca8;
  --hm-orgchart-expanded-line-color: #249a79;
}

이 방식은 Custom Property 상속만 Shadow DOM을 통과한다. 호스트의 .hm-orgchart-employee { ... } 같은 selector는 Shadow 내부 카드에 적용되지 않는다. 내부 selector를 수정해야 하면 stylesheetUrls.core 파일 안에 작성한다. 호스트 class를 제거하면 기본값이 즉시 복원된다. 외부 테마를 동시에 쓸 경우 Shadow 파일의 :host에 같은 입력을 선언하면 조상에서 상속된 값보다 우선한다. 소비자가 직접 inline으로 입력 변수를 설정하면 소비자 inline이 우선하므로 CSS 파일 운영 시 이를 제거한다.

P11.5 Fixed·Expanded 캔버스 배경 독립 설정 (2026-10-14)

캔버스 전용 입력은 --hm-orgchart-fixed-canvas-background와 --hm-orgchart-expanded-canvas-background다. 새 JS 옵션이나 React Props 없이 같은 Core CSS로 React·Vanilla에 적용된다.

  • Fixed readOnly / Full Composition: 미지정 시 기존 --hm-orgchart-bg-content → #F5F8FB 유지. composition과 내부 stage에 함께 적용된다. Fixed interactive는 미지정 시 기존 투명 배경을 유지한다.
  • Expanded / Auto: 내부 viewport에만 적용. 미지정 시 기존 --hm-orgchart-bg-surface → #FFFFFF를 유지하여 기존 외부 테마 override가 무효화되지 않는다. 카드 배경·바깥 프레임의 --hm-orgchart-expanded-preview-background는 바뀌지 않는다.
  • Chart Only: 기본 흰 캔버스와 기존 Fit·Wheel Zoom·Pan 유지. Auto 내부 viewport이므로 Expanded 캔버스 입력을 지정하면 같은 방식으로 커스터마이징할 수 있다. Fixed Chart Only는 계속 미지원이다.
  • 각 전용 입력이 기존 공통 배경 입력보다 우선한다. 한쪽 전용 입력은 다른 유형에 영향을 주지 않는다. Storybook 페이지·Header·외부 Container는 적용 대상이 아니다.

호스트 HTML에 연결하는 CSS 예시:

.orgchart-canvas-theme {
  --hm-orgchart-fixed-canvas-background: #F5F8FB;
  --hm-orgchart-expanded-canvas-background: #FFFFFF;
}

React (앞 절의 공개 CSS URL import와 dataSource 사용):

import coreUrl from '@hm-design/org-chart/style.css?url';

<OrgChart className="orgchart-canvas-theme" dataSource={dataSource}
  displayMode="readOnly" stylesheetUrls={{ engine: engineUrl, core: coreUrl }} />
<OrgChart className="orgchart-canvas-theme" dataSource={autoDataSource}
  layoutMode="auto" stylesheetUrls={{ engine: engineUrl, core: coreUrl }} />

Vanilla (앞 절의 공개 ESM import와 CSS 서빙 경로 사용):

fixedContainer.classList.add('orgchart-canvas-theme');
expandedContainer.classList.add('orgchart-canvas-theme');
const stylesheetUrls = { engine: '/assets/org-chart/engine.css', core: '/assets/org-chart/style.css' };
const fixed = createOrgChart({ container: fixedContainer, dataSource,
  displayMode: 'readOnly', stylesheetUrls });
const expanded = createOrgChart({ container: expandedContainer, dataSource: autoDataSource,
  layoutMode: 'auto', stylesheetUrls });
await fixed.mount();
await expanded.mount();

Shadow DOM 내부의 외부 CSS 파일을 사용할 때는 기존 기본 CSS import 뒤의 :host에 위 두 입력을 선언한다. 호스트 상속·:host·소비자 inline의 기존 우선순위는 그대로다. 같은 페이지에서 서로 다른 class 또는 각 컨테이너의 CSS 입력으로 인스턴스별 배경을 지정할 수 있다. 회사색·카드·연결선·팝업은 이 입력의 대상이 아니다.

공식 컬러 변수

아래 이름은 모두 --hm-orgchart- 접두사를 사용한다. resolved-*, current-*, expanded-primary, expanded-card-primary는 내부 계산 결과이므로 테마는 아래 공개 입력을 사용한다. 표에 없는 geometry API와 기존 table 변수는 그대로 유지한다.

입력 이름 (접두사 생략) 적용 범위 / 기본값 정책
fixed-canvas-background, expanded-canvas-background Fixed composition/stage 및 Auto 내부 viewport의 독립 배경. 위 P11.5 절의 기존 입력 fallback 유지
bg-surface, bg-content, bg-subtle, bg-brand 캔버스·내용·보조 배경·브랜드 배경. Fixed readOnly의 bg-content 미지정 기본값은 기존 #F5F8FB
fg-primary, fg-secondary 일반 텍스트 / 보조 텍스트
stroke-default, stroke-input-default, stroke-input-focus 일반 Border / 입력 Border / Focus
accent-color, on-accent, action-primary, on-action-primary 기존 공통 강조·액션 색
company-{id} Auto 회사 제목과 해당 직원의 소속 회사 강조선. 소속이 부모와 다르면 직원 소속색을 사용
company-{id}-secondary-{n} 기존 보조색 alias 입력. {n}=1은 hanmac/saman/jangheon-industry/jangheon/ptc/halla, {n}=2는 ptc만 있다. baron에는 보조색 입력이 없다. 현재 주요 제목·카드에는 primary만 사용
group-title-background, group-title-color Fixed·Auto 그룹 제목 배경(단색/gradient), 글자. 배경 미지정 시 부서/회사색
group-border Fixed 일반 조직 카드의 상단 Border. Auto에는 기본 그룹 외곽 Border가 없음
card-background, card-color, card-position-color, card-border, card-accent CSS 카드 배경·이름/텍스트·Fixed 직위·Border·강조선. Fixed 부서별 배경과 일반 fallback 카드의 텍스트 입력은 공통 값보다 우선한다. SVG/compact 카드의 HTML 이름은 공통 card-color를 사용한다. 직원 카드 Border는 공통 card-border가 부서 Border보다 우선하며 상단 강조선은 card-accent가 우선한다. SVG 이미지 내부 색은 바뀌지 않는다
leader-card-background, top-leader-card-background Auto 그룹장 / 대표자 카드의 별도 배경. 미지정 시 공통 카드 배경
card-hover-background, card-focus-color Auto 카드 Hover 배경 / Core 직원 버튼 Focus 색(Fixed에도 Focus 적용)
expanded-line-color Auto CSS pseudo선과 SVG 조직·직원 연결선. 기존 기본 #738399
fixed-line-color Fixed 엔진 연결선, Workdesk overlay의 실제 SVG path/arrowhead. PNG 화살표 제외
gradient, border, department-color 기존 Fixed 공통 부서 gradient·Border·제목 글자색
{department}-gradient, {department}-border, {department}-department-color, {department}-card-background, {department}-card-color Fixed 6개 부서별 override. 기존 측정 gradient·기본값 유지
status-title-background, status-title-color, status-title-border 파견·휴직 요약 제목의 공통 색. 기존 회색 gradient, readOnly 흰 글자 유지
status-card-gradient, status-card-border, status-card-color, status-position-color Fixed 파견·휴직 요약 카드의 공통 gradient·Border·이름·직위. 기존 회색 기본값
dispatched-title-background, on-leave-title-background 파견 / 휴직 제목의 독립 gradient, 공통보다 우선
dispatched-card-gradient, dispatched-card-border, dispatched-card-color 파견 카드 독립 입력. Fixed 요약·Auto 상태 직원 카드에 적용
on-leave-card-gradient, on-leave-card-border, on-leave-card-color 휴직 카드 독립 입력. Fixed 요약·Auto 상태 직원 카드에 적용
dialog-background, dialog-color, dialog-border Core native 팝업 배경·글자·Border
dialog-close-color, dialog-close-hover-background, dialog-focus-color Core 닫기 버튼 기본·Hover·Focus. 미지정 시 기존 HM 호스트 값/기본 fallback

회사 {id}와 기본색은 hanmac #CB2E28, saman #F08300, jangheon-industry #4580A6, jangheon #1D418D, ptc #84338E, halla #0068B7, baron #0C367F다. 등록된 ID와 기존 프리셋은 그대로다. Fixed {department}는 business-management, business-planning, talent-growth, integrated-system, design-planning, solution-sales다. 회사색 override로 Fixed 부서색을 바꾸지 않는다.

:host {
  --hm-orgchart-company-saman: #385ca8; /* Auto 회사 제목·소속 카드 강조선 */
  --hm-orgchart-business-planning-gradient: linear-gradient(180deg, #385ca8, #23417f);
  --hm-orgchart-business-planning-border: #385ca8; /* Fixed 부서: 별도 설정 */
  --hm-orgchart-dispatched-card-gradient: linear-gradient(180deg, #e9f9f0, #a9d8bd);
  --hm-orgchart-dispatched-card-border: #278857;
  --hm-orgchart-on-leave-card-gradient: linear-gradient(180deg, #f7efff, #d6bbeb);
  --hm-orgchart-dialog-background: #eef2ff;
  --hm-orgchart-dialog-focus-color: #8958ba;
}

색상 입력에는 유효한 CSS color를 사용한다. *-background는 적용되는 CSS 속성에 맞는 배경 표현식을, *-gradient는 linear-gradient(...) 같은 CSS image 표현식을 사용한다(Fixed 요약 카드는 background-image에 적용). CSS 파일만으로 동작하며 소스를 수정하거나 JS 색상 설정을 추가할 필요가 없다. 그룹 공통 배경을 설정하면 회사별 제목 배경보다 우선하므로 회사별 배경을 유지하려면 공통 배경을 생략한다. 상태 카드도 Auto의 회사 강조선을 유지한다.

런타임 변경과 기본 테마 복원

호스트 변수 class/값을 바꾸면 이미 mount된 차트에 반영되며 데이터·Zoom·Pan을 재생성하지 않는다. 서버의 CSS 파일 값을 바꾼 뒤 기존 link는 브라우저 캐시에 따라 다시 요청되지 않을 수 있으므로 CSS link를 갱신하거나 새 URL로 mount한다. React는 stylesheetUrls.core URL 변경 시 기존 lifecycle로 remount한다. Vanilla는 기존 handle을 destroy()한 뒤 새 CSS URL로 createOrgChart()·mount()한다. 새 테마 변경 API는 추가하지 않았다.

복원은 호스트 테마 class/link/소비자 inline 입력을 제거하고 Core URL을 기본 @hm-design/org-chart/style.css?url 또는 /assets/org-chart/style.css로 되돌린다. 일부 값만 복원하려면 외부 파일의 해당 선언을 삭제한다. resolved-*에 unset을 주는 방식은 사용하지 않는다.

HM Dialog와 Fixed 이미지 한계

React /hm-dialog의 employeePopup="hm-dialog"는 Shadow 밖의 HM React Dialog다. 위 dialog-* 변수는 Core 팝업에만 적용된다. HM Dialog에는 기존 @hm-design/ui/theme.css와 @hm-design/ui/theme의 applyRuntimeTheme/resetRuntimeTheme를 사용한다(Runtime Theme 가이드). Portal은 DOM 경계를 따르므로 차트 컨테이너에만 적용한 Scoped Theme는 body Portal로 전달되지 않는다. 같은 팔레트를 원하면 HM Global Runtime Theme 입력과 OrgChart CSS 입력에 각각 연결한다. 자동 동기화나 HM Dialog 디자인 변경은 하지 않는다.

Fixed에서 회사 로고 PNG, cards/*.svg의 대표자·6개 부서 직원/그룹장 카드 이미지, workdesk/Group.svg, bg_sky.png, bg_main.png, Frame2@3x.png, 1-8-4x-1@2x.png, team-arrow-*.png는 이미지 내부 픽셀 색상을 CSS 변수로 바꿀 수 없다. 카드 바깥의 Focus, CSS 제목·파견/휴직·배경과 실제 inline SVG 연결 path는 변경할 수 있다. 카드 CSS 배경을 바꿔도 SVG 이미지의 불투명 영역은 그대로다. 자산 재작성·필터 착색·이미지 교체는 하지 않았다.

검증 범위·Guard 원본 상태는 P11 결과 보고서를 참고한다.

1. 설치·패키지·실행 환경

  • @hm-design/org-chart@0.1.0: 데이터 타입·Validator·JSON DataSource·집계·이동 함수·DOM 렌더러. React 없이 사용한다. 내부 엔진 의존성은 orgchart@6.0.0이며 소비자가 엔진을 직접 조작하지 않는다.
  • @hm-design/org-chart-react@0.1.0: 위 Core에 의존하는 React lifecycle adapter. 조직 배치·카드·데이터 검증을 다시 구현하지 않는다. React peer는 ^19.0.0, HM Dialog 선택에 사용하는 @hm-design/ui@0.1.0 peer는 optional이다.
  • 저장소는 npm workspaces다. 공유 방식은 공식 Gitea 소스 연결이며 Git Clone 후 의존성 설치·빌드를 별도로 수행한다. npm Registry Publish와 ZIP 배포는 현재 공유 방식에 포함하지 않는다.
  • Core 렌더링은 DOM·Shadow DOM·ResizeObserver·native dialog를 사용하는 브라우저 작업이다. SSR 중에는 mount하지 않는다. React 호스트도 client 경계에서 렌더링한다.

Feature 브랜치 Git Clone

Git, Node.js와 npm, 공식 Gitea 및 npm 레지스트리 접근이 필요하다. Node.js 조건은 ^20.19.0 || >=22.12.0이며 Node 21은 제외한다. P10.3 실행 환경은 Node 22.23.2 / npm 10.9.8이다. 기존 폴더를 덮어쓰지 않고 새 경로에서 실행한다. ZIP·별도 설치 파일·다른 작업 폴더의 node_modules/dist 복사는 필요하지 않다.

git clone --branch feature/orgchart-package --single-branch https://gitea.hmac.kr/b25027/HMDesign.git HMDesign-OrgChart
cd HMDesign-OrgChart
git branch --show-current
git rev-parse HEAD
# 검증 기준 고정: 기존 변경이 없는 새 Clone에서 실행한다.
git checkout --detach d56d587fd20316f19554105f33c200060394962b
git rev-parse HEAD
node --version
npm.cmd --version
npm.cmd ci
npm.cmd run build:packages
npm.cmd run typecheck --ignore-scripts
npm.cmd run test:org-chart
npm.cmd run build --ignore-scripts

--ignore-scripts는 위에서 완료한 package build를 pretypecheck/prebuild에서 반복하지 않기 위한 옵션이다. 이 위치에서 Typecheck/Build 본문은 실제로 실행된다. 루트 설치는 Storybook/HM UI를 포함하는 개발 workspace 설치이며 Core 소비자의 최소 의존성을 의미하지 않는다.

Git으로 받은 소스만으로 의존성 설치나 빌드가 완료되는 것은 아니다. npm.cmd ci는 해당 Commit의 lockfile을 사용하며 npm.cmd run build:packages가 공식 workspace 패키지 빌드 명령이다. 위 타입·테스트·앱 빌드 명령은 소비 환경 검증용 안내이며 P10.7 문서 작업에서 실행한 결과가 아니다.

버전 고정과 업데이트

  • 브랜치 Clone은 이동하는 브랜치의 HEAD를 받는다. 재현 가능한 소비 버전은 전체 Commit SHA로 고정하고 git rev-parse HEAD 결과를 소비 프로젝트 기록에 남긴다. 위 detached checkout은 브랜치 포인터를 변경하지 않는다.
  • 업데이트 시 기존 로컬 변경을 먼저 확인·보존하고 git fetch origin feature/orgchart-package로 후보를 가져온다. 새 승인/검증 SHA를 확인한 뒤 git checkout --detach <새로 확인한 전체 SHA>로 바꾼다. 예시 자리표시자를 그대로 실행하지 않는다.
  • Commit을 바꾼 뒤 npm.cmd ci와 npm.cmd run build:packages를 수행하고 호스트 타입·빌드·핵심 동작을 확인한다. 같은 0.1.0 package version만으로 두 소스 버전이 동일하다고 판단하지 않는다.
  • 기존 프로젝트에는 필요에 따라 Git Submodule 또는 Subtree로 소스를 연결할 수 있다. Submodule은 부모 저장소의 gitlink Commit, Subtree는 가져온 upstream Commit을 기록한다. 두 방식도 의존성 설치·빌드·호스트 연결은 별도다. 기존 UI Submodule 사용 사례를 OrgChart Submodule/Subtree 통합 검증 완료로 표현하지 않는다. OrgChart의 두 통합 방식은 NOT VERIFIED다.

Vanilla 예제는 아래 build 명령 후 dev:org-chart-example로 실행한다. React 예제는 package build 후 build:org-chart-react-example, dev:org-chart-react-example을 사용한다. HM Dialog Reference는 npm.cmd run storybook의 Expanded OrgChart에서 확인한다. 포트가 사용 중이면 기존 서버를 재사용한 결과를 독립 Clone 성공으로 보고하지 않는다.

저장소의 설치 절차를 완료한 환경에서 사용하는 명령이다. Windows에서는 npm.cmd를 사용한다.

npm.cmd run build:org-chart
npm.cmd run build:org-chart-react
npm.cmd run build:org-chart-example
npm.cmd run dev:org-chart-example
# 또는 React 예제 서버
npm.cmd run dev:org-chart-react-example

위 명령을 실행하면 기존 예제/산출물을 갱신할 수 있다. 병렬 문서 작업의 검증 명령으로 실행하지 않는다. 실행 위치·포트는 Vanilla 예제 서버, React Vite 설정을 확인하고 기존 서버와 충돌시키지 않는다.

외부 프로젝트 최소 설치

외부 호스트의 최소 의존성을 검증할 때는 Clone한 소스에서 직접 생성한 npm pack 산출물을 로컬 설치한다. tarball을 별도 전달하거나 Registry publish를 요구하지 않는다. 위 package build가 완료된 Clone 루트에서 다음을 실행한 뒤 아래 설치 경로를 생성된 파일 위치로 바꾼다.

New-Item -ItemType Directory -Path test-results/local-pack -Force
npm.cmd pack -w @hm-design/org-chart --ignore-scripts --pack-destination test-results/local-pack
npm.cmd pack -w @hm-design/org-chart-react --ignore-scripts --pack-destination test-results/local-pack
npm.cmd pack -w @hm-design/ui --ignore-scripts --pack-destination test-results/local-pack

생성물은 Git ignored 로컬 검증 자료이며 Git으로 받은 소스에서 소비자가 만드는 설치 artifact다. tarball/ZIP을 별도 배포하거나 Registry에 게시하는 절차가 아니다. A/B/C는 별도 호스트 폴더에서 아래처럼 설치한다.

# A: Core + Vanilla. React/HMDesign UI 없이 설치한다.
npm.cmd install ./hm-design-org-chart-0.1.0.tgz
# B: Core + React Wrapper. HMDesign UI/Theme/Tailwind 없이 설치한다.
npm.cmd install ./hm-design-org-chart-0.1.0.tgz ./hm-design-org-chart-react-0.1.0.tgz react@19.3.0 react-dom@19.3.0
# C: HM Dialog를 명시적으로 사용할 때만 HMDesign UI와 해당 peer를 추가한다.
npm.cmd install ./hm-design-ui-0.1.0.tgz

Core와 Wrapper의 package.json에서 실제 dependency/peer를 확인한다. C의 HM Theme/Tailwind 연결은 호스트가 제공하며 /hm-dialog가 CSS를 자동 import하지 않는다. 기본 Entry는 HM UI runtime/type 의존 그래프를 포함하지 않는다. 설치 여부로 popup을 자동 전환하지 않는다.

공개 Export와 CSS

Core package의 exports는 ., ./style.css, ./engine.css, ./browser다.

  • 주 진입점: createOrgChart, JsonOrgChartDataSource, OrgChartSourceError, validateOrgChartDataset, isSafeImageUrl, normalizeOrgChartDataset, calculateSummary, snapshotOrgChartDataset.
  • 색상: companyColorPresets, getCompanyColorPreset; 타입: CompanyId, CompanyColorPreset.
  • 가상 예제: createOrgChartExampleDataset 및 관련 타입.
  • 이동: createOrgChartMoveValidator, validateOrgChartMove, applyOrgChartMove 및 관련 타입.
  • 독립 모듈: createOrgChartTree, createPositionSummary, createAffiliationSummary, createStatusSummary. 이들은 Full Composition의 Auto 기능 전체를 제공하는 별도 React 컴포넌트가 아니다.
  • 데이터·옵션·Handle 타입은 같은 주 진입점에서 가져온다. 내부 src/dist 경로를 소비 코드에서 import하지 않는다.

React 기본 @hm-design/org-chart-react의 runtime export는 OrgChart 하나이며 HMDesign UI가 필요 없다. 선택 Entry @hm-design/org-chart-react/hm-dialog는 OrgChart와 HMEmployeeDialog를 export한다. 두 Entry 모두 OrgChartProps, OrgChartEmployeeSelection, OrgChartEmployeePopupProps와 Core 공개 타입을 type-only로 export한다. JsonOrgChartDataSource 같은 runtime helper는 Core에서 import한다. 내부 src/dist Import는 사용하지 않는다.

stylesheetUrls: { engine, core }는 필수다. Core가 자기 Shadow DOM에 engine CSS → core CSS 순으로 link를 넣는다. document에 CSS를 import하는 것만으로 내부 카드가 스타일링되지는 않는다. 아래 ?url은 Vite 전용이며 다른 bundler에서는 공개 CSS를 실제 배포 URL로 해석해 전달한다.

React 없는 일반 HTML도 공개 browser bundle을 HTTP 서버로 배포해 사용할 수 있다. ./browser는 ESM named import용이 아니라 build가 생성하는 IIFE HMOrgChart 전역 bundle이다. 이 가이드의 Vanilla TypeScript 예제는 주 ESM 진입점을 사용한다. URL JSON은 HTTP(S)/상대 경로를 사용하며 file:// fetch는 지원하지 않는다.

근거: Core manifest, Core exports, Data exports, React manifest, React exports, Core build.

2. 빠른 시작용 데이터 — 예제 A / B / E

다음은 가상 데이터 예제다. data.ts로 저장한다. 예제의 회사·분기 수를 제품 기본값으로 사용하지 않는다. A는 1회사·2본부·본부별 2팀·총 7명(대표 1명 + 본부장 2명 + 팀 직원 4명)이다. B는 회사별 독립 Root 두 개와 회사별 컬러를 보여준다. E는 동일한 singleCompany를 React와 Vanilla에서 사용한다.

업무 명칭인 본부를 이 예제에서는 office로 대응한다. Contract에 headquarters enum을 새로 추가하지 않는다.

// data.ts
import {
  getCompanyColorPreset, validateOrgChartDataset,
  type CompanyId, type Employee, type Organization, type OrgChartDataset,
} from '@hm-design/org-chart';

export function makeCompanyDataset(
  companyId: CompanyId, teamCounts: readonly number[],
): OrgChartDataset {
  if (teamCounts.some(count => !Number.isSafeInteger(count) || count < 0)) {
    throw new Error('팀 수는 0 이상의 정수여야 합니다.');
  }
  const name = getCompanyColorPreset(companyId).companyName;
  const rootId = companyId + '-root';
  const organizations: Organization[] = [];
  const employees: Employee[] = [];
  const addEmployee = (id: string, organizationId: string, position: string) => {
    employees.push({
      id, organizationId, affiliationId: companyId, name: '가상 ' + id,
      position, jobTitle: null, profileImage: null, workStatus: 'active',
    });
  };
  addEmployee(companyId + '-ceo', rootId, '대표이사');
  organizations.push({
    id: rootId, companyId, parentId: null, name, type: 'root',
    leaderId: companyId + '-ceo', displayOrder: 0,
  });
  teamCounts.forEach((count, index) => {
    const officeId = companyId + '-office-' + (index + 1);
    const leaderId = officeId + '-leader';
    addEmployee(leaderId, officeId, '본부장');
    organizations.push({
      id: officeId, parentId: rootId, name: (index + 1) + '본부',
      type: 'office', leaderId, displayOrder: index,
    });
    for (let team = 0; team < count; team++) {
      const teamId = officeId + '-team-' + (team + 1);
      organizations.push({
        id: teamId, parentId: officeId, name: (team + 1) + '팀',
        type: 'team', leaderId: null, displayOrder: team,
      });
      addEmployee(teamId + '-employee', teamId, '사원');
    }
  });
  const data: OrgChartDataset = {
    schemaVersion: '0.1-draft', organizations, employees,
    affiliations: [{ id: companyId, name }],
    metadata: { asOfDate: '2026-10-13', dataVersion: 'example-1' },
  };
  const checked = validateOrgChartDataset(data);
  if (!checked.valid) throw new Error(JSON.stringify(checked.issues));
  return checked.data;
}

export const singleCompany = makeCompanyDataset('hanmac', [2, 2]);
const secondCompany = makeCompanyDataset('saman', [1]);
export const multiCompany: OrgChartDataset = {
  ...singleCompany,
  organizations: [...singleCompany.organizations, ...secondCompany.organizations],
  employees: [...singleCompany.employees, ...secondCompany.employees],
  affiliations: [...singleCompany.affiliations, ...secondCompany.affiliations],
  metadata: { ...singleCompany.metadata, dataVersion: 'example-multi-1' },
};
const checkedMulti = validateOrgChartDataset(multiCompany);
if (!checkedMulti.valid) throw new Error(JSON.stringify(checkedMulti.issues));

Root의 companyId는 하위 조직이 상속한다.복수 회사는 parentId: null인 별도 Root를 만든다. 회사들을 잇는 가짜 상위 조직을 추가하지 않는다. B는 위 multiCompany를 같은 렌더러에 전달하면 된다.

3. React 최소 사용 — 예제 D / E

Vite React 프로젝트에서 아래 App.tsx, main.tsx, index.html과 앞의 data.ts를 사용한다. 기본 조회용 설정은 dragDropEnabled={false}다. Zoom/Pan/팝업은 별도 설정으로 계속 사용한다.

// App.tsx
import { OrgChart } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';

const source = new JsonOrgChartDataSource(singleCompany);
const stylesheetUrls = { engine: engineUrl, core: coreUrl };
const autoLayout = { type: 'tb' as const, initialVisibleDepth: 'all' as const };

export default function App() {
  return <OrgChart
    dataSource={source} stylesheetUrls={stylesheetUrls}
    layoutMode="auto" displayMode="readOnly" autoLayout={autoLayout}
    dragDropEnabled={false} employeePopup="core"
    onStateChange={state => {
      if (state.status === 'error') console.error(state.issues);
    }}
    onLifecycleError={error => console.error(error)}
  />;
}
// main.tsx
import { createRoot } from 'react-dom/client';
import App from './App';

const container = document.getElementById('root');
if (!container) throw new Error('root 요소가 없습니다.');
const root = createRoot(container);
root.render(<App />);
window.addEventListener('pagehide', () => root.unmount(), { once: true });
<!-- React index.html -->
<!doctype html>
<html lang="ko">
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>OrgChart</title></head>
  <body style="margin:0">
    <div id="root"></div>
    <script type="module" src="/main.tsx"></script>
  </body>
</html>

React가 전용 div와 Core handle의 mount·subscribe·destroy를 소유한다. 소비자가 같은 div에 다른 자식이나 별도 Core 인스턴스를 넣지 않는다. StrictMode도 wrapper가 lifecycle을 관리한다. 공개 ref/imperative handle prop이나 data, title, children prop은 없다.

기본 Entry의 core 모드는 Core CSS로 동작하며 HMDesign UI/Theme/Tailwind를 설치하지 않아도 된다. 실제 tarball을 설치한 React 19.3.0/Vite 8.3.0 환경의 타입·Production Build 검증은 최소 설치 보고서를 참조한다. 다른 bundler·React major의 지원을 이 결과로 확대하지 않는다.

HMDesign Dialog 선택형 연동

기존 HM Dialog 소비자는 Import를 /hm-dialog로 변경하고 employeePopup="hm-dialog"를 유지한다. 기본 Entry에 HM UI가 설치되어 있어도 자동 전환하지 않으며, HM Entry만 Import해도 기본 popup은 Core다. 실제 @hm-design/ui와 호스트의 index.css → hm-theme.css/Tailwind 연결이 필요하다.

// HMChart.tsx
import { OrgChart } from '@hm-design/org-chart-react/hm-dialog';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';

const source = new JsonOrgChartDataSource(singleCompany);
export function HMChart() {
  return <OrgChart dataSource={source}
    stylesheetUrls={{ engine: engineUrl, core: coreUrl }}
    layoutMode="auto" dragDropEnabled={false} employeePopup="hm-dialog" />;
}

외부 Popup Adapter

기본 Entry에 employeePopup="adapter"와 employeePopupAdapter를 함께 전달한다. Adapter는 { selection, onClose }를 받고 표시·닫기·접근성·focus 복귀를 소유한다. selection에는 직원 원본과 context가 있다. 아래 native dialog는 호스트가 소유하는 예시이며 조직도 카드·Core·데이터를 수정하지 않는다.

// CustomChart.tsx
import { useEffect, useRef } from 'react';
import { OrgChart, type OrgChartEmployeePopupProps } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';

function EmployeePopup({ selection, onClose }: OrgChartEmployeePopupProps) {
  const dialog = useRef<HTMLDialogElement>(null);
  useEffect(() => {
    if (!selection) return;
    const element = dialog.current!;
    element.showModal();
    return () => {
      element.close();
      if (selection.context.trigger.isConnected) selection.context.trigger.focus();
    };
  }, [selection]);
  return selection ? <dialog ref={dialog} aria-label="직원 정보"
    onCancel={event => { event.preventDefault(); onClose(); }}>
    <p>{selection.employee.name} · {selection.employee.position}</p>
    <button type="button" onClick={onClose}>닫기</button>
  </dialog> : null;
}
const source = new JsonOrgChartDataSource(singleCompany);
export function CustomChart() {
  return <OrgChart dataSource={source}
    stylesheetUrls={{ engine: engineUrl, core: coreUrl }} layoutMode="auto"
    employeePopup="adapter" employeePopupAdapter={EmployeePopup} />;
}

Adapter 전달만으로는 활성화되지 않는다. 기본 Entry에서 hm-dialog/adapter를 선택하고 Adapter를 제공하지 않으면 Core popup을 유지하고 onLifecycleError로 구성 오류를 알린다. onEmployeeClick이 지정되면 이 선택보다 우선한다. Fixed는 기존 Core popup을 유지하고 외부 popup은 onEmployeeClick으로 연결한다.

근거: Props, Wrapper lifecycle, HM Dialog.

4. Vanilla TypeScript 최소 사용 — 예제 D / E

React가 없는 Vite TypeScript 프로젝트에서 data.ts, 아래 main.ts과 index.html을 사용한다. 반환 cleanup은 페이지/호스트 lifecycle 종료 시 호출한다.

// main.ts
import { createOrgChart, JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';

export async function mountChart(container: HTMLElement): Promise<() => void> {
  const chart = createOrgChart({
    container, dataSource: new JsonOrgChartDataSource(singleCompany),
    stylesheetUrls: { engine: engineUrl, core: coreUrl },
    layoutMode: 'auto', displayMode: 'readOnly', dragDropEnabled: false,
    autoLayout: { type: 'tb', initialVisibleDepth: 'all' },
    onStateChange: state => {
      if (state.status === 'error') console.error(state.issues);
    },
  });
  try {
    await chart.mount();
    const state = chart.getState();
    if (state.status !== 'ready') throw new Error('조직도를 표시할 수 없습니다.');
    // Auto에서 필요할 때 호출: 현재 전체 표시 영역을 맞춘다.
    chart.fitToView();
    return () => chart.destroy();
  } catch (error) {
    chart.destroy();
    throw error;
  }
}

const container = document.getElementById('chart');
if (!container) throw new Error('chart 요소가 없습니다.');
const dispose = await mountChart(container);
window.addEventListener('pagehide', dispose, { once: true });
<!-- Vanilla index.html -->
<!doctype html>
<html lang="ko">
  <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>OrgChart</title></head>
  <body style="margin:0">
    <div id="chart"></div>
    <script type="module" src="/main.ts"></script>
  </body>
</html>

createOrgChart() 자체는 DOM/요청 부작용 없이 handle을 만든다. mount(container?)는 연결된 HTMLElement 하나를 소유하고 첫 load까지 수행한다. 다른 handle이 소유한 컨테이너, 중복 mount, destroy 후 재사용은 거절한다. destroy()는 여러 번 호출해도 안전한 최종 종료이며 다시 사용할 때는 새 handle을 만든다.

Core의 subscribe(listener)는 unsubscribe 함수를 반환하며 이후 상태 변경을 구독한다. getState()는 현재 상태를 읽는다. Auto의 getData()는 숨긴 회사와 로컬 이동을 포함한 현재 immutable 데이터, getSelectedCompanies()/getAutoLayout()/getInteractions()/getDragDropEnabled()는 현재 옵션을 조회한다. getData()는 Auto 데이터가 준비되지 않으면 undefined다.

mount/update/reload의 Promise 완료만으로 성공을 판정하지 않는다. Contract·소스·CSS 오류는 reject 대신 getState().status === 'error'/onStateChange로 전달될 수 있다.

5. JSON 데이터 계약

현재 구현 타입 이름은 **OrgChartDataset**다. 과거 데이터 계약 문서의 초안 이름·선택 필드는 현재 타입보다 우선하지 않는다. JSON 문자열을 직접 JsonOrgChartDataSource에 전달하면 URL로 해석된다. 문자열 JSON은 먼저 JSON.parse(text)하고 Validator를 통과시킨다.

필수·선택 필드

  • Dataset 필수: schemaVersion: '0.1-draft', organizations: Organization[], employees: Employee[], affiliations: Affiliation[], metadata: Metadata.
  • Dataset 선택: relations?: OrganizationRelation[], presentation?: PresentationConfig.
  • Organization 필수: id: string, parentId: string | null, name: string, type: 'root' | 'division' | 'office' | 'div' | 'cell' | 'team', leaderId: string | null, displayOrder: number(0 이상 안전한 정수). 선택: companyId?: string.
  • Employee는 모든 필드 필수: id, organizationId, affiliationId, name, position은 string; jobTitle, profileImage는 string 또는 null; workStatus는 active | dispatched | onLeave.
  • Affiliation 필수: id: string, name: string. 별도 Company[]는 없다. Auto 회사 식별은 Organization.companyId와 등록된 회사 프리셋을 사용하며 직원의 관계사/법인 소속은 Employee.affiliationId로 구분한다.
  • Metadata 필수: 실제 달력 날짜인 asOfDate: 'YYYY-MM-DD', 비어 있지 않은 dataVersion: string. 선택: schemaVersion?: '0.1-draft'(최상위와 같아야 함), updatedAt?: string | null(timezone 포함 RFC3339), updateNote?: string | null.
  • Relation은 id, sourceOrganizationId, targetOrganizationId, type: 'cooperation' | 'support'가 필수다. 추가 관계는 검증/보존되지만 기본 계층 연결선으로 렌더링되지 않는다.
  • Presentation을 넣으면 title: string, description: string | null, hubText: string | null, organizationOrder: string[], groups의 showEmployees/showSummary/showDispatched/showOnLeave/showAffiliations: boolean 모두를 넣는다. Presentation 생략은 현재 heading 제거 옵션이 아니다.

nullable 필드는 생략하거나 빈 문자열로 대신하지 않는다. 필수 string은 공백뿐인 값이 거절된다. Validator는 trim으로 원본을 고치지 않는다. Contract에 없는 키도 UNKNOWN_FIELD다. 원본에 추가 정보가 있다면 별도 자료에 보존하고 Contract용 변환본을 만든다.

최소 JSON: 회사 → 본부 → 팀 → 직원

{
  "schemaVersion": "0.1-draft",
  "organizations": [
    { "id": "company-root", "companyId": "saman", "parentId": null, "name": "삼안", "type": "root", "leaderId": null, "displayOrder": 0 },
    { "id": "hq-1", "parentId": "company-root", "name": "1본부", "type": "office", "leaderId": null, "displayOrder": 0 },
    { "id": "team-1", "parentId": "hq-1", "name": "1팀", "type": "team", "leaderId": "employee-1", "displayOrder": 0 }
  ],
  "employees": [
    { "id": "employee-1", "organizationId": "team-1", "affiliationId": "saman", "name": "가상직원", "position": "차장", "jobTitle": "팀장", "profileImage": null, "workStatus": "active" }
  ],
  "affiliations": [{ "id": "saman", "name": "삼안" }],
  "metadata": { "asOfDate": "2026-10-13", "dataVersion": "minimal-1" }
}

leaderId는 해당 조직에 소속된 Employee.id를 참조한다. 위 팀장도 employees에 한 번만 있다. 조직장 별도 레코드를 추가해 총원을 두 번 세지 않는다. 직원은 한 organizationId로 직접 소속되며 이름으로 관계를 연결하지 않는다. ID 중복 검사는 collection 내부 기준이고 서로 다른 collection의 같은 문자열은 현재 Validator가 거절하지 않지만, 호스트 변환 단계에서는 엔티티 prefix로 구분하기를 권장한다.

profileImage는 HTTP(S) 또는 안전한 상대 URL이다. null 또는 이미지 load 실패 시 기존 사람 SVG Placeholder로 전환한다. credentials·data URL·javascript·protocol-relative URL을 이미지 API로 우회하지 않는다. 직위(position)와 직책(jobTitle)은 별개이며 연락처 같은 미지원 정보를 합성하지 않는다.

깊은 JSON: 회사 → 부문 → 본부 → 부서 → 팀 → 직원

현재 Auto의 트리는 enum 순서에 종속되지 않고 실제 parentId 관계를 따른다. 다음은 5개 조직 계층 + 직원 카드 예제다. 본부=office, 부서=div 대응은 이 예제의 변환 선택이며 일반 기업 의미를 강제하지 않는다.

{
  "schemaVersion": "0.1-draft",
  "organizations": [
    { "id": "deep-root", "companyId": "hanmac", "parentId": null, "name": "한맥기술", "type": "root", "leaderId": null, "displayOrder": 0 },
    { "id": "division-1", "parentId": "deep-root", "name": "기술부문", "type": "division", "leaderId": null, "displayOrder": 0 },
    { "id": "hq-1", "parentId": "division-1", "name": "기술본부", "type": "office", "leaderId": null, "displayOrder": 0 },
    { "id": "department-1", "parentId": "hq-1", "name": "설계부서", "type": "div", "leaderId": null, "displayOrder": 0 },
    { "id": "team-1", "parentId": "department-1", "name": "설계팀", "type": "team", "leaderId": null, "displayOrder": 0 }
  ],
  "employees": [
    { "id": "deep-employee-1", "organizationId": "team-1", "affiliationId": "hanmac", "name": "가상직원", "position": "사원", "jobTitle": null, "profileImage": null, "workStatus": "active" }
  ],
  "affiliations": [{ "id": "hanmac", "name": "한맥기술" }],
  "metadata": { "asOfDate": "2026-10-13", "dataVersion": "deep-1" }
}

임의의 최대 Depth를 보장하는 API는 없다. 소스는 가변 계층을 처리하지만 극단적으로 깊거나 큰 입력의 렌더링·성능은 실제 브라우저에서 별도로 확인한다.

근거: 실제 타입, Validator, DataSource, 정규화, 집계.

6. Fixed / Auto 선택과 레이아웃

Fixed — 기존 Workdesk 디자인 구성

layoutMode: 'fixed'가 생략 시 기본이다. displayMode: 'interactive' | 'readOnly'와 별도 설정이다.

  • Fixed readOnly는 기존 승인 SVG 카드·대표자·허브·요약과 Workdesk 구성/비례 축소를 사용한다. 기준 캔버스는 1920×914.5, 축 X=935다.
  • 측정된 배치는 business-management/business-planning/talent-growth/integrated-system/design-planning/solution-sales ID의 기존 열 구성에 맞는다. 상위 열이 매핑되지 않으면 일반 중앙 flow로 돌아가며 임의 데이터에 승인된 좌표 배치를 보장하지 않는다. 데이터의 ID/parentId를 디자인 매핑에 맞추려고 바꾸지 않는다.
  • readOnly는 전체 펼침이며 Fixed interactive는 기존 접기 동작을 제공한다. Auto용 TB/LR/Hybrid·회사 선택·Drag & Drop·Zoom/Pan Handle은 Fixed의 새로운 기능이 아니다.
  • 표시 데이터 제약: employee-card.ts는 Workdesk 표현 카드의 이름을 홍 길 동으로 표시하고 Fixed 대표자 카드는 대표이사 직위를 표시하는 경로가 있다. 원본 Employee는 유지되지만 화면 표시가 원본 인명과 일치하지 않을 수 있다. 실제 사용자 데이터 표시에는 이를 보고하고 Auto를 검토한다. 이를 이번 문서 작업에서 수정하지 않는다.
  • readOnly가 제목·기준일·요약까지 제거하는 Chart Only prop은 아니다. Chart Only는 Auto 전용 presentationMode: 'chart-only'이며 Fixed에서는 거부된다(Chart Only 공개 API).

Auto / Expanded — 데이터 기반 가변 조직도

layoutMode: 'auto'는 회사별 독립 Root와 전체 관계를 사용한다. Auto의 회사별 컬러·접기·팝업·Zoom/Pan·이동은 Core 구현을 공유한다.

  • autoLayout.type: tb(기본, 위→아래), lr(왼쪽→오른쪽), hybrid(지정 Depth부터 세로 목록). 현재 기준 소스는 세 유형을 구현한다.
  • hybridStartDepth?: 2 | 3 | 4: 기본 3, global Hybrid용. Root 조직=Depth 1이며 직원 카드를 조직 Depth로 세지 않는다.
  • initialVisibleDepth?: 1 | 2 | 3 | 'all': 기본 all. 해당 Depth부터 그룹을 접는 초기 정책이고 조직장은 남는다. 값을 변경하면 접힘 상태를 초기 정책으로 재설정한다. 다른 레이아웃 변경은 유효한 ID의 접힘 상태를 유지한다.
  • hybridOrganizationIds?: readonly string[]: TB에서 지정 조직의 자손만 세로 배치. LR/global Hybrid와 동시 사용은 거절한다. 전환 전에 빈 배열로 해제한다.
  • interactions: { collapse?, zoom?, pan? }: 각각 기본 true, 서로 독립이다. Compact 레이아웃, 임의 layoutConfig, 엔진 방향 옵션 전달, 공개 collapse setter는 없다.
  • presentationMode: 'default'에서 initialView를 생략하면 초기 배율 1·Bounds의 가로 중앙·상단 여유 배치다. initialView: 'fit' 또는 Chart Only 기본값은 전체 Bounds를 맞추고 가로·세로 중앙에 배치한다. 사용자가 Pan/Zoom한 뒤에는 viewport를 보존한다.
  • 명시적 Fit은 표시된 Bounds를 맞추고 가로·세로 중앙에 둔다. 큰 입력은 읽기 어려울 만큼 축소될 수 있어 initial Depth 또는 확대를 함께 검증한다. Fit은 수동 zoom.min보다 작아질 수 있다.
  • Auto unknown 회사 프리셋은 거절한다. 회사 선택 undefined=전체, []=명시적 빈 표시. 회사 숨김/복귀는 원본 데이터나 완료된 이동을 삭제하지 않는다.
  • companyTopNodes는 회사 ID별 employeeId/title/name/profileImage/organizationName/display 표시 옵션이다. 기본은 Root.leaderId이며 직원은 해당 회사 Root 소속이어야 한다. employeeId: null은 상단 카드 배정을 제거한다. display는 card | titleOnly | hidden. 표시 override는 원본 직원/팝업 identity를 바꾸지 않는다.

Vanilla Auto에서 설정 변경은 기존 handle로 한다.

import type { OrgChartHandle } from '@hm-design/org-chart';

export async function configureLayout(chart: OrgChartHandle) {
  await chart.setAutoLayout({ hybridOrganizationIds: [] });
  await chart.setAutoLayout({ type: 'lr', initialVisibleDepth: 2 });
  await chart.setInteractions({ collapse: true, zoom: true, pan: true });
  await chart.setSelectedCompanies(['hanmac', 'saman']);
  // chart.setSelectedCompanies()는 전체, []는 빈 선택.
}

setter는 mount 후 Auto에서 호출한다. Vanilla의 생성 옵션인 layoutMode/displayMode/companyTopNodes/employeeConnections/zoom/organizationLinks/presentationMode/initialView/wheelZoom을 바꾸려면 destroy 후 새 handle을 만든다.

React에서는 대응 prop으로 변경한다. dataSource 변경은 reload, autoLayout/interactions/dragDropEnabled/selectedCompanyIds와 Auto 팝업 변경은 기존 handle을 갱신한다. aggregationPolicy·CSS URL·layoutMode·displayMode·companyId·companyTopNodes·employeeConnections·zoom·organizationLinks·presentationMode·initialView·wheelZoom 값 변경은 handle을 재생성한다. 객체 옵션은 값으로 비교하므로 같은 값의 inline 객체 자체가 재마운트를 일으키지는 않는다. setAutoLayout는 patch merge이며 React에서 새 autoLayout 객체에 넣지 않은 필드가 매번 Core 기본으로 초기화된다고 가정하지 않는다.

근거: Core Handle/옵션, 배치 정책, 회사 검증, Workdesk 좌표, Fixed 구성, Viewport.

7. 디자인·회사별 컬러·카드 재사용

현재 Auto의 getCompanyColorPreset(id).primary 값:

  • hanmac 한맥기술: #CB2E28
  • saman 삼안: #F08300
  • jangheon-industry 장헌산업: #4580A6
  • jangheon 장헌: #1D418D
  • ptc 피티씨: #84338E
  • halla 한라산업개발: #0068B7
  • baron 바론컨설턴트: #0C367F

색상은 조회 helper와 Core에 맡기고 새 카드 CSS에 복사하지 않는다. Root.companyId → 자손 상속 → legacy Root leader의 등록 affiliation → 생성 옵션 companyId(기본 hanmac)의 순으로 회사가 결정된다. 새 데이터는 Root.companyId를 명시한다. 조직 회사와 직원 affiliation은 다른 값일 수 있다. 카드 강조는 등록된 affiliation 프리셋을 우선하고 미등록 affiliation은 조직 회사 색을 사용한다. 자손의 companyId가 부모 회사와 다르면 Auto에서 COMPANY_HIERARCHY다.

Fixed는 별도의 기존 부서 ID별 스타일·SVG를 사용한다. 회사 컬러 옵션을 바꿔 Fixed 전체를 재색칠하는 API는 없다.

카드와 글자

  • Fixed의 기존 대표자 SVG는 146×74, 1Depth 조직장 SVG는 146×68, 더 깊은 조직장/일반 직원의 small SVG는 108×52다. 등록 부서 자산이 없는 조직은 기존 fallback 카드다. 모든 Fixed 데이터에 SVG 매핑이 있는 것은 아니다.
  • Fixed SVG 직위/이름 기본 크기는 small 15/18px, 대표자 20/24px, 부서장 SVG 직위 18px·이름 24px다. 이름 weight 600, 직위 400이며 전역 서체는 Noto Sans KR 등 기존 fallback을 따른다. 재설계 규격으로 사용하지 않는다.
  • Auto는 SVG 배경 대신 기존 중립 카드와 회사 강조 Border를 쓴다. 일반 직원 폭 114px·최소 높이 54px, 조직장/상단 카드 폭 154px·최소 높이 72px, 사진 36×36, padding 8px, Border 1px, inset 강조 3px, 이름/직위 13/10px다. 긴 텍스트는 실측 높이가 늘 수 있다.
  • Auto의 Root 대표자와 하위 조직장 카드는 같은 leader geometry를 사용한다. 1Depth/2Depth별 새 디자인 prop은 없다. 3글자 한글 이름은 표시 계층에서 띄어 쓰고 직위 공백을 표시상 제거하지만 원본과 callback 데이터는 유지한다.
  • Auto 일반 카드에서 jobTitle·근무 상태를 별도 행으로 출력하지 않는 경로가 있다. 팝업은 원본 jobTitle/workStatus를 보여준다. Fixed의 파견/휴직 보조 카드·요약과 Auto의 직원 카드를 혼동하지 않는다.
  • Fixed SVG의 외형 Border는 자산에 포함되고 Auto Border는 CSS/실제 연결면에 따라 강조한다. 회사 타이틀 텍스트는 현재 CSS에서 흰색이며 특정 색 조합의 대비가 승인됐다는 뜻은 아니다.

연결선·스타일 적용

Auto 전체 트리 방향은 autoLayout.type, 직원 연결 방향은 조직 ID별 employeeConnections: { [id]: 'top' | 'left' | 'right' }다. LR/세로 경로는 실제 endpoint를 측정해 연결면을 정하고 LR 직원 카드의 연결/강조면은 왼쪽이다. TB/Hybrid 조직장은 상단 연결을 사용한다. 직원 방향 옵션을 전체 트리 방향 API로 해석하지 않는다.

Auto 관계선/직원선은 기존 --hm-orgchart-expanded-line-color(기본 #738399)를 공유한다. Fixed 선·회사 Border와 별개다. 기본 선과 기존 SVG/회사 프리셋을 변경하지 않는다.

Core는 Shadow DOM을 사용하므로 호스트의 일반 selector로 내부 카드가 바뀌지 않는다. 기존 공개 CSS 두 개와 Core를 재사용한다. 별도 승인된 스타일 변경이 있을 때만 Expanded 스타일 안내의 실제 변수·스타일 로딩 경로를 확인한다. 변수 이름을 추측하거나 카드 교체 API를 만들지 않는다.

근거: 회사 프리셋, Fixed 부서 자산, 대표자 자산, 직원 카드, 공통 카드 CSS, Auto 카드 CSS, Auto 연결선.

8. 인터랙션·팝업·Drag & Drop

탐색

  • Auto toolbar의 확대/축소는 현재 배율×step 또는 ÷step, 기본 step=1.2, min=0.05, max=3이다. 공개 handle에는 zoomIn/zoomOut이 없으며 zoomTo(scale)를 사용한다.
  • 기본 화면은 Ctrl/⌘ + 휠, Chart Only는 modifier 없이 직접 휠 Zoom을 사용한다(wheelZoom으로 선택 가능). 휠은 커서 중심 확대·축소이며 viewport 초점에서 +/−, 빈 공간 드래그/방향키 Pan을 지원한다. panBy(x,y), getViewportState(): {scale,x,y} | undefined를 제공한다.
  • toolbar의 100% 복원은 배율 1과 위치를 함께 복원한다. zoomTo(1)는 배율 변경이므로 동일한 동작이 아니다. 공개 resetViewport 메서드는 없다.
  • fitToView()는 Auto에서 전체 표시 범위를 맞춘다. Fixed의 위 handle viewport 호출은 Auto 동작을 제공하지 않는다.
  • 그룹 타이틀은 click/Enter/Space로 접기·펼치고 조직장은 남긴다. 부모 재확장 시 유효한 자손 접힘 상태를 유지한다. interactions.collapse=false는 토글만 끈다. 하위 내용 없는 제목은 aria-disabled로 접기 불가이나 이동/키보드 대상일 수 있다.
  • Zoom/Pan을 끄면 해당 조작/API를 막지만 Fit은 독립적으로 가능하다. manual Pan이 OFF여도 키보드로 화면 밖 카드에 도달하면 배율을 유지하고 초점을 화면에 드러낸다.

팝업

Core/Vanilla 기본 직원 카드 click/Enter/Space는 native dialog를 연다. X·ESC로 닫고 Tab 초점을 가두며 원래 카드로 복귀한다. 카드가 사라졌거나 인스턴스가 정리되면 shell/연결 상태에 따라 복귀한다. Core dialog에는 외부 클릭으로 닫는 별도 핸들러가 없다.

React Auto의 employeePopup은 core(기본), hm-dialog, adapter, none이다. HM 모드는 /hm-dialog Entry 또는 명시적으로 주입한 HMEmployeeDialog를 사용한다. HM Dialog·Avatar·Button으로 X·닫기 버튼·ESC·외부 클릭으로 닫으며 onCloseAutoFocus에서 원래/재생성된 카드 또는 shell에 복귀한다. adapter는 호스트의 employeePopupAdapter를 명시적으로 선택한다. none은 기본 팝업/button 의미를 제거하지만 키보드 탐색용 초점은 남긴다. Fixed에서는 employeePopup 선택이 적용되지 않고 기존 Core 팝업을 유지한다.

onEmployeeClick(employee, context)를 지정하면 기본 팝업보다 우선한다. context는 organization, affiliation, trigger: HTMLElement, originalEvent: MouseEvent | KeyboardEvent다. 임의 popup API를 추가하지 않고 호스트가 자신이 만든 UI의 닫기·접근성·focus 복귀를 책임진다.

이동 정책

  • dragDropEnabled는 Core/React 공통으로 기본 false다. displayMode readOnly도 이 옵션을 대체하는 편집 잠금이 아니다.
  • OFF는 포인터 이동·Alt+M·handle.move를 차단하고 EDITING_DISABLED를 반환한다. OFF에서도 독립 설정에 따른 Pan/Zoom/Fit·팝업·접기는 유지한다.
  • ON은 Auto 직원 카드/조직 타이틀 드래그 이동, Alt+M → 방향키 대상 선택 → Enter 확정 / ESC 취소를 지원한다. 빈 공간은 Pan이다.
  • Root, 자신/자손으로 조직 이동, 같은 소속, leaderId 참조를 손상시키는 조직장 이동 및 지정된 상단 직원 이동을 차단한다. 대상 회사 affiliation이 없으면 회사 간 이동도 거절한다.
  • 동일 회사 직원 이동은 organizationId만 바꾸고 affiliationId를 보존한다. 회사 간 직원 이동은 affiliationId도 대상 회사로 변경한다.
  • 그룹 이동은 parentId를 바꾸며 회사 간 이동은 하위 조직 전체 companyId와 소속 직원 affiliationId도 대상 회사로 바꾼다. ID·이름·직위·사진·자손 관계·조직장·정렬은 보존한다.
  • 결과는 현재 Core 인스턴스의 새 immutable snapshot이다. 원본 JSON·DataSource·서버·파일은 수정하지 않고 영구 저장 API도 없다. 새 source/reload는 소스 데이터를 다시 적용한다. UI 회사 선택만으로 이동 snapshot을 초기화하지 않는다.
  • OFF 전환은 진행 중 드래그/키보드 이동을 취소하고 이미 완료한 이동은 남긴다. 설정만 바꿀 때 reload하지 않는다.
import type { OrgChartHandle, OrgChartMoveResult } from '@hm-design/org-chart';

export async function moveEmployee(chart: OrgChartHandle): Promise<OrgChartMoveResult> {
  chart.setDragDropEnabled(true);
  const result = await chart.move({
    kind: 'employee',
    sourceId: 'hanmac-office-1-team-1-employee',
    targetOrganizationId: 'hanmac-office-2-team-1',
  });
  chart.setDragDropEnabled(false);
  if (result.moved) console.log(result.data); // 새 snapshot, 저장은 호스트 별도 책임
  else console.error(result.code, result.message);
  return result;
}

위 예제는 A를 mount한 handle 기준이다. onMove(result)도 성공/실패를 알리며 result.moved로 분기한다. 성공은 data/request/previousOrganizationId, 실패는 code/message다. React 성공 이동은 열린 HM Dialog를 닫는다. 데이터용 순수 applyOrgChartMove는 UI OFF 잠금과 별개인 변환 함수이며 handle.move 대신 호출해 편집 잠금을 우회하지 않는다.

근거: Viewport 이벤트, 편집 이벤트, 이동 정책, Core 팝업, HM 팝업, HM Dialog primitive.

9. 데이터 업데이트 — 예제 C

Vanilla update(input)는 전달 snapshot을 검증해 현재 인스턴스에 적용하고 DataSource 자체를 교체하지 않는다. reload(nextSource?)는 현재/새 source를 load한다. 이후 reload가 이전 source 값을 다시 적용할 수 있다는 차이를 구분한다. 인라인 JsonOrgChartDataSource는 생성 시 값을 캡처하므로 원본 객체를 나중에 바꾸는 방식은 업데이트가 아니다.

import {
  JsonOrgChartDataSource, validateOrgChartDataset, type OrgChartHandle,
} from '@hm-design/org-chart';

export async function updateChart(chart: OrgChartHandle, input: unknown) {
  const checked = validateOrgChartDataset(input);
  if (!checked.valid) throw new Error(JSON.stringify(checked.issues));
  await chart.update(checked.data);
  if (chart.getState().status !== 'ready') throw new Error('업데이트 실패');
}

export async function replaceSource(chart: OrgChartHandle, input: unknown) {
  const checked = validateOrgChartDataset(input);
  if (!checked.valid) throw new Error(JSON.stringify(checked.issues));
  await chart.reload(new JsonOrgChartDataSource(checked.data));
  if (chart.getState().status !== 'ready') throw new Error('소스 변경 실패');
}

React는 dataSource identity가 바뀌면 같은 handle에 reload한다. useMemo/module 상수로 source를 안정적으로 만들고 매 render에서 새 source를 만들지 않는다. 다음은 호스트가 받은 새 데이터 prop으로 갱신하는 별도 React 예제다.

// UpdatingChart.tsx
import { useMemo } from 'react';
import { OrgChart } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource, type OrgChartDataset } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';

const stylesheetUrls = { engine: engineUrl, core: coreUrl };
export function UpdatingChart({ dataset }: { dataset: OrgChartDataset }) {
  const source = useMemo(() => new JsonOrgChartDataSource(dataset), [dataset]);
  return <OrgChart dataSource={source} stylesheetUrls={stylesheetUrls}
    layoutMode="auto" dragDropEnabled={false}
    onStateChange={state => {
      if (state.status === 'error') console.error(state.issues);
    }}
    onLifecycleError={error => console.error(error)} />;
}

새 dataset 객체를 전달한다. 성공한 onMove의 data를 집계용 read model로 저장하는 것과 바로 새 source로 넣어 reload하는 것을 구분한다. Core는 최신 요청 revision을 사용해 오래된 load 결과를 무시/취소한다. 재생성 옵션 변경은 viewport/이동의 보존을 보장하지 않는다.

10. AI 코딩 에이전트 적용 절차

  1. AI 페이지 생성 정책에 따라 생성 방식이 미지정이면 Chart Only / Full Page 선택 질문에 대한 답변을 받은 뒤 진행한다. 이미 명시된 방식은 다시 묻지 않는다. 현재 사용자 요청·Git 기준·공개 API를 확인하고 회사/조직 유형·부모 관계·분기 수·직원 수를 정리한다. Root를 포함한 조직 Depth와 직원 수 집계 기준을 구분한다.
  2. 자연어의 가상 데이터를 생성하거나 제공 JSON·CSV·Excel을 Contract로 변환한다. Core가 CSV/XLSX parser나 자연어 생성기를 제공하는 것은 아니다. 외부 변환 결과는 unknown으로 취급한다.
  3. 원본 이름·ID·소속·직위·사진·부모 관계를 보존한다. 조직장과 대표자를 총원에 포함할지 확인하고 employees에 한 번만 생성한다. 미지원 필드와 원본을 별도 보존하며 오류 항목을 삭제해 Validator를 통과시키지 않는다.
  4. validateOrgChartDataset와 요청별 회사·조직·Depth·인원 검사를 수행한다. Auto 회사 프리셋/회사 경계/상단 직원 제약도 확인한다. Validator 성공만으로 Auto 표시가 보장되지 않는다.
  5. 기존 공개 Core/React·카드·컬러·연결선을 재사용해 실제 client 진입점을 만든다. 새로운 엔진·입력 UI·개발 패널을 만들지 않는다.
  6. 구현 완료 후 한 번의 통합 검증으로 핵심 동작·데이터 보존·연결선·가독성·viewport·키보드·필요한 타입/build/Guard를 확인한다. 390/768/1280/1440px 및 요청 크기에서 확인한 실행 URL/캡처와 미검증을 보고한다.

예: 삼안 6본부, 팀 5·2·4·1·3·2=17, 총 500명은 실제 생성 데이터의 organizations와 unique employee 수로 검증한다. 한맥기술 3부문×2본부×4팀과 300명도 같은 절차로 처리한다. 이 가이드의 7명 예제 분배를 재사용하지 않는다.

공개 createOrgChartExampleDataset는 기존 template에서 employeeCount: 20 | 100 | 300, scope total | per-company로 가상 예시를 만드는 helper다. 임의 500명 생성·실제 Excel 변환 API가 아니다. 회사별 모드는 선택 회사마다 N명이며 total 모드는 원래 회사별 분배 후 선택 회사만 필터링한다. 실제 사용자 데이터를 이 helper로 재생성하지 않는다.

Chart Only 정책과 현재 기능의 관계

AI 페이지 생성 정책을 먼저 읽고 AGENTS.md·CLAUDE.md의 연결을 따른다. 생성 방식이 미지정이면 코드 작성 전에 Chart Only / Full Page를 질문하고 답변 후 구현한다. Chart Only를 선택하면 조직도 캔버스만 표시하고 외부 제목·통계·설명·조작 패널을 만들지 않는다. Full Page를 선택하면 기존 HM UI와 OrgChart Core·React Wrapper로 요청된 UI와 전체 레이아웃을 구성한다. 이미 명시된 방식은 다시 묻지 않으며 Fixed·Auto는 생성 방식 선택을 대신하지 않는다.

Chart Only 공개 API (Auto)

Auto(layoutMode: 'auto')에서 presentationMode: 'chart-only'를 지정하면 Core가 흰색 캔버스 + 조직 노드 + 연결선만 렌더링한다. React와 Vanilla는 같은 Core 옵션을 사용한다. 기본값은 기존 화면과 동일하다('default').

옵션 값 기본값 동작
presentationMode 'default' | 'chart-only' 'default' chart-only는 외부 제목·기준일·회사/인원 요약·Zoom/Fit 버튼 바·배율·조작 안내·Drag & Drop 문구·카드형 외곽(회색 패딩·테두리)을 만들지 않는다. 노드 안 조직명·직원 정보는 유지한다. 이동 결과 live status는 화면에서 숨긴 채 보조기술에만 제공한다.
initialView 'center' | 'fit' chart-only 'fit', 그 외 'center' fit은 전체 Forest Bounds(연결선 포함)를 16px 여백 안에 담고 가로·세로 중앙에 둔다. 100%를 넘겨 확대하지 않는다. 측정 전에는 조직도를 숨겨 첫 화면이 튀지 않는다.
wheelZoom 'modifier' | 'direct' chart-only 'direct', 그 외 'modifier' direct는 캔버스 안에서 일반 휠로 포인터 기준 확대·축소한다. 캔버스 밖은 페이지 스크롤을 유지한다. modifier는 기존 Ctrl/⌘ + 휠이다.
// ChartOnly.tsx — 기본 Entry, HMDesign UI 불필요. 앞의 data.ts를 사용한다.
import { OrgChart } from '@hm-design/org-chart-react';
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';

const source = new JsonOrgChartDataSource(singleCompany);
export function ChartOnly() {
  return <OrgChart dataSource={source}
    stylesheetUrls={{ engine: engineUrl, core: coreUrl }}
    layoutMode="auto" presentationMode="chart-only"
    initialView="fit" wheelZoom="direct" dragDropEnabled={false}
    style={{ height: '100vh' }} />;
}
// chart-only.ts — 앞의 data.ts와 호스트가 제공하는 HTMLElement를 사용한다.
import { createOrgChart, JsonOrgChartDataSource } from '@hm-design/org-chart';
import engineUrl from '@hm-design/org-chart/engine.css?url';
import coreUrl from '@hm-design/org-chart/style.css?url';
import { singleCompany } from './data';

export async function mountChartOnly(container: HTMLElement): Promise<() => void> {
  const chart = createOrgChart({
    container, dataSource: new JsonOrgChartDataSource(singleCompany),
    stylesheetUrls: { engine: engineUrl, core: coreUrl },
    layoutMode: 'auto', presentationMode: 'chart-only',
    initialView: 'fit', wheelZoom: 'direct', dragDropEnabled: false,
  });
  try {
    await chart.mount();
    if (chart.getState().status !== 'ready') throw new Error('조직도를 표시할 수 없습니다.');
    return () => chart.destroy();
  } catch (error) {
    chart.destroy();
    throw error;
  }
}
  • 두 예제는 기본값과 같은 initialView/wheelZoom을 명시해 선택 가능한 값을 보여준다. Vanilla 호스트는 높이를 정하고 반환 cleanup을 lifecycle 종료 때 호출한다. /hm-dialog Entry의 Chart Only도 같은 Props를 쓰며 팝업은 employeePopup="hm-dialog"를 명시한다.
  • 캔버스는 컨테이너 높이를 채운다(height: 100%). 컨테이너 높이가 정해지지 않으면 최소 320px이다.
  • 자동 Fit 시점: 최초 렌더링, update()/reload()/React dataSource 교체, 회사 선택·Auto layout 변경. 휠 Zoom·Pan·직원 팝업·그룹 접기/펼치기·Drag & Drop ON/OFF·React 단순 리렌더링에서는 다시 Fit하지 않는다. 사용자가 조작하기 전 캔버스 크기가 바뀌면 다시 Fit하고, 조작한 뒤에는 사용자 뷰를 유지한다.
  • Fit 배율이 zoom.min보다 작을 수 있다(대규모 조직). 이때 휠 축소는 Fit 배율에서 멈추고, 확대는 zoom.max까지 가능하다. 카드 크기·글자 크기는 바꾸지 않는다.
  • 빈 공간 드래그 Pan, 방향키/+/-, 직원 팝업(기본 Core, /hm-dialog 선택형, employeePopupAdapter), 그룹 접기·펼치기, Drag & Drop 기본 OFF는 그대로다. 공개 fitToView()/zoomTo()/panBy() handle도 그대로 동작한다.
  • React Wrapper는 autoLayout·interactions·selectedCompanyIds·zoom 등 객체 옵션을 값으로 비교한다. 같은 값의 inline 객체로 리렌더링해도 다시 마운트하거나 Fit하지 않는다.
  • Fixed는 지원하지 않는다. presentationMode: 'chart-only'와 Fixed(layoutMode 생략 포함)를 함께 쓰면 TypeError를 던진다. 승인된 Workdesk 구성(제목·요약·아트워크·비례 축소)을 임의로 걷어내지 않기 위해서다. Fixed에 Auto와 같은 휠 Zoom/Pan도 없다.
  • 실행 예제와 브라우저 검증: examples/org-chart-chart-only.

P10.2 당시에는 내장 UI와 바깥 회색 영역이 남아 Chart Only가 PARTIAL이었으며 React 초기 Fit 옵션도 없었다. 이 과거 검증 원본은 P10.2 통합 보고서에 보존한다. 이후 위 공개 옵션 3개가 구현됐고 최종 통합 검증은 Auto Chart Only React/Vanilla 51/51 PASS를 기록한다. 과거 PARTIAL을 현재 기능의 미완료 상태로 해석하지 않는다. chartOnly/showToolbar/showHeader라는 prop은 현재도 없으며 정확한 API는 presentationMode다. displayMode: 'readOnly'나 dataset의 presentation 생략은 Chart Only 선택을 대신하지 않는다. createOrgChartTree도 Auto Chart Only의 대체 API가 아니다.

React 없이 생성해 달라는 명시적 요청은 이 가이드의 Vanilla Core 경로로 처리하고 Runtime portability의 명시적 adapter 계약을 확인한다. HM React 화면을 HTML로 모방해 결과라고 제출하지 않는다.

11. 오류·검증·지원 한계

검사와 오류 처리

  • validateOrgChartDataset는 valid=true이면 깊게 freeze한 snapshot을 반환한다. false이면 issues: {code,path,message}[]를 반환한다.
  • 기본 오류: SCHEMA_VERSION, UNKNOWN_FIELD, REQUIRED_VALUE, DUPLICATE_ID, EMPTY_ORGANIZATIONS, INVALID_TYPE/STATUS/RELATION, PARENT_REFERENCE, LEADER_REFERENCE/LEADER_APPROVAL_REQUIRED, ORGANIZATION_REFERENCE, AFFILIATION_REFERENCE, HIERARCHY_CYCLE, RELATION_REFERENCE, PRESENTATION_REFERENCE, UNSAFE_INPUT.
  • organizations=[]는 오류이며 employees=[]는 유효할 수 있다. State 타입의 empty가 실제 empty UI 지원 증거는 아니다.
  • Auto는 추가로 COMPANY_PRESET/COMPANY_HIERARCHY 및 TOP_NODE_CONFIG/TOP_NODE_REFERENCE를 검사한다. 기본 Validator는 회사 프리셋/회사 간 계층 경계를 검사하지 않으므로 두 층을 구분한다.
  • source 오류는 SOURCE_URL/LOAD/PARSE/ABORT/SNAPSHOT, 스타일 오류는 CORE_STYLE 등으로 구별한다. Core state.error와 React onLifecycleError의 책임을 구분한다.
  • calculateSummary(data).raw는 unique 직원·direct/inclusive·position·affiliation·status 집계다. 업무 승인 집계는 aggregationPolicy가 없으면 unavailable(policyRequired)이다. 현재 정책 구현은 includeLeaders=true와 membershipMode=primaryOnly 등 제한을 확인하며 임의 excludeLeaders/uniqueEmployee 지원을 선언하지 않는다.

주요 미지원·별도 확인 대상

  • 자연어→데이터 자동 생성 UI, CSV/XLSX 직접 import, 서버 저장·외부 API·CSV/PDF/이미지 export는 공개 사용 기능이 아니다.
  • 임의 회사 프리셋 등록 setter·카드 교체·Compact·공개 collapse setter·React imperative ref는 없다. Chart Only는 Auto 전용 presentationMode로만 제공하며 Fixed Chart Only는 없다.
  • 제공 데이터의 최대 인원/Depth·모든 브라우저·실제 모바일 터치/스크린리더·외부 fresh install을 이번 소스 분석으로 보장하지 않는다.
  • 기존 P10 PARTIAL/Guard FAIL 10건의 원본 기록은 보존한다. P10 Feature 통합은 Chart Only·초기 Fit·직접 Wheel Zoom, GuardFix의 F3–F7 해결 5건, AI 생성 방식 사전 선택 정책을 포함했고 당시 잔여 FAIL 5건·NOT VERIFIED 9건이었다. 이후 P11의 동일 대상 실제 Guard는 status/staticStatus PASS, FAIL 0건이다. Guard runtimeStatus NOT VERIFIED와 제품 전체 미검증을 계속 구분한다. Cell 디자인·미리보기는 제외한다.
  • P10.3 GuardReview는 별도 Worktree에서 당시 FAIL 10건을 재현한 원본 분석이다. 현재 Auto Chart Only와 P11 외부 CSS 테마는 지원한다. Fixed Chart Only·Fixed SVG/PNG 이미지 내부 테마화는 미지원이다. P11.2 최종 디자인 승인은 Storybook 4개 화면과 Auto Chart Only React/Vanilla의 기본 디자인 APPROVED를 기록하며 접근성·보안 위험 수용과 구분한다. main Merge·Docker 배포는 완료하지 않았다.

이 가이드 예제 검증 범위

아래 검증 상태는 이 문서의 예제를 대상으로 한 별도 결과이며 기존 제품 회귀검사 결과가 아니다.

  • TypeScript PASS: 이 문서에서 추출한 TS/TSX 코드 블록 8개를 설치된 TypeScript 7.0.2의 strict·ES2022·Bundler·react-jsx 설정으로 검사했다. Core/React import는 기준 Worktree의 src 공개 진입점, CSS URL은 기존 Vite client 타입에 연결했다. 새 빌드 산출물을 지원 증거로 사용하지 않았다.
  • 데이터 실행 PASS: 기준 Worktree의 데이터 모듈을 임시 경로에 컴파일해 JSON 2개와 data.ts의 A/B 데이터 2개를 실제 Validator로 확인했다. A=7조직/7명, B=독립 Root 2개/10명, 조직장 중복 없는 집계, 직원 이동 후 원본 소속·총원 보존, 자손으로 그룹 이동 거절, 회사 프리셋 7개 값을 확인했다.
  • Runtime NOT VERIFIED: App/UpdatingChart의 React 렌더링, mountChart의 Vanilla mount, configureLayout, handle 기반 moveEmployee/updateChart/replaceSource, HTML 진입점의 실제 실행은 확인하지 않았다. 타입 검사와 순수 applyOrgChartMove 실행을 이들 브라우저 예제의 실행 PASS로 해석하지 않는다.
  • 실제 React/Vanilla 화면 렌더링·CSS load·인터랙션 시연·시각 QA: NOT VERIFIED.
  • 설치/전체 build/Storybook 회귀·외부 소비 프로젝트 fresh install: NOT VERIFIED.

위 항목은 UsageGuide 원본 문서 작업 당시의 범위다. P10.2 추가 검증: 통합 후 TS/TSX 예제 10개를 현재 package 공개 .d.ts에 연결해 strict·skipLibCheck=false로 확인했다(PASS). 기존 실제 npm pack A/B/C 설치·타입·Build·팝업 검사 PASS는 현재 배포 JS/타입/CSS의 byte 동일성을 확인해 재사용했다. 독립 Chart Only React/Vanilla Build와 위 브라우저 검사는 새로 수행했다. 이 가이드의 HMChart/CustomChart 예제 자체의 브라우저 시연, 전체 회귀·성능·SSR·스크린리더·모바일 touch는 NOT VERIFIED다.

P11.6 추가 검증(2026-10-14): 현재 가이드의 TS/TSX 블록 14개를 깨끗한 빌드의 공개 .d.ts에 연결해 strict·skipLibCheck=false로 검사했다(PASS). 앞 절의 변수를 사용하는 외부 CSS/P11.5 부분 예제 2개에는 명시된 데이터·Import 문맥을 보충했다. JSON 예제 2개는 실제 Validator PASS다. React·Vanilla 배경 독립 설정·테마 제거 복구·Chart Only 커스텀 배경과 새 Storybook 빌드는 추가 브라우저 20/20 PASS다. 예제 각각의 전체 브라우저 실행이나 Guard Runtime PASS를 뜻하지 않는다. 검사 범위·기존 증거 재사용·배포 플랫폼 미검증은 P11.6 정합성 보고서를 따른다.

12. 관련 문서·실제 소스

필요한 부분만 확인한다.

원본 UsageGuide/P10.2의 문서 예제 검증 기록은 위에 보존했다. P10.7에서는 검증 기준 SHA·Gitea Commit 고정 절차·현재 Chart Only 지원 상태와 옵션 설명을 공개 소스와 대조했으며 새 전체 타입 검사·제품 테스트·빌드·브라우저 검사는 수행하지 않았다. P11/P11.1의 후속 검증은 P11 보고서와 P11.1 정합성 검증을 따른다. P11.1은 공개 API·제품 스타일·Story 소스를 변경하지 않는다.