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,10 +1,17 @@
|
||||
import type OrgChart from 'orgchart';
|
||||
import type { EngineNode } from 'orgchart';
|
||||
import type { EngineNode, EngineOptions } from 'orgchart';
|
||||
|
||||
export type RenderEngineNode = EngineNode;
|
||||
export interface EngineEditing {
|
||||
canDrop(source: HTMLElement, target: HTMLElement): boolean;
|
||||
onDrop(source: HTMLElement, target: HTMLElement): void;
|
||||
}
|
||||
export interface EngineAdapter {
|
||||
update(data: RenderEngineNode[]): void;
|
||||
toggle(node: HTMLElement): boolean | undefined;
|
||||
prepareDrag(source: HTMLElement): void;
|
||||
drop(source: HTMLElement, target: HTMLElement): void;
|
||||
cancelDrag(): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
@@ -51,6 +58,8 @@ export function createEngineAdapter(
|
||||
viewport: HTMLElement,
|
||||
data: RenderEngineNode[],
|
||||
decorate: (node: HTMLElement, data: RenderEngineNode, signal: AbortSignal) => void,
|
||||
layoutOptions: Pick<EngineOptions, 'direction' | 'verticalLevel' | 'visibleLevel'> = {},
|
||||
editing?: EngineEditing,
|
||||
): EngineAdapter {
|
||||
const Abort = viewport.ownerDocument.defaultView?.AbortController ?? AbortController;
|
||||
let generation = new Abort();
|
||||
@@ -59,7 +68,12 @@ export function createEngineAdapter(
|
||||
const forest = viewport.ownerDocument.createElement('div');
|
||||
forest.className = 'hm-orgchart-forest';
|
||||
viewport.append(forest);
|
||||
function cancelDrag(): void {
|
||||
if (!editing) return;
|
||||
for (const instance of instances) instance.dragendHandler();
|
||||
}
|
||||
function releaseGeneration(): void {
|
||||
cancelDrag();
|
||||
generation.abort();
|
||||
for (const instance of instances) {
|
||||
instance.disconnectInitObserver();
|
||||
@@ -84,8 +98,10 @@ export function createEngineAdapter(
|
||||
treeContainer.className = 'hm-orgchart-tree';
|
||||
forest.append(treeContainer);
|
||||
const instance = new Engine(treeContainer, {
|
||||
...layoutOptions,
|
||||
data: root, pan: false, zoom: false, minimap: false, draggable: false, exportButton: false,
|
||||
chartClass: 'hm-orgchart-engine',
|
||||
...(editing ? { dropCriteria: (source: HTMLElement, _parent: HTMLElement | null, target: HTMLElement) => editing.canDrop(source, target) } : {}),
|
||||
nodeTemplate: () => '<article class="hm-orgchart-organization"></article>',
|
||||
createNode: (node, value) => decorate(node, value, generation.signal),
|
||||
});
|
||||
@@ -108,6 +124,23 @@ export function createEngineAdapter(
|
||||
}
|
||||
function bindGeneration(): void {
|
||||
forest.addEventListener('transitionend', syncButtons, { signal: generation.signal });
|
||||
if (!editing) return;
|
||||
for (const instance of instances) {
|
||||
// Core owns immutable data and rerendering. Reuse upstream drop events,
|
||||
// but cancel its DOM/data mutation before either horizontal or hybrid path.
|
||||
const commitDrop = (event: Event) => {
|
||||
event.preventDefault();
|
||||
const detail = (event as CustomEvent<{ draggedNode?: HTMLElement; draggedItem?: HTMLElement; dropZone: HTMLElement }>).detail;
|
||||
if (!detail) return;
|
||||
const source = detail.draggedNode ?? detail.draggedItem;
|
||||
const target = detail.dropZone;
|
||||
if (source && target && instance.chart?.contains(source) && instances.some(candidate => candidate.chart?.contains(target)) && editing.canDrop(source, target)) {
|
||||
editing.onDrop(source, target);
|
||||
}
|
||||
};
|
||||
instance.chart?.addEventListener('nodedrop.orgchart', commitDrop, { signal: generation.signal });
|
||||
instance.chart?.addEventListener('otherdropped.orgchart', commitDrop, { signal: generation.signal });
|
||||
}
|
||||
}
|
||||
build(data);
|
||||
bindGeneration();
|
||||
@@ -133,6 +166,25 @@ export function createEngineAdapter(
|
||||
syncButtons();
|
||||
return !expanded;
|
||||
},
|
||||
prepareDrag(source) {
|
||||
if (disposed || !editing) return;
|
||||
cancelDrag();
|
||||
if (!instances.some(candidate => candidate.chart?.contains(source))) return;
|
||||
// Independent company trees share one editing policy. Upstream caches the
|
||||
// source and filters targets within its own chart, so filter each tree.
|
||||
for (const instance of instances) instance.filterAllowedDropNodes(source);
|
||||
},
|
||||
drop(source, target) {
|
||||
if (disposed || !editing) return;
|
||||
const instance = instances.find(candidate => candidate.chart?.contains(source));
|
||||
if (!instance || !instances.some(candidate => candidate.chart?.contains(target)) || !editing.canDrop(source, target)) { cancelDrag(); return; }
|
||||
// Refresh upstream's cached source and allowed targets for the final data
|
||||
// policy check. Pointer coordinates and thresholds stay in the Core UI.
|
||||
for (const candidate of instances) candidate.filterAllowedDropNodes(source);
|
||||
void instance.dropHandler({ dropZone: target });
|
||||
cancelDrag();
|
||||
},
|
||||
cancelDrag,
|
||||
dispose() {
|
||||
if (disposed) return;
|
||||
disposed = true;
|
||||
|
||||
Reference in new issue
Block a user