60 lines
3.7 KiB
TypeScript
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);
|
|
}
|
|
}
|