- Shared entry point docs/ai/AI_GUIDE.md (type selection Web / SW / OrgChart / mixed, steps 1-7, package style separation, report format, examples A-D) and AGENTS_SNIPPET.md, copied into every package ai/ folder - New ai/ docs for @hm-design/ui and @hm-design/org-chart (+ org-chart-react pointer): AI_USAGE, COMPONENT_CATALOG, DESIGN_RULES, COMPOSITION_PATTERNS - SW ai/ linked to the shared guide; catalog lists every Props type explicitly - "ai" added to package files of ui, org-chart, org-chart-react - scripts/ai-docs.mjs: sync:ai-docs / check:ai-docs (copies, files, catalog <-> exports, links) - Maintenance guide and verification record under docs/ai
151 lines
18 KiB
Markdown
151 lines
18 KiB
Markdown
# @hm-design/org-chart-react v0.1
|
|
|
|
React 19 lifecycle adapter for `@hm-design/org-chart@0.1.0`. The wrapper delegates organization DOM, engine work, validation, summaries, interactions and Shadow DOM styles to the existing Vanilla Core. Auto can optionally show employee information with the public HM Dialog in a separate React Portal. It has no navigation, page layout, fetch client or separate node design.
|
|
|
|
## Usage
|
|
|
|
```tsx
|
|
import { useMemo } from 'react';
|
|
import { JsonOrgChartDataSource } from '@hm-design/org-chart';
|
|
import { OrgChart } from '@hm-design/org-chart-react';
|
|
import type { OrgChartDataset, OrgChartState } from '@hm-design/org-chart-react';
|
|
import engineCssUrl from '@hm-design/org-chart/engine.css?url';
|
|
import coreCssUrl from '@hm-design/org-chart/style.css?url';
|
|
// Core P1 supplies standalone OrgChart design; no HM Theme/font import needed.
|
|
|
|
const stylesheetUrls = { engine: engineCssUrl, core: coreCssUrl };
|
|
|
|
export function OrganizationContent({ data }: { data: OrgChartDataset }) {
|
|
const dataSource = useMemo(() => new JsonOrgChartDataSource(data), [data]);
|
|
const onStateChange = (state: OrgChartState) => {
|
|
// Data/source/validation/style failures are state.status === 'error'.
|
|
// A ready state contains the Core's shared summary and dataVersion.
|
|
void state;
|
|
};
|
|
return (
|
|
<OrgChart
|
|
dataSource={dataSource}
|
|
stylesheetUrls={stylesheetUrls}
|
|
onStateChange={onStateChange}
|
|
onLifecycleError={error => { console.error('OrgChart lifecycle failed', error); }}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
The `?url` imports above are Vite asset imports, not a universal browser feature. Other hosts resolve the two Core public CSS entries to actual deployment URLs. Importing CSS only into the document does not style the Core's Shadow DOM. Engine CSS is linked first, then standalone Core CSS. P1 owns its colors, typography and geometry without depending on HM Theme. Set public `--hm-orgchart-*` properties on the wrapper container to override only this instance. No HM common variables are redefined. The default entry has no runtime or declaration dependency on HM UI. For independent Tree/Summary integration, use the [Core factories](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/docs/org-chart/standalone-components-p1.md) in a ref/effect without copying their UI.
|
|
|
|
## Explicit popup adapters
|
|
|
|
Minimum React installation: `@hm-design/org-chart`, `@hm-design/org-chart-react`, React 19 and the host renderer (usually `react-dom`). HM UI, HM Theme and Tailwind are unnecessary for the default entry and Core popup. Core + Vanilla requires only `@hm-design/org-chart` and its public CSS URLs.
|
|
|
|
For the existing HM Dialog, install the optional `@hm-design/ui@0.1.0` peer and its required peers, provide the host's real HM Theme/Tailwind CSS, and explicitly select both the entry and popup mode:
|
|
|
|
```tsx
|
|
import { OrgChart } from '@hm-design/org-chart-react/hm-dialog';
|
|
// Existing dataSource, stylesheetUrls and all other Props are unchanged.
|
|
<OrgChart dataSource={dataSource} stylesheetUrls={stylesheetUrls}
|
|
layoutMode="auto" employeePopup="hm-dialog" />;
|
|
```
|
|
|
|
Migration from the previous HM popup setup: change the `OrgChart` import to `/hm-dialog`; keep `employeePopup="hm-dialog"`. Installing UI alone never switches the popup. Even the HM entry defaults to the Core popup. The default entry can also use the exported `HMEmployeeDialog` as `employeePopupAdapter` explicitly.
|
|
|
|
For another design system, keep the default entry and implement a React component with the public `OrgChartEmployeePopupProps` contract:
|
|
|
|
```tsx
|
|
import { OrgChart, type OrgChartEmployeePopupProps } from '@hm-design/org-chart-react';
|
|
// MyEmployeePopup receives { selection, onClose }; selection is null when closed.
|
|
// Read selection.employee/context without mutating them. Own accessible modal
|
|
// semantics, Escape/close behavior and focus restoration to context.trigger.
|
|
<OrgChart dataSource={dataSource} stylesheetUrls={stylesheetUrls} layoutMode="auto"
|
|
employeePopup="adapter" employeePopupAdapter={MyEmployeePopup} />;
|
|
```
|
|
|
|
Neither Core nor the dataset changes. Supplying an adapter alone does not enable it. An explicit `onEmployeeClick` still takes precedence. If an adapter mode is requested without an adapter, the wrapper retains the Core popup and reports a configuration error through `onLifecycleError`. Fixed continues to use its existing Core popup. Department Colors and Overview remain Storybook-only pages and have no new exports.
|
|
|
|
## Public API
|
|
|
|
Default named runtime export: `OrgChart`. `/hm-dialog` exports the same `OrgChart` Props and `HMEmployeeDialog` adapter. Type exports: `OrgChartProps`, `OrgChartEmployeeSelection`, `OrgChartEmployeePopupProps` and the Core's public types, reexported as types only. Runtime helpers such as `JsonOrgChartDataSource` remain available from `@hm-design/org-chart`.
|
|
|
|
`OrgChartProps` reuses `Omit<OrgChartOptions, 'container' | 'onStateChange'>`:
|
|
|
|
- Required `dataSource`: the same Core `OrgChartDataSource` interface, `load(options?: { signal?: AbortSignal }): Promise<unknown>`.
|
|
- Required `stylesheetUrls`: `{ engine: string; core: string }`, the exact Core asset contract.
|
|
- Optional `displayMode`: `'interactive'` (default) or `'readOnly'`, passed to the Core composition. Changing it recreates the Core handle.
|
|
- Optional `layoutMode`: `'fixed'` (default) or `'auto'`, independent of `displayMode`. Auto renders company forests, collapse and Zoom/Pan/Fit. P5-4 retains the current Expanded card presentation by user decision; external SVG matching is no longer required. Changing mode recreates the Core handle.
|
|
|
|
See [Expanded usage and styling](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/docs/org-chart/expanded-usage.md) for company
|
|
selection, top nodes, JSON sources, popup ownership and Auto-only CSS examples.
|
|
Keyboard focus reveals offscreen controls at the current Zoom; group-title Hover
|
|
feedback and drag/keyboard activation are implemented in the shared Core.
|
|
- Optional `companyId`: legacy Auto fallback company preset (default `hanmac`). Explicit/inherited organization company identity takes priority; Fixed ignores company colors. Changing it recreates the handle. See [Core README](https://gitea.hmac.kr/b25027/HMDesign/src/commit/d2b76e766466e1f156f6a0f8f63710e023af900e/packages/org-chart/README.md).
|
|
- Optional `selectedCompanyIds`: undefined selects all, [] selects none. Changes call Core selection on the existing instance, preserving per-company collapse state and source counts.
|
|
- Optional `autoLayout`: `{ type: 'tb' | 'lr' | 'hybrid', hybridStartDepth: 2 | 3 | 4, initialVisibleDepth: 1 | 2 | 3 | 'all', hybridOrganizationIds?: readonly string[] }` (each field optional). Changes use Core `setAutoLayout` on the existing instance; TB/LR/Hybrid changes retain valid collapse state. Per-group hybrid IDs apply only to TB; clear them when selecting LR/global Hybrid. Omission follows Core defaults (TB, Hybrid depth 3, all visible).
|
|
- Optional `interactions`: `{ collapse?: boolean, zoom?: boolean, pan?: boolean }`, all enabled by default. Auto independently enables/disables group toggles, Zoom controls/wheel Zoom and drag Pan via Core `setInteractions` on the same instance. Omitted fields are enabled, and removing the prop restores all three defaults. Existing collapse and viewport state remain; TB/LR/Hybrid has no interaction prerequisite. Fixed ignores this option and keeps its existing behavior.
|
|
- Optional `employeePopup`: `'core'` (default), `'hm-dialog'`, `'adapter'`, or `'none'`. Applies only to Auto; Fixed retains its existing Core popup. HM Dialog requires the explicit `/hm-dialog` entry or `employeePopupAdapter={HMEmployeeDialog}`, the optional `@hm-design/ui@0.1.0` peer and the consumer's real HM Theme/Tailwind CSS entry. An explicit `onEmployeeClick` always takes precedence and suppresses either default popup. Host popup state closes on source/company changes, adapter replacement and successful movement. HM focus restoration still resolves the employee's replacement card in the original ShadowRoot after a layout render. Auto uses Core `setEmployeeClickHandler` to change popup ownership on the same instance, retaining company selection, layout, collapse and viewport state.
|
|
- Optional `employeePopupAdapter`: React `ComponentType<OrgChartEmployeePopupProps>`. Receives the untouched employee/context selection and `onClose`; used only with an explicit adapter popup mode. No global registration or package-presence detection is performed.
|
|
- Optional `companyTopNodes`, `employeeConnections`, `zoom`: immutable Core configuration maps/range. Identity changes recreate the handle; memoize these objects. Names, positions and profile overrides affect only Auto presentation, not callback data.
|
|
- Optional `organizationLinks`: existing organization-ID-to-URL map, applied only to original 1Depth titles. Missing/blank values retain plain titles. Core validates HTTP(S)/relative URLs and rejects invalid URLs, other schemes and credentials. Treat the map as immutable and memoize it; a new map recreates the handle.
|
|
- Optional `onEmployeeClick(employee, context)`: host-owned information UI, replacing the Core's built-in popup. `context` contains the existing `organization`, `affiliation`, focusable `trigger` card and `originalEvent`. It works in both modes and receives untouched Employee data.
|
|
- Optional `onMove(result: OrgChartMoveResult)`: latest committed callback receives Auto employee/group move outcomes (`moved` distinguishes success from rejection). Successful moves close an open HM employee Dialog, so its employee context cannot become stale. Changing this callback does not recreate or reload Core. Movement changes the Core's in-memory snapshot; no fixture or server is written.
|
|
- Optional `aggregationPolicy`: the Core's `ApprovedAggregationPolicy`.
|
|
- Optional `className` and `style`: applied only to React's outer container.
|
|
- Optional `onStateChange(state: OrgChartState)`: latest committed callback receives actual Core state changes.
|
|
- Optional `onLifecycleError(error: unknown)`: reports unexpected Core creation or rejected mount/reload calls and unexpected cleanup errors. Controlled data, source, validation, rendering and stylesheet failures use the Core's `error` state instead.
|
|
|
|
The container is owned by React; its dedicated children are owned by Core. There are no React children, imperative handle, title, employee or engine manipulation Props. Titles, descriptions, presentation groups, organization order and metadata use the common data contract. An omitted lifecycle error callback leaves unexpected operation errors handled but does not produce a second error UI or console log. Consumer state callback errors follow the Core's isolation behavior; lifecycle error callback exceptions are also contained.
|
|
|
|
Without `onEmployeeClick`, employee cards follow `employeePopup` in Auto: Core read-only information dialog (default), public HM Dialog, or no popup. Fixed always retains the Core popup. Both dialogs use only existing employee/organization/affiliation fields and omit missing values. Core readOnly names match the displayed card while source data remains unchanged; HM Dialog shows source employee data. The native modal sits outside the scaled Stage and HM Dialog uses a React Portal; both support Close, ESC, focus trapping and return to the card. Group-link events remain separate; a visible name tooltip closes before popup activation. A custom callback owns its popup's accessibility and cleanup.
|
|
|
|
```tsx
|
|
<OrgChart
|
|
dataSource={dataSource}
|
|
stylesheetUrls={stylesheetUrls}
|
|
displayMode="readOnly"
|
|
organizationLinks={configuredOrganizationLinks}
|
|
onEmployeeClick={(employee, context) => openEmployeeInformation(employee, context)}
|
|
/>
|
|
```
|
|
|
|
Both map and popup function above are supplied by the host; no real destination URL or employee information is embedded in the package. Omit the callback for the default popup.
|
|
|
|
## Lifecycle and updates
|
|
|
|
P9.4 adds `dragDropEnabled?: boolean` (default `false`) for Auto. Pass `true` to
|
|
enable employee/group pointer, keyboard and public Core moves. Changing this prop
|
|
calls the shared Core `setDragDropEnabled` synchronously on the existing handle;
|
|
it neither remounts nor reloads the source. Switching OFF cancels active movement,
|
|
clears highlights and retains completed moves, popup, collapse and viewport state.
|
|
Pan/Zoom/Fit remain independent. Fixed ignores the prop.
|
|
|
|
Mount creates one new Core handle, subscribes to its state and calls `mount(container)`. Mount already loads the source; the wrapper does not perform an additional initial reload. The wrapper does not synthesize `idle`, `destroyed` or `empty` callbacks. Current Core emits `loading`, then `ready`/`error`; employees `[]` is ready, organizations `[]` is error. An `empty` type member does not establish a supported Empty State.
|
|
|
|
A new `dataSource` object triggers the existing handle's `reload(nextSource)`. This starts Core's latest revision and aborts or ignores older responses. Updating fields inside the same source object is not a reload signal. `JsonOrgChartDataSource` captures inline input at its construction; create a new source for a new immutable snapshot. Memoize sources rather than constructing one on every render.
|
|
|
|
Changing callback identity updates the latest committed callback without reload or recreation. In Auto, adding/removing `onEmployeeClick` or changing `employeePopup` updates Core's employee handler without replacing the handle. Fixed retains its original ownership policy: adding/removing `onEmployeeClick` recreates the handle. Abandoned concurrent renders do not update callbacks. Policy or `organizationLinks` identity changes, displayMode changes, or either stylesheet URL string value changes dispose the previous handle and create a new one because these settings have no Core setter. Replacing the stylesheet URL object with the same two strings does not recreate. Treat policy and link-map objects as immutable and memoize them; changing an object internally is not a configuration update signal.
|
|
|
|
Auto separates blank-space Pan from employee-card/group-title movement. Click continues to open employee information or toggle a group; employees and groups may move within or across companies. Employee moves update `organizationId`; group moves update `parentId` and retain the subtree and IDs. Within-company moves preserve existing affiliations, including dispatched employees. Cross-company employee moves additionally update `affiliationId` to the target company; cross-company group moves update the moved group's and every descendant's `companyId` and their employees' `affiliationId`. The target company controls colors and counts; leaders and company roots remain protected. Focus a card/title and press Alt+M to begin keyboard movement, arrow keys to choose a target, Enter to confirm, or ESC to cancel. Employee cards retain a keyboard focus entry point when `employeePopup="none"`. Fixed has no movement editing.
|
|
|
|
Cleanup invalidates the wrapper generation, unsubscribes, calls Core's terminal `destroy()` and clears its handle reference. React Strict Mode's setup → cleanup → setup uses a fresh handle. Old generations cannot deliver state/lifecycle callbacks to the new instance; superseded operation rejection handlers are also ignored. Cleanup runs before replacement, leaving at most one active Core per wrapper container. The wrapper does not repeat engine disposal or DOM removal itself.
|
|
|
|
Auto retains valid organization collapse state on source/selection updates, fits reloads/selection and restores toggle focus. Fixed retains its previous regeneration policy. Source errors can recover by replacing the source; Core validation/summary/node behavior remains identical to Vanilla. Invalid runtime Props that prevent Core construction are lifecycle usage errors: fix them and remount the component (for example by changing its React key). No rendered fallback silently replaces an invalid configuration.
|
|
|
|
Committed callback publication, Core ownership and source updates use layout effects in that order. Cleanup/reload runs synchronously with React commit, so a pending old response cannot notify in a gap before passive cleanup. Server rendering produces only the outer container; SSR/hydration remains outside the verified scope.
|
|
|
|
## Package boundary and verification limits
|
|
|
|
The build emits separate ESM `dist/index.js` and `dist/hm-dialog.js` entries, their shared UI-independent wrapper chunk, and TypeScript declarations. It preserves the `"use client"` boundary and leaves React (including `react/jsx-runtime`), `@hm-design/org-chart`, and optional `@hm-design/ui` external. Only the HM entry imports UI. React is a `^19.0.0` peer, matching the repository's existing React package convention; Core `0.1.0` is a runtime dependency. React DOM is supplied by the consuming app and is not imported by this package. Core must be built before wrapper typecheck/build in this workspace because dependency resolution uses its public declarations. Building all entries, including the HM adapter, also needs HM UI declarations in the maintainer workspace; installing the packed default entry does not.
|
|
|
|
From the repository root:
|
|
|
|
```sh
|
|
npm run typecheck --workspace @hm-design/org-chart-react
|
|
npm run build --workspace @hm-design/org-chart-react
|
|
```
|
|
|
|
Storybook is one React host, not proof of every consumer deployment. The adapter delegates Auto Pan/Zoom to Core; it adds no alternate layout, additional relation overlays or API client. SSR/hydration, full screen reader behavior, physical touch, complete heap/GC recovery, HM Dialog without its Theme, Workdesk reference visual approval and every deployment environment require their own evidence. Default/Core rendering requires no HM Theme. Public ESM import without a DOM does not establish SSR/hydration support. Consult the minimal-install verification report for actual packed-package consumer results.
|
|
|
|
## AI coding tools
|
|
|
|
AI coding tools should read [`ai/AI_GUIDE.md`](./ai/AI_GUIDE.md) first (shared HMDesign entry point). [`ai/AI_USAGE.md`](./ai/AI_USAGE.md) points to the OrgChart rules, API catalog and patterns shipped in `@hm-design/org-chart/ai/`. `ai/` is included in the packed package.
|