feat(org-chart): finalize chart features and AI generation rules
This commit is contained in:
1 parent
7614507583
commit
d0828a7ff4
80 files changed
+8152
-221
No files matched your search
@@ -1,7 +1,9 @@
|
||||
/** dabeng owns recursive widths and connectors. Reserve the tallest content
|
||||
* height per depth so child rows align with unequal staff counts. Observe the
|
||||
* articles rather than reserved nodes to avoid ResizeObserver feedback. */
|
||||
export function alignExpandedLevels(viewport: HTMLElement, signal: AbortSignal, layoutReady: () => void = () => {}): void {
|
||||
import type { ResolvedAutoLayout } from './auto-layout-policy.js';
|
||||
|
||||
export function alignExpandedLevels(viewport: HTMLElement, signal: AbortSignal, layoutReady: () => void = () => {}, layout?: ResolvedAutoLayout): void {
|
||||
const view = viewport.ownerDocument.defaultView;
|
||||
if (!view || signal.aborted) return;
|
||||
const nodes = [...viewport.querySelectorAll<HTMLElement>('.node[data-depth]')];
|
||||
@@ -10,15 +12,20 @@ export function alignExpandedLevels(viewport: HTMLElement, signal: AbortSignal,
|
||||
frame = 0;
|
||||
if (signal.aborted) return;
|
||||
const heights = new Map<string, number>();
|
||||
const widths = new Map<string, number>();
|
||||
for (const node of nodes) {
|
||||
const height = node.querySelector<HTMLElement>('.hm-orgchart-organization')?.offsetHeight ?? 0;
|
||||
heights.set(node.dataset.depth!, Math.max(heights.get(node.dataset.depth!) ?? 0, Math.ceil(height)));
|
||||
const width = node.querySelector<HTMLElement>('.hm-orgchart-organization')?.offsetWidth ?? 0;
|
||||
widths.set(node.dataset.depth!, Math.max(widths.get(node.dataset.depth!) ?? 0, Math.ceil(width)));
|
||||
}
|
||||
for (const node of nodes) {
|
||||
node.style.minHeight = `${heights.get(node.dataset.depth!) ?? 0}px`;
|
||||
node.style.minHeight = layout?.type === 'lr' || node.closest('.nodes.vertical') ? '0' : `${heights.get(node.dataset.depth!) ?? 0}px`;
|
||||
if (layout?.type === 'lr') node.style.minWidth = `${widths.get(node.dataset.depth!) ?? 0}px`;
|
||||
node.style.setProperty('--hm-orgchart-expanded-content-height', `${node.querySelector<HTMLElement>('.hm-orgchart-organization')?.offsetHeight ?? 0}px`);
|
||||
}
|
||||
drawEmployeeConnectors(viewport);
|
||||
if (layout?.type === 'lr' || layout?.type === 'hybrid' || layout?.hybridOrganizationIds.length) drawOrganizationConnectors(viewport, layout);
|
||||
layoutReady();
|
||||
};
|
||||
const schedule = () => {
|
||||
@@ -36,6 +43,58 @@ export function alignExpandedLevels(viewport: HTMLElement, signal: AbortSignal,
|
||||
schedule();
|
||||
}
|
||||
|
||||
/** Adapt dabeng's existing hierarchy DOM to tall HM cards. These paths own only
|
||||
* connector presentation; dabeng still creates and recursively lays out nodes. */
|
||||
function drawOrganizationConnectors(viewport: HTMLElement, layout: ResolvedAutoLayout): void {
|
||||
const document = viewport.ownerDocument;
|
||||
for (const tree of viewport.querySelectorAll<HTMLElement>('.hm-orgchart-tree')) {
|
||||
tree.querySelector(':scope > .hm-orgchart-organization-connectors')?.remove();
|
||||
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
||||
svg.classList.add('hm-orgchart-organization-connectors');
|
||||
svg.setAttribute('aria-hidden', 'true');
|
||||
svg.setAttribute('width', String(tree.offsetWidth));
|
||||
svg.setAttribute('height', String(tree.offsetHeight));
|
||||
const nodes = [...tree.querySelectorAll<HTMLElement>('.node[data-organization-id]')];
|
||||
const byId = new Map(nodes.map(node => [node.dataset.organizationId!, node]));
|
||||
const local = (element: HTMLElement) => {
|
||||
const treeRect = tree.getBoundingClientRect(), rect = element.getBoundingClientRect();
|
||||
// Forest scale is the only transform (LR's upstream rotation is scoped out).
|
||||
const scale = Number(viewport.dataset.scale) || 1;
|
||||
return { x: (rect.left - treeRect.left) / scale, y: (rect.top - treeRect.top) / scale,
|
||||
width: element.offsetWidth, height: element.offsetHeight };
|
||||
};
|
||||
for (const node of nodes) {
|
||||
const parent = byId.get(node.dataset.parentId!);
|
||||
if (!parent) continue;
|
||||
const article = parent.querySelector<HTMLElement>(':scope > .hm-orgchart-organization')!;
|
||||
const heading = node.querySelector<HTMLElement>('.hm-orgchart-organization-name')!;
|
||||
const from = local(article), to = local(heading);
|
||||
const left = layout.type === 'lr' || !!node.closest('.nodes.vertical');
|
||||
const path = document.createElementNS(svg.namespaceURI, 'path');
|
||||
if (layout.type === 'lr') {
|
||||
const parentHeading = local(parent.querySelector<HTMLElement>('.hm-orgchart-organization-name')!);
|
||||
const x = parentHeading.x + parentHeading.width, y = parentHeading.y + parentHeading.height / 2;
|
||||
const targetY = to.y + to.height / 2, rail = (x + to.x) / 2;
|
||||
path.setAttribute('d', `M ${x} ${y} H ${rail} V ${targetY} H ${to.x}`);
|
||||
} else if (left) {
|
||||
const x = from.x + from.width / 2, y = from.y + from.height;
|
||||
const rail = to.x - 16, targetY = to.y + to.height / 2;
|
||||
const branchY = y + 12;
|
||||
path.setAttribute('d', `M ${x} ${y} V ${branchY} H ${rail} V ${targetY} H ${to.x}`);
|
||||
} else {
|
||||
const x = from.x + from.width / 2, y = from.y + from.height;
|
||||
const targetX = to.x + to.width / 2, rail = (y + to.y) / 2;
|
||||
path.setAttribute('d', `M ${x} ${y} V ${rail} H ${targetX} V ${to.y}`);
|
||||
}
|
||||
path.setAttribute('data-organization-id', node.dataset.organizationId!);
|
||||
path.setAttribute('data-parent-id', node.dataset.parentId!);
|
||||
path.setAttribute('data-direction', left ? 'left' : 'top');
|
||||
svg.append(path);
|
||||
}
|
||||
tree.prepend(svg);
|
||||
}
|
||||
}
|
||||
|
||||
/** Draw routes in unscaled article coordinates. The endpoint defines both the
|
||||
* connector's incoming direction and the single accent side on the card. */
|
||||
function drawEmployeeConnectors(viewport: HTMLElement): void {
|
||||
|
||||
Reference in new issue
Block a user