Files
HMDesign/packages/org-chart-react/README.md
T

9.4 KiB

@hm-design/org-chart-react v0.1

React 19 lifecycle adapter for @hm-design/org-chart@0.1.0. The wrapper renders one empty host container and delegates all organization DOM, engine work, validation, summaries, interactions and Shadow DOM styles to the existing Vanilla Core. It has no navigation, page layout, fetch client or separate node design.

Usage

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 wrapper does not depend on @hm-design/ui; P3 adds lookup Props without replacing the Core renderer. For independent Tree/Summary integration, use the Core factories in a ref/effect without copying their UI.

Public API

Named runtime export: OrgChart. Type exports: OrgChartProps 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 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 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, all employee cards open the same Core read-only information dialog on click, Enter or Space. It uses only existing employee/organization/affiliation fields and omits missing values. readOnly names match the displayed 홍 길 동 card while source data remains unchanged. The native modal sits outside the scaled Stage and supports 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.

<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

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. Adding/removing onEmployeeClick switches built-in/custom popup ownership and 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 the Core offers no configuration 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.

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.

Core regeneration resets expansion/scroll state according to the existing Core contract. 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 ESM dist/index.js plus TypeScript declarations, preserves the "use client" boundary and leaves React (including react/jsx-runtime) and @hm-design/org-chart external. 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.

From the repository root:

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. This adapter does not add pan/zoom, advanced mobile reflow, additional relation overlays or reserved Core presentation sections. SSR/hydration, full screen reader behavior, complete heap/GC recovery, Theme-less rendering, Workdesk reference visual approval and production consumers require their own evidence. Public ESM import without a DOM does not establish SSR/hydration support. Consult the final integration report for the actual typecheck/build/browser/consumer results.