feat(org-chart): support standalone React integration and update guides

This commit is contained in:
shkim4 committed 2026-10-13 16:03:05 +09:00
1 parent 5acaf8a295
commit b637f86631
19 files changed
+1455 -64

No files matched your search

+22 -16
View File
@@ -1,12 +1,9 @@
"use client";
import { lazy, Suspense, useLayoutEffect, useRef, useState } from 'react';
import { useLayoutEffect, useRef, useState } from 'react';
import { createOrgChart } from '@hm-design/org-chart';
import type { OrgChartAutoLayoutOptions, OrgChartDataSource, OrgChartHandle, OrgChartInteractionOptions, OrgChartOptions } from '@hm-design/org-chart';
import type { OrgChartProps } from './types.js';
import type { EmployeeDialogSelection } from './HMEmployeeDialog.js';
const HMEmployeeDialog = lazy(() => import('./HMEmployeeDialog.js'));
import type { OrgChartEmployeeSelection, OrgChartProps } from './types.js';
function annotateEmployeeCards(shadow: ShadowRoot, popup: NonNullable<OrgChartProps['employeePopup']>, customCallback: boolean) {
const actionable = customCallback || popup !== 'none';
@@ -33,7 +30,7 @@ interface Instance {
dragDropEnabled: boolean;
readonly employeeClick: NonNullable<OrgChartOptions['onEmployeeClick']>;
employeeCallbackEnabled: boolean;
popupMode: 'core' | 'hm-dialog' | 'none';
popupMode: NonNullable<OrgChartProps['employeePopup']>;
operation: number;
}
@@ -56,6 +53,7 @@ export function OrgChart({
onEmployeeClick,
onMove,
employeePopup = 'core',
employeePopupAdapter: EmployeePopupAdapter,
className,
style,
onStateChange,
@@ -64,19 +62,28 @@ export function OrgChart({
const containerRef = useRef<HTMLDivElement>(null);
const instanceRef = useRef<Instance | null>(null);
const generationRef = useRef(0);
const [employeeSelection, setEmployeeSelection] = useState<EmployeeDialogSelection | null>(null);
const committedRef = useRef({ dataSource, onStateChange, onLifecycleError, onEmployeeClick, onMove, employeePopup });
const [employeeSelection, setEmployeeSelection] = useState<OrgChartEmployeeSelection | null>(null);
const requestedAdapter = employeePopup === 'hm-dialog' || employeePopup === 'adapter';
const effectivePopup = requestedAdapter && !EmployeePopupAdapter ? 'core' : employeePopup;
const committedRef = useRef({ dataSource, onStateChange, onLifecycleError, onEmployeeClick, onMove, employeePopup: effectivePopup });
// Publish committed values before Core effects run, without letting callback
// identity changes or abandoned concurrent renders alter Core ownership.
useLayoutEffect(() => {
committedRef.current = { dataSource, onStateChange, onLifecycleError, onEmployeeClick, onMove, employeePopup };
}, [dataSource, onStateChange, onLifecycleError, onEmployeeClick, onMove, employeePopup]);
committedRef.current = { dataSource, onStateChange, onLifecycleError, onEmployeeClick, onMove, employeePopup: effectivePopup };
}, [dataSource, onStateChange, onLifecycleError, onEmployeeClick, onMove, effectivePopup]);
useLayoutEffect(() => {
if (layoutMode !== 'auto' || !requestedAdapter || EmployeePopupAdapter || onEmployeeClick) return;
try {
committedRef.current.onLifecycleError?.(new Error('OrgChart popup adapter is missing. Use @hm-design/org-chart-react/hm-dialog or supply employeePopupAdapter. Falling back to the Core popup.'));
} catch { /* Consumer error reporting cannot prevent Core rendering. */ }
}, [layoutMode, requestedAdapter, EmployeePopupAdapter, Boolean(onEmployeeClick)]);
const engineStylesheetUrl = stylesheetUrls.engine;
const coreStylesheetUrl = stylesheetUrls.core;
const hasEmployeeCallback = typeof onEmployeeClick === 'function';
const popupMode = layoutMode === 'auto' ? employeePopup : 'core';
const popupMode = layoutMode === 'auto' ? effectivePopup : 'core';
const adapterOwnsPopup = popupMode !== 'core';
const hasAdapterCallback = hasEmployeeCallback || popupMode !== 'core';
const fixedHasEmployeeCallback = layoutMode !== 'auto' && hasEmployeeCallback;
@@ -103,7 +110,7 @@ export function OrgChart({
if (generationRef.current !== generation) return;
const callback = committedRef.current.onEmployeeClick;
if (callback) callback(employee, context);
else if (committedRef.current.employeePopup === 'hm-dialog') setEmployeeSelection({ employee, context });
else if (committedRef.current.employeePopup === 'hm-dialog' || committedRef.current.employeePopup === 'adapter') setEmployeeSelection({ employee, context });
};
handle = createOrgChart({
dataSource: source,
@@ -197,7 +204,7 @@ export function OrgChart({
if (shadow && layoutMode === 'auto') {
annotateEmployeeCards(shadow, popupMode, hasEmployeeCallback);
}
}, [dataSource, selectedCompanyIds, popupMode, hasEmployeeCallback, layoutMode, adapterOwnsPopup]);
}, [dataSource, selectedCompanyIds, popupMode, hasEmployeeCallback, layoutMode, adapterOwnsPopup, EmployeePopupAdapter]);
useLayoutEffect(() => {
const instance = instanceRef.current;
@@ -259,8 +266,7 @@ export function OrgChart({
return <>
<div ref={containerRef} className={className} style={style} />
{popupMode === 'hm-dialog' && !hasEmployeeCallback ? <Suspense fallback={null}>
<HMEmployeeDialog selection={employeeSelection} onClose={() => setEmployeeSelection(null)} />
</Suspense> : null}
{(popupMode === 'hm-dialog' || popupMode === 'adapter') && EmployeePopupAdapter && !hasEmployeeCallback
? <EmployeePopupAdapter selection={employeeSelection} onClose={() => setEmployeeSelection(null)} /> : null}
</>;
}