feat(orgchart): integrate chart-only viewport and scoped guard fixes
Add initial fit and direct wheel zoom with preserved default presentation. Integrate five scoped GuardFix findings and AI generation preselection policy. Exclude Cell experiments and retain five historical guard failures.
This commit is contained in:
1 parent
0a8daf7ee7
commit
d56d587fd2
28 files changed
+1170
-77
No files matched your search
@@ -72,10 +72,10 @@ export function createDepartmentColors(document: Document, context: RenderContex
|
||||
const comparisonTitle = document.createElement('h3'); comparisonTitle.textContent = '텍스트 대비 MODEL · 현재 / 일반 제목 / 미적용 후보';
|
||||
const normal = document.createElement('div'); normal.className = 'hm-orgchart-contrast-normal'; normal.dataset.contrastTarget = 'full-normal'; normal.textContent = preset.label;
|
||||
const candidate = document.createElement('div'); candidate.className = 'hm-orgchart-contrast-candidate'; candidate.dataset.contrastTarget = 'candidate'; candidate.dataset.designStatus = 'UNAPPROVED_NOT_APPLIED'; candidate.textContent = preset.label;
|
||||
candidate.style.color = 'var(--hm-orgchart-contrast-candidate-color, #10243b)';
|
||||
candidate.style.color = '#10243b';
|
||||
const chip = document.createElement('div'); chip.className = 'hm-orgchart-contrast-chip'; chip.dataset.designStatus = 'UNAPPROVED_NOT_APPLIED';
|
||||
const chipLabel = document.createElement('span'); chipLabel.className = 'hm-orgchart-contrast-chip-label'; chipLabel.dataset.contrastTarget = 'candidate-chip'; chipLabel.textContent = preset.label;
|
||||
chipLabel.style.color = 'var(--hm-orgchart-contrast-candidate-color, #10243b)'; chipLabel.style.backgroundColor = 'var(--hm-orgchart-contrast-candidate-chip-background, #ffffff)'; chip.append(chipLabel);
|
||||
chipLabel.style.color = '#10243b'; chipLabel.style.backgroundColor = '#ffffff'; chip.append(chipLabel);
|
||||
const results = document.createElement('dl'); results.className = 'hm-orgchart-contrast-results';
|
||||
for (const [key, label] of [['current', '현재 Story 제목: 실제 크기 기준'], ['full-normal', 'Full 제목 동등 예제: 15px bold 일반 텍스트'], ['candidate', '후보 #10243b: 미승인·미적용'], ['candidate-chip', '후보 white label + #10243b: 미승인·미적용']]) {
|
||||
const term = document.createElement('dt'); term.textContent = label;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createHeading, type RenderContext } from './render.js';
|
||||
import type { RenderEngineNode } from './engine.js';
|
||||
import { getCompanyColorPreset } from '../company-color-presets.js';
|
||||
import { alignExpandedLevels } from './expanded-layout.js';
|
||||
import type { OrgChartWheelZoom } from './types.js';
|
||||
|
||||
/** Auto-only presenter. The source and popup identity remain unchanged. */
|
||||
export function decorateExpandedNode(node: HTMLElement, value: RenderEngineNode, context: RenderContext, signal: AbortSignal): void {
|
||||
@@ -27,7 +28,6 @@ export function decorateExpandedNode(node: HTMLElement, value: RenderEngineNode,
|
||||
node.dataset.verticalLayout = String(vertical);
|
||||
const layoutDirection = context.autoLayout?.type === 'lr' || vertical ? 'left' : 'top';
|
||||
node.style.setProperty('--hm-orgchart-expanded-primary', preset.primary);
|
||||
node.style.setProperty('--hm-orgchart-expanded-title-ink', ['saman', 'jangheon-industry'].includes(companyId) ? 'var(--hm-orgchart-resolved-fg-primary)' : 'var(--hm-orgchart-resolved-on-action-primary)');
|
||||
preset.secondary.forEach((color, index) => node.style.setProperty(`--hm-orgchart-expanded-secondary-${index + 1}`, color));
|
||||
for (const edge of node.querySelectorAll('.edge')) edge.setAttribute('aria-hidden', 'true');
|
||||
const leaderId = top?.employeeId === undefined ? organization.leaderId : top.employeeId;
|
||||
@@ -90,11 +90,13 @@ export function decorateExpandedNode(node: HTMLElement, value: RenderEngineNode,
|
||||
}
|
||||
}
|
||||
|
||||
export function createExpandedOrgChartContent(document: Document, context: RenderContext, tree: HTMLElement, signal: AbortSignal, layoutReady: () => void): HTMLElement {
|
||||
export function createExpandedOrgChartContent(document: Document, context: RenderContext, tree: HTMLElement, signal: AbortSignal, layoutReady: () => void,
|
||||
presentation: { chartOnly?: boolean; wheelZoom?: OrgChartWheelZoom } = {}): HTMLElement {
|
||||
const content = document.createElement('div');
|
||||
content.className = 'hm-orgchart-expanded';
|
||||
content.dataset.component = 'ExpandedOrgChartContent';
|
||||
content.dataset.layoutMode = 'auto';
|
||||
if (presentation.chartOnly) content.dataset.presentationMode = 'chart-only';
|
||||
content.dataset.autoLayout = context.autoLayout?.type ?? 'tb';
|
||||
content.dataset.hybridStartDepth = String(context.autoLayout?.hybridStartDepth ?? 3);
|
||||
content.dataset.initialVisibleDepth = String(context.autoLayout?.initialVisibleDepth ?? 'all');
|
||||
@@ -105,8 +107,10 @@ export function createExpandedOrgChartContent(document: Document, context: Rende
|
||||
const preset = getCompanyColorPreset(company);
|
||||
if (preset) content.style.setProperty(`--hm-orgchart-company-${company}`, preset.primary);
|
||||
}
|
||||
tree.setAttribute('aria-label', '확장형 조직 계층도 · Ctrl 또는 Command + 휠로 확대 · 드래그로 이동');
|
||||
content.append(createHeading(document, context), tree);
|
||||
tree.setAttribute('aria-label', `확장형 조직 계층도 · ${presentation.wheelZoom === 'direct' ? '휠로 확대·축소' : 'Ctrl 또는 Command + 휠로 확대'} · 드래그로 이동`);
|
||||
// Chart Only: titles inside organization nodes remain; the outer heading does not.
|
||||
if (presentation.chartOnly) content.append(tree);
|
||||
else content.append(createHeading(document, context), tree);
|
||||
alignExpandedLevels(tree, signal, layoutReady, context.autoLayout);
|
||||
return content;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { OrgChartViewportState } from './types.js';
|
||||
import type { OrgChartViewportState, OrgChartWheelZoom } from './types.js';
|
||||
|
||||
export interface ExpandedViewport {
|
||||
zoomTo(scale: number, anchor?: { x: number; y: number }): void;
|
||||
@@ -6,18 +6,27 @@ export interface ExpandedViewport {
|
||||
fit(): void;
|
||||
state(): OrgChartViewportState;
|
||||
hasManualPosition(): boolean;
|
||||
fitScale(): number;
|
||||
layoutReady(): void;
|
||||
}
|
||||
|
||||
/** One transformed forest, shared by HTML and React; dialogs live in the shell. */
|
||||
export function createExpandedViewport(viewport: HTMLElement, toolbar: HTMLElement, signal: AbortSignal,
|
||||
config: { min: number; max: number; step: number; zoomEnabled?: boolean; panEnabled?: boolean }, initial?: OrgChartViewportState, centerInitially = true, initialManualPosition = false): ExpandedViewport {
|
||||
/** `toolbar` is optional: Chart Only keeps every viewport interaction without
|
||||
* rendering its buttons, ratio text or guidance. */
|
||||
export function createExpandedViewport(viewport: HTMLElement, toolbar: HTMLElement | undefined, signal: AbortSignal,
|
||||
config: { min: number; max: number; step: number; zoomEnabled?: boolean; panEnabled?: boolean; wheelZoom?: OrgChartWheelZoom; fitFloor?: boolean; initialFitScale?: number },
|
||||
initial?: OrgChartViewportState, initialPlacement?: 'center' | 'fit', initialManualPosition = false): ExpandedViewport {
|
||||
const document = viewport.ownerDocument;
|
||||
const view = document.defaultView!;
|
||||
const forest = viewport.querySelector<HTMLElement>('.hm-orgchart-forest')!;
|
||||
let { scale, x, y } = initial ?? { scale: 1, x: 0, y: 0 };
|
||||
let manualPosition = initialManualPosition;
|
||||
let placement: 'center' | 'fit' | undefined = centerInitially && !manualPosition ? 'center' : undefined;
|
||||
let placement: 'center' | 'fit' | undefined = initialPlacement && !manualPosition ? initialPlacement : undefined;
|
||||
// An initial Fit hides the forest until real bounds and canvas size are
|
||||
// measured, so the first frame never shows an unfitted 100% tree.
|
||||
if (placement === 'fit') viewport.dataset.placementPending = 'true';
|
||||
else delete viewport.dataset.placementPending;
|
||||
let fitScale = config.initialFitScale ?? Infinity;
|
||||
let drag: { id: number; x: number; y: number; moved: boolean } | undefined;
|
||||
let suppressClick = false;
|
||||
let focusFrame = 0;
|
||||
@@ -34,10 +43,14 @@ export function createExpandedViewport(viewport: HTMLElement, toolbar: HTMLEleme
|
||||
viewport.dataset.panX = String(x);
|
||||
viewport.dataset.panY = String(y);
|
||||
output.textContent = `${Math.round(scale * 100)}%`;
|
||||
buttons[0].disabled = !zoomEnabled || scale >= config.max;
|
||||
buttons[1].disabled = !zoomEnabled || scale <= config.min;
|
||||
if (buttons.length) {
|
||||
buttons[0].disabled = !zoomEnabled || scale >= config.max;
|
||||
buttons[1].disabled = !zoomEnabled || scale <= config.min;
|
||||
}
|
||||
};
|
||||
const clamp = (value: number) => Math.max(config.min, Math.min(config.max, value));
|
||||
// With an initial Fit, a large forest can fit below the manual minimum.
|
||||
// Zooming out from there holds the fitted scale instead of jumping inward.
|
||||
const clamp = (value: number) => Math.max(config.fitFloor ? Math.min(config.min, fitScale) : config.min, Math.min(config.max, value));
|
||||
// Engine wrappers reserve asymmetric margins (LR) and empty row space (TB).
|
||||
// Center the complete rendered content, including organization connector rails,
|
||||
// in forest-local coordinates so Pan/Zoom do not affect the measured bounds.
|
||||
@@ -60,12 +73,14 @@ export function createExpandedViewport(viewport: HTMLElement, toolbar: HTMLEleme
|
||||
if (fit) {
|
||||
// Only an explicit Fit shrinks to contain, even below manual Zoom's min.
|
||||
scale = Math.max(Number.EPSILON, Math.min((viewport.clientWidth - 32) / bounds.width, (viewport.clientHeight - 32) / bounds.height, 1));
|
||||
fitScale = scale;
|
||||
}
|
||||
x = (viewport.clientWidth - bounds.width * scale) / 2 - bounds.left * scale;
|
||||
y = (fit ? Math.max(16, (viewport.clientHeight - bounds.height * scale) / 2) : 16) - bounds.top * scale;
|
||||
apply();
|
||||
delete viewport.dataset.placementPending;
|
||||
};
|
||||
const preservePosition = () => { manualPosition = true; placement = undefined; };
|
||||
const preservePosition = () => { manualPosition = true; placement = undefined; delete viewport.dataset.placementPending; };
|
||||
const controller: ExpandedViewport = {
|
||||
zoomTo(next, anchor = { x: viewport.clientWidth / 2, y: viewport.clientHeight / 2 }) {
|
||||
if (!zoomEnabled) return;
|
||||
@@ -82,6 +97,7 @@ export function createExpandedViewport(viewport: HTMLElement, toolbar: HTMLEleme
|
||||
},
|
||||
state: () => ({ scale, x, y }),
|
||||
hasManualPosition: () => manualPosition,
|
||||
fitScale: () => fitScale,
|
||||
layoutReady() { if (placement) position(placement === 'fit'); },
|
||||
};
|
||||
const actions: [string, () => void][] = [
|
||||
@@ -90,24 +106,34 @@ export function createExpandedViewport(viewport: HTMLElement, toolbar: HTMLEleme
|
||||
['100% 복원', () => { preservePosition(); scale = 1; x = 16; y = 16; apply(); }],
|
||||
['Fit to View', () => controller.fit()],
|
||||
];
|
||||
toolbar.setAttribute('role', 'group');
|
||||
toolbar.setAttribute('aria-label', '조직도 확대·이동 도구');
|
||||
for (const [label, action] of actions) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button'; button.textContent = label;
|
||||
button.addEventListener('click', action, { signal });
|
||||
buttons.push(button); toolbar.append(button);
|
||||
const directWheel = config.wheelZoom === 'direct';
|
||||
if (toolbar) {
|
||||
toolbar.setAttribute('role', 'group');
|
||||
toolbar.setAttribute('aria-label', '조직도 확대·이동 도구');
|
||||
for (const [label, action] of actions) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button'; button.textContent = label;
|
||||
button.addEventListener('click', action, { signal });
|
||||
buttons.push(button); toolbar.append(button);
|
||||
}
|
||||
toolbar.append(output);
|
||||
const hint = document.createElement('span');
|
||||
hint.textContent = [panEnabled ? '드래그·방향키 이동' : '', zoomEnabled ? directWheel ? '휠 확대·축소' : 'Ctrl/⌘ + 휠 확대' : ''].filter(Boolean).join(' · ');
|
||||
toolbar.append(hint);
|
||||
}
|
||||
toolbar.append(output);
|
||||
const hint = document.createElement('span');
|
||||
hint.textContent = [panEnabled ? '드래그·방향키 이동' : '', zoomEnabled ? 'Ctrl/⌘ + 휠 확대' : ''].filter(Boolean).join(' · ');
|
||||
toolbar.append(hint);
|
||||
// The listener lives on the canvas only: outside it, the page scrolls as usual.
|
||||
viewport.addEventListener('wheel', event => {
|
||||
if (!zoomEnabled) return;
|
||||
if (!event.ctrlKey && !event.metaKey) return;
|
||||
if (!directWheel && !event.ctrlKey && !event.metaKey) return;
|
||||
// Direct mode normalizes line/page deltas and caps one event so a
|
||||
// high-resolution wheel cannot jump across the zoom range.
|
||||
const delta = directWheel
|
||||
? Math.max(-120, Math.min(120, event.deltaY * (event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? viewport.clientHeight : 1)))
|
||||
: event.deltaY;
|
||||
if (directWheel && !delta) return;
|
||||
event.preventDefault();
|
||||
const box = viewport.getBoundingClientRect();
|
||||
controller.zoomTo(scale * Math.exp(-event.deltaY * 0.002), { x: event.clientX - box.left, y: event.clientY - box.top });
|
||||
controller.zoomTo(scale * Math.exp(-delta * 0.002), { x: event.clientX - box.left, y: event.clientY - box.top });
|
||||
}, { passive: false, signal });
|
||||
viewport.addEventListener('keydown', event => {
|
||||
if (event.target !== viewport) return;
|
||||
|
||||
@@ -70,6 +70,14 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
if (Object.values(employeeConnections).some(value => !['top', 'left', 'right'].includes(value))) throw new TypeError('employeeConnections must use top, left or right');
|
||||
const zoom = { min: options.zoom?.min ?? 0.05, max: options.zoom?.max ?? 3, step: options.zoom?.step ?? 1.2 };
|
||||
if (expanded && (!Number.isFinite(zoom.min) || !Number.isFinite(zoom.max) || !Number.isFinite(zoom.step) || zoom.min <= 0 || zoom.min > 1 || zoom.max < 1 || zoom.min >= zoom.max || zoom.step <= 1)) throw new TypeError('zoom requires 0 < min <= 1 <= max and step > 1');
|
||||
if (options.presentationMode !== undefined && options.presentationMode !== 'default' && options.presentationMode !== 'chart-only') throw new TypeError('presentationMode must be "default" or "chart-only"');
|
||||
if (options.initialView !== undefined && options.initialView !== 'center' && options.initialView !== 'fit') throw new TypeError('initialView must be "center" or "fit"');
|
||||
if (options.wheelZoom !== undefined && options.wheelZoom !== 'modifier' && options.wheelZoom !== 'direct') throw new TypeError('wheelZoom must be "modifier" or "direct"');
|
||||
// Fixed owns the approved Workdesk composition; never strip it silently.
|
||||
if (view === 'composition' && options.presentationMode === 'chart-only' && !expanded) throw new TypeError('presentationMode "chart-only" requires layoutMode "auto"');
|
||||
const chartOnly = expanded && options.presentationMode === 'chart-only';
|
||||
const initialView = options.initialView ?? (chartOnly ? 'fit' : 'center');
|
||||
const wheelZoom = options.wheelZoom ?? (chartOnly ? 'direct' : 'modifier');
|
||||
const copySelection = (ids?: readonly string[]) => {
|
||||
if (ids !== undefined && (!Array.isArray(ids) || ids.some(id => typeof id !== 'string' || !getCompanyColorPreset(id)))) throw new TypeError('selectedCompanyIds must contain registered company IDs');
|
||||
return ids === undefined ? undefined : [...new Set(ids)];
|
||||
@@ -327,6 +335,7 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
if (LoadedEngine) Engine = LoadedEngine;
|
||||
const document = content!.ownerDocument;
|
||||
const previousViewport = expandedViewport?.state();
|
||||
const previousFitScale = expandedViewport?.fitScale();
|
||||
const previousManualPosition = expandedViewport?.hasManualPosition();
|
||||
employeeDialog?.close();
|
||||
contentGeneration?.abort();
|
||||
@@ -354,18 +363,24 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
}
|
||||
// If regeneration removed keyboard focus, offer the stable viewport next.
|
||||
const nextContent = view === 'composition' ? expanded
|
||||
? createExpandedOrgChartContent(document, nextContext, viewport!, contentGeneration.signal, () => expandedViewport?.layoutReady())
|
||||
? createExpandedOrgChartContent(document, nextContext, viewport!, contentGeneration.signal, () => expandedViewport?.layoutReady(), { chartOnly, wheelZoom })
|
||||
: createOrgChartContent(document, nextContext, viewport!, contentGeneration.signal)
|
||||
: view === 'tree' ? viewport!
|
||||
: view === 'positionSummary' ? createPositionSummary(document, nextContext)
|
||||
: view === 'affiliationSummary' ? createAffiliationSummary(document, nextContext)
|
||||
: createStatusSummary(document, nextContext, contentGeneration.signal);
|
||||
if (expanded) {
|
||||
const toolbar = document.createElement('div');
|
||||
toolbar.className = 'hm-orgchart-expanded-toolbar';
|
||||
nextContent.insertBefore(toolbar, viewport!);
|
||||
// Chart Only keeps the same viewport controller without its visible toolbar.
|
||||
const toolbar = chartOnly ? undefined : document.createElement('div');
|
||||
if (toolbar) {
|
||||
toolbar.className = 'hm-orgchart-expanded-toolbar';
|
||||
nextContent.insertBefore(toolbar, viewport!);
|
||||
}
|
||||
// A fitted replacement starts from the new forest, not the previous Pan/Zoom.
|
||||
const resetView = fitNext && initialView === 'fit';
|
||||
expandedViewport = createExpandedViewport(viewport!, toolbar, contentGeneration.signal,
|
||||
{ ...zoom, zoomEnabled: interactionOptions.zoom, panEnabled: interactionOptions.pan }, previousViewport, fitNext, previousManualPosition);
|
||||
{ ...zoom, zoomEnabled: interactionOptions.zoom, panEnabled: interactionOptions.pan, wheelZoom, fitFloor: initialView === 'fit', initialFitScale: resetView ? undefined : previousFitScale },
|
||||
resetView ? undefined : previousViewport, fitNext ? initialView : undefined, !resetView && !!previousManualPosition);
|
||||
fitNext = false;
|
||||
const feedback = document.createElement('p');
|
||||
feedback.className = 'hm-orgchart-move-feedback'; feedback.id = `${prefix}-move-feedback`;
|
||||
@@ -383,13 +398,15 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
else source.removeAttribute('aria-keyshortcuts');
|
||||
source.setAttribute('aria-describedby', feedback.id);
|
||||
}
|
||||
const selection = document.createElement('p');
|
||||
selection.className = 'hm-orgchart-expanded-selection';
|
||||
selection.setAttribute('aria-live', 'polite');
|
||||
const selected = new Set(roots.map(root => companyState!.companies.get(root.organization.id)!));
|
||||
const employeeCount = data.employees.filter(employee => selected.has(companyState!.companies.get(employee.organizationId)!)).length;
|
||||
selection.textContent = `선택 ${selected.size}개 회사 · ${employeeCount}명 / 원본 ${data.employees.length}명`;
|
||||
nextContent.insertBefore(selection, toolbar);
|
||||
if (toolbar) {
|
||||
const selection = document.createElement('p');
|
||||
selection.className = 'hm-orgchart-expanded-selection';
|
||||
selection.setAttribute('aria-live', 'polite');
|
||||
const selected = new Set(roots.map(root => companyState!.companies.get(root.organization.id)!));
|
||||
const employeeCount = data.employees.filter(employee => selected.has(companyState!.companies.get(employee.organizationId)!)).length;
|
||||
selection.textContent = `선택 ${selected.size}개 회사 · ${employeeCount}명 / 원본 ${data.employees.length}명`;
|
||||
nextContent.insertBefore(selection, toolbar);
|
||||
}
|
||||
if (!roots.length) {
|
||||
const empty = document.createElement('p');
|
||||
empty.className = 'hm-orgchart-expanded-empty';
|
||||
@@ -447,6 +464,7 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
root = document.createElement('div');
|
||||
root.className = 'hm-orgchart-host';
|
||||
root.dataset.orgchartInstance = prefix;
|
||||
if (chartOnly) root.dataset.presentationMode = 'chart-only';
|
||||
const shadow = root.attachShadow({ mode: 'open' });
|
||||
const styleDisposers: (() => void)[] = [];
|
||||
const styles = [...(needsEngine ? [{ key: 'engine', href: stylesheetUrls.engine }] : []), { key: 'core', href: stylesheetUrls.core }];
|
||||
@@ -472,6 +490,7 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
shell = document.createElement('section');
|
||||
shell.className = 'hm-orgchart-shell';
|
||||
shell.dataset.layoutMode = view === 'composition' ? layoutMode : 'fixed';
|
||||
if (chartOnly) shell.dataset.presentationMode = 'chart-only';
|
||||
// The Core's Korean labels must shape identically in ko HTML and en Storybook hosts.
|
||||
shell.lang = 'ko';
|
||||
shell.tabIndex = -1;
|
||||
|
||||
@@ -50,6 +50,17 @@ export interface OrgChartTopNode {
|
||||
}
|
||||
export interface OrgChartViewportState { scale: number; x: number; y: number }
|
||||
|
||||
/** Auto only. 'chart-only' renders a white canvas with nodes and connectors:
|
||||
* no heading, as-of date, company/count summary, toolbar or visible guidance.
|
||||
* Accessible live status stays visually hidden. Fixed rejects it. */
|
||||
export type OrgChartPresentationMode = 'default' | 'chart-only';
|
||||
/** Auto only. Placement on first render and on full data/layout replacement.
|
||||
* 'fit' contains the whole forest (never above 100%) and centers it. */
|
||||
export type OrgChartInitialView = 'center' | 'fit';
|
||||
/** Auto only. 'modifier' zooms with Ctrl/⌘ + wheel; 'direct' zooms with the
|
||||
* plain wheel inside the canvas. Both anchor at the pointer. */
|
||||
export type OrgChartWheelZoom = 'modifier' | 'direct';
|
||||
|
||||
export interface OrgChartEmployeeClickContext {
|
||||
readonly organization: Organization | undefined;
|
||||
readonly affiliation: Affiliation | undefined;
|
||||
@@ -84,6 +95,12 @@ export interface OrgChartOptions {
|
||||
* from above. LR always routes employees from the left. */
|
||||
employeeConnections?: Readonly<Record<string, OrgChartConnectionDirection>>;
|
||||
zoom?: { min?: number; max?: number; step?: number };
|
||||
/** Auto only. Defaults to 'default' (existing heading, summary and toolbar). */
|
||||
presentationMode?: OrgChartPresentationMode;
|
||||
/** Auto only. Defaults to 'fit' in chart-only, otherwise 'center'. */
|
||||
initialView?: OrgChartInitialView;
|
||||
/** Auto only. Defaults to 'direct' in chart-only, otherwise 'modifier'. */
|
||||
wheelZoom?: OrgChartWheelZoom;
|
||||
/** No entry (or an empty URL) keeps an ordinary title. HTTP(S) and relative URLs are supported. */
|
||||
organizationLinks?: OrgChartOrganizationLinks;
|
||||
/** Optional host-owned information UI. When supplied, it replaces the default read-only dialog.
|
||||
|
||||
@@ -2,6 +2,6 @@ export * from './data/index.js';
|
||||
export { companyColorPresets, getCompanyColorPreset } from './company-color-presets.js';
|
||||
export type { CompanyColorPreset, CompanyId } from './company-color-presets.js';
|
||||
export { createOrgChart } from './core/index.js';
|
||||
export type { OrgChartInteractionOptions, OrgChartAutoLayoutType, OrgChartAutoLayoutOptions, OrgChartConnectionDirection, OrgChartTopNode, OrgChartViewportState, OrgChartDisplayMode, OrgChartLayoutMode, OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartOrganizationLinks, OrgChartState } from './core/types.js';
|
||||
export type { OrgChartInteractionOptions, OrgChartPresentationMode, OrgChartInitialView, OrgChartWheelZoom, OrgChartAutoLayoutType, OrgChartAutoLayoutOptions, OrgChartConnectionDirection, OrgChartTopNode, OrgChartViewportState, OrgChartDisplayMode, OrgChartLayoutMode, OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartOrganizationLinks, OrgChartState } from './core/types.js';
|
||||
export { createOrgChartTree, createPositionSummary, createAffiliationSummary, createStatusSummary } from './components.js';
|
||||
export type { OrgChartSummaryComponentOptions } from './components.js';
|
||||
@@ -273,3 +273,24 @@
|
||||
.hm-orgchart-expanded .node[data-drop-state="blocked"] > .hm-orgchart-organization { outline: 1px dotted var(--hm-orgchart-resolved-stroke-default); outline-offset: 2px; }
|
||||
.hm-orgchart-expanded .node[data-drop-hover="allowed"] > .hm-orgchart-organization { outline: 3px solid var(--hm-orgchart-resolved-action-primary); }
|
||||
.hm-orgchart-expanded .node[data-drop-hover="blocked"] > .hm-orgchart-organization { outline: 3px double var(--hm-orgchart-resolved-fg-secondary); }
|
||||
|
||||
/* Chart Only (presentationMode="chart-only"): one white canvas that fills the
|
||||
host container. Default Expanded rules above are unchanged. The host sets
|
||||
the canvas size; without a definite height the 320px minimum applies. */
|
||||
:host([data-presentation-mode="chart-only"]) { height: 100%; }
|
||||
.hm-orgchart-shell[data-presentation-mode="chart-only"] { height: 100%; background: var(--hm-orgchart-resolved-bg-surface); }
|
||||
.hm-orgchart-shell[data-presentation-mode="chart-only"] > .hm-orgchart-content { height: 100%; }
|
||||
/* Loading/error status overlays the canvas instead of shifting it. */
|
||||
.hm-orgchart-shell[data-presentation-mode="chart-only"] > .hm-orgchart-state { position: absolute; top: var(--hm-orgchart-resolved-spacing-lg); left: var(--hm-orgchart-resolved-spacing-lg); z-index: 1; margin: 0; }
|
||||
.hm-orgchart-expanded[data-presentation-mode="chart-only"] {
|
||||
display: block; height: 100%; padding: 0; border: 0; border-radius: 0;
|
||||
background: var(--hm-orgchart-resolved-bg-surface);
|
||||
}
|
||||
.hm-orgchart-expanded[data-presentation-mode="chart-only"] .hm-orgchart-viewport { height: 100%; min-height: 320px; border: 0; border-radius: 0; }
|
||||
.hm-orgchart-expanded[data-presentation-mode="chart-only"] .hm-orgchart-viewport:focus-visible { outline-offset: -2px; }
|
||||
/* Live move status remains available to assistive technology only. */
|
||||
.hm-orgchart-expanded[data-presentation-mode="chart-only"] .hm-orgchart-move-feedback {
|
||||
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
|
||||
clip-path: inset(50%); white-space: nowrap; border: 0;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-viewport[data-placement-pending] .hm-orgchart-forest { visibility: hidden; }
|
||||
Reference in new issue
Block a user