feat(org-chart): support standalone React integration and update guides
This commit is contained in:
1 parent
5acaf8a295
commit
b637f86631
19 files changed
+1455
-64
No files matched your search
@@ -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}
|
||||
</>;
|
||||
}
|
||||
Reference in new issue
Block a user