import type { Organization } from '../data/index.js'; import type { OrgChartAutoLayoutOptions, OrgChartAutoLayoutType, OrgChartInteractionOptions } from './types.js'; export function resolveInteractions(options: OrgChartInteractionOptions = {}): Readonly> { 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, initialized: Set, 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); } }