feat(orgchart): integrate chart-only viewport and scoped guard fixes
Add initial fit and direct wheel zoom with preserved default presentation. Integrate five scoped GuardFix findings and AI generation preselection policy. Exclude Cell experiments and retain five historical guard failures.
This commit is contained in:
1 parent
0a8daf7ee7
commit
d56d587fd2
28 files changed
+1170
-77
No files matched your search
@@ -21,12 +21,17 @@ function annotateEmployeeCards(shadow: ShadowRoot, popup: NonNullable<OrgChartPr
|
||||
}
|
||||
}
|
||||
|
||||
// Core captures option objects by value. Compare them the same way so a parent
|
||||
// re-render with equal inline literals neither remounts nor re-fits the chart.
|
||||
const optionKey = (value: unknown) => JSON.stringify(value ?? null);
|
||||
|
||||
interface Instance {
|
||||
readonly handle: OrgChartHandle;
|
||||
readonly generation: number;
|
||||
source: OrgChartDataSource;
|
||||
autoLayout: OrgChartAutoLayoutOptions | undefined;
|
||||
interactions: OrgChartInteractionOptions | undefined;
|
||||
selectedCompanyIds: readonly string[] | undefined;
|
||||
dragDropEnabled: boolean;
|
||||
readonly employeeClick: NonNullable<OrgChartOptions['onEmployeeClick']>;
|
||||
employeeCallbackEnabled: boolean;
|
||||
@@ -49,6 +54,9 @@ export function OrgChart({
|
||||
companyTopNodes,
|
||||
employeeConnections,
|
||||
zoom,
|
||||
presentationMode,
|
||||
initialView,
|
||||
wheelZoom,
|
||||
organizationLinks,
|
||||
onEmployeeClick,
|
||||
onMove,
|
||||
@@ -126,6 +134,9 @@ export function OrgChart({
|
||||
companyTopNodes,
|
||||
employeeConnections,
|
||||
zoom,
|
||||
presentationMode,
|
||||
initialView,
|
||||
wheelZoom,
|
||||
organizationLinks,
|
||||
onEmployeeClick: hasAdapterCallback ? employeeClick : undefined,
|
||||
onMove: result => {
|
||||
@@ -134,7 +145,7 @@ export function OrgChart({
|
||||
committedRef.current.onMove?.(result);
|
||||
},
|
||||
});
|
||||
const instance: Instance = { handle, source, autoLayout, interactions, dragDropEnabled, employeeClick, employeeCallbackEnabled: hasAdapterCallback, popupMode, generation, operation: 1 };
|
||||
const instance: Instance = { handle, source, autoLayout, interactions, selectedCompanyIds, dragDropEnabled, employeeClick, employeeCallbackEnabled: hasAdapterCallback, popupMode, generation, operation: 1 };
|
||||
instanceRef.current = instance;
|
||||
unsubscribe = handle.subscribe(state => {
|
||||
if (instanceRef.current === instance && generationRef.current === generation) {
|
||||
@@ -181,7 +192,7 @@ export function OrgChart({
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [aggregationPolicy, engineStylesheetUrl, coreStylesheetUrl, displayMode, layoutMode, companyId, companyTopNodes, employeeConnections, zoom, organizationLinks, fixedHasEmployeeCallback]);
|
||||
}, [optionKey(aggregationPolicy), engineStylesheetUrl, coreStylesheetUrl, displayMode, layoutMode, companyId, optionKey(companyTopNodes), optionKey(employeeConnections), optionKey(zoom), presentationMode, initialView, wheelZoom, optionKey(organizationLinks), fixedHasEmployeeCallback]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
@@ -208,14 +219,14 @@ export function OrgChart({
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
if (!instance || layoutMode !== 'auto' || instance.autoLayout === autoLayout) return;
|
||||
if (!instance || layoutMode !== 'auto' || optionKey(instance.autoLayout) === optionKey(autoLayout)) return;
|
||||
instance.autoLayout = autoLayout;
|
||||
void instance.handle.setAutoLayout(autoLayout ?? { type: 'tb', hybridStartDepth: 3, initialVisibleDepth: 'all', hybridOrganizationIds: [] }).catch(error => {
|
||||
if (instanceRef.current !== instance) return;
|
||||
try { committedRef.current.onLifecycleError?.(error); }
|
||||
catch { /* Keep layout setter failures in the lifecycle error contract. */ }
|
||||
});
|
||||
}, [autoLayout, layoutMode]);
|
||||
}, [optionKey(autoLayout), layoutMode]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
@@ -231,24 +242,26 @@ export function OrgChart({
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
if (!instance || layoutMode !== 'auto' || instance.interactions === interactions) return;
|
||||
if (!instance || layoutMode !== 'auto' || optionKey(instance.interactions) === optionKey(interactions)) return;
|
||||
instance.interactions = interactions;
|
||||
void instance.handle.setInteractions({ collapse: true, zoom: true, pan: true, ...interactions }).catch(error => {
|
||||
if (instanceRef.current !== instance) return;
|
||||
try { committedRef.current.onLifecycleError?.(error); }
|
||||
catch { /* Interaction controls use the existing lifecycle error contract. */ }
|
||||
});
|
||||
}, [interactions, layoutMode]);
|
||||
}, [optionKey(interactions), layoutMode]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
if (!instance || layoutMode !== 'auto') return;
|
||||
// Mount already passed this selection to Core; only a changed value re-applies.
|
||||
if (!instance || layoutMode !== 'auto' || optionKey(instance.selectedCompanyIds) === optionKey(selectedCompanyIds)) return;
|
||||
instance.selectedCompanyIds = selectedCompanyIds;
|
||||
void instance.handle.setSelectedCompanies(selectedCompanyIds).catch(error => {
|
||||
if (instanceRef.current !== instance) return;
|
||||
try { committedRef.current.onLifecycleError?.(error); }
|
||||
catch { /* Match other lifecycle callbacks: never leak a rejected Promise. */ }
|
||||
});
|
||||
}, [selectedCompanyIds, layoutMode]);
|
||||
}, [optionKey(selectedCompanyIds), layoutMode]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
|
||||
Reference in new issue
Block a user