Files
HMDesign/packages/org-chart/src/core/auto-layout-policy.ts
T

60 lines
3.7 KiB
TypeScript

import type { Organization } from '../data/index.js';
import type { OrgChartAutoLayoutOptions, OrgChartAutoLayoutType, OrgChartInteractionOptions } from './types.js';
export function resolveInteractions(options: OrgChartInteractionOptions = {}): Readonly<Required<OrgChartInteractionOptions>> {
for (const key of ['collapse', 'zoom', 'pan'] as const) {
if (options[key] !== undefined && typeof options[key] !== 'boolean') throw new TypeError(`interactions.${key} must be boolean`);
}
return Object.freeze({ collapse: options.collapse ?? true, zoom: options.zoom ?? true, pan: options.pan ?? true });
}
export interface ResolvedAutoLayout {
readonly type: OrgChartAutoLayoutType;
readonly hybridStartDepth: 2 | 3 | 4;
readonly initialVisibleDepth: 1 | 2 | 3 | 'all';
readonly hybridOrganizationIds: readonly string[];
}
/** Match installed dabeng 6's mutually exclusive directional/vertical policies. */
export function resolveAutoLayout(options: OrgChartAutoLayoutOptions = {}): ResolvedAutoLayout {
const type = options.type ?? 'tb';
const hybridStartDepth = options.hybridStartDepth ?? 3;
const initialVisibleDepth = options.initialVisibleDepth ?? 'all';
if (!['tb', 'lr', 'hybrid'].includes(type)) throw new TypeError('autoLayout.type must be tb, lr or hybrid');
if (![2, 3, 4].includes(hybridStartDepth)) throw new TypeError('autoLayout.hybridStartDepth must be 2, 3 or 4');
if (![1, 2, 3, 'all'].includes(initialVisibleDepth)) throw new TypeError('autoLayout.initialVisibleDepth must be 1, 2, 3 or all');
if (options.hybridOrganizationIds !== undefined && (!Array.isArray(options.hybridOrganizationIds) || options.hybridOrganizationIds.some(id => typeof id !== 'string' || !id.trim()))) throw new TypeError('autoLayout.hybridOrganizationIds must contain nonempty organization IDs');
const hybridOrganizationIds = Object.freeze([...new Set(options.hybridOrganizationIds ?? [])]);
if (type !== 'tb' && hybridOrganizationIds.length) throw new TypeError('Per-group hybridOrganizationIds are supported only with TB; clear them before selecting LR or Hybrid');
return Object.freeze({ type, hybridStartDepth, initialVisibleDepth, hybridOrganizationIds });
}
export function autoEngineOptions(layout: ResolvedAutoLayout): { direction: 't2b' | 'l2r'; verticalLevel?: number; visibleLevel: number } {
// Core owns collapse state and prunes its projection. Upstream visibleLevel
// would retain hidden descendants that disagree with Core keyboard/state APIs.
return { direction: layout.type === 'lr' ? 'l2r' : 't2b', visibleLevel: 999,
...(layout.type === 'hybrid' ? { verticalLevel: layout.hybridStartDepth } : {}) };
}
/** Apply the visibleLevel policy to new IDs only, keeping user changes stable. */
export function applyInitialVisibility(organizations: readonly Organization[], collapsed: Set<string>, initialized: Set<string>, visibleDepth: ResolvedAutoLayout['initialVisibleDepth'], reset = false): void {
const valid = new Set(organizations.map(organization => organization.id));
for (const id of collapsed) if (!valid.has(id) || reset) collapsed.delete(id);
for (const id of initialized) if (!valid.has(id) || reset) initialized.delete(id);
const byId = new Map(organizations.map(organization => [organization.id, organization]));
for (const organization of organizations) {
if (initialized.has(organization.id)) continue;
initialized.add(organization.id);
if (visibleDepth === 'all') continue;
let depth = 1;
let current = organization;
const seen = new Set([current.id]);
while (current.parentId) {
const parent = byId.get(current.parentId);
if (!parent || seen.has(parent.id)) break;
seen.add(parent.id); current = parent; depth++;
}
if (depth >= visibleDepth) collapsed.add(organization.id);
}
}