feat(org-chart): separate fixed and auto layout previews
This commit is contained in:
1 parent
80583eb4ca
commit
a92913d6bc
18 files changed
+1211
-7
No files matched your search
@@ -44,6 +44,8 @@ Named runtime export: `OrgChart`. Type exports: `OrgChartProps` and the Core's p
|
||||
- Required `dataSource`: the same Core `OrgChartDataSource` interface, `load(options?: { signal?: AbortSignal }): Promise<unknown>`.
|
||||
- Required `stylesheetUrls`: `{ engine: string; core: string }`, the exact Core asset contract.
|
||||
- Optional `displayMode`: `'interactive'` (default) or `'readOnly'`, passed to the Core composition. Changing it recreates the Core handle.
|
||||
- Optional `layoutMode`: `'fixed'` (default) or `'auto'`, independent of `displayMode`. Auto is the P5-1A initial-group preview; automatic recursive layout, collapse and Zoom/Pan remain deferred. Changing it recreates the same Core handle.
|
||||
- Optional `companyId`: existing Expanded company preset ID (default `hanmac`). Changing it recreates the handle; Fixed ignores company colors. Unknown Auto IDs report a Core construction lifecycle error. All seven IDs and preview limits are documented in the [Core README](../org-chart/README.md#layout-boundary-p5-1a).
|
||||
- Optional `organizationLinks`: existing organization-ID-to-URL map, applied only to original 1Depth titles. Missing/blank values retain plain titles. Core validates HTTP(S)/relative URLs and rejects invalid URLs, other schemes and credentials. Treat the map as immutable and memoize it; a new map recreates the handle.
|
||||
- Optional `onEmployeeClick(employee, context)`: host-owned information UI, replacing the Core's built-in popup. `context` contains the existing `organization`, `affiliation`, focusable `trigger` card and `originalEvent`. It works in both modes and receives untouched Employee data.
|
||||
- Optional `aggregationPolicy`: the Core's `ApprovedAggregationPolicy`.
|
||||
|
||||
@@ -18,6 +18,8 @@ export function OrgChart({
|
||||
aggregationPolicy,
|
||||
stylesheetUrls,
|
||||
displayMode,
|
||||
layoutMode,
|
||||
companyId,
|
||||
organizationLinks,
|
||||
onEmployeeClick,
|
||||
className,
|
||||
@@ -62,6 +64,8 @@ export function OrgChart({
|
||||
aggregationPolicy,
|
||||
stylesheetUrls: { engine: engineStylesheetUrl, core: coreStylesheetUrl },
|
||||
displayMode,
|
||||
layoutMode,
|
||||
companyId,
|
||||
organizationLinks,
|
||||
onEmployeeClick: hasEmployeeCallback ? (employee, context) => {
|
||||
if (generationRef.current === generation) committedRef.current.onEmployeeClick?.(employee, context);
|
||||
@@ -101,7 +105,7 @@ export function OrgChart({
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [aggregationPolicy, engineStylesheetUrl, coreStylesheetUrl, displayMode, organizationLinks, hasEmployeeCallback]);
|
||||
}, [aggregationPolicy, engineStylesheetUrl, coreStylesheetUrl, displayMode, layoutMode, companyId, organizationLinks, hasEmployeeCallback]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const instance = instanceRef.current;
|
||||
|
||||
@@ -24,6 +24,39 @@ chart.destroy();
|
||||
|
||||
## Organization links and employee information (P3)
|
||||
|
||||
### Layout boundary (P5-1A)
|
||||
|
||||
`layoutMode?: 'fixed' | 'auto'` is independent of `displayMode`. The default
|
||||
`'fixed'` keeps the existing Workdesk composition and its display-mode behavior.
|
||||
`'auto'` currently selects the **P5-1A initial preview**, showing the representative
|
||||
and immediate organization groups from the validated hierarchy. Descendants stay
|
||||
in the dataset and statistics but are not placed in this preview. Automatic
|
||||
recursive placement/recalculation/collision prevention belongs to P5-1B;
|
||||
collapse controls and Zoom/Pan remain deferred. No preview readiness state claims
|
||||
those later features are complete.
|
||||
|
||||
Use `companyId` with Auto to select an existing company preset (default `hanmac`).
|
||||
The seven IDs are `hanmac`, `saman`, `jangheon-industry`, `jangheon`, `ptc`, `halla`,
|
||||
and `baron`. Main colors style only Expanded group headers; indexed secondary
|
||||
variables come from that same preset. Fixed ignores this option and preserves
|
||||
its organization gradients. Unknown Auto IDs reject construction with `TypeError`.
|
||||
The preview reuses approved Workdesk card presentation, the same data/engine and
|
||||
employee dialog; it has an independent wrapper without Fixed coordinates or scaling.
|
||||
Choose `displayMode: 'readOnly'` for the matching read-only popup name policy.
|
||||
|
||||
```js
|
||||
const chart = createOrgChart({
|
||||
...configuredBaseOptions,
|
||||
layoutMode: 'auto',
|
||||
displayMode: 'readOnly',
|
||||
companyId: 'hanmac',
|
||||
});
|
||||
```
|
||||
|
||||
Options are captured at construction: create a new handle to change the layout or
|
||||
company. Standalone Tree/Summary factories keep their existing rendering paths.
|
||||
|
||||
|
||||
### P3.2 follow-up: representative badges
|
||||
|
||||
Representative cards never render job-title, role or status badges, empty attribute wrappers or a badge row, including standalone specimens. They display `대표이사` / `홍 길 동`, retaining the approved 146×74 asset and existing typography, with no gap between position and name. The fixture's raw representative position (`경영진`), employee data and dialog fields remain unchanged. Other 1Depth/2Depth badge policies are preserved. This display policy takes precedence over the historical P3.2/P3.1 notes below.
|
||||
|
||||
@@ -30,7 +30,7 @@ execFileSync(process.execPath, [path.resolve(path.dirname(typescriptManifest), t
|
||||
// Ship one self-contained public CSS asset. ?url and file:// consumers cannot
|
||||
// rely on source-relative imports or a bundler to discover private style files.
|
||||
const coreStyles = await readFile(path.join(root, 'src/styles/org-chart.css'), 'utf8');
|
||||
const inlinedStyles = await Promise.all(['design-tokens.css', 'employee-card.css', 'summary-sections.css', 'org-chart-content.css', 'org-chart-readonly.css', 'org-chart-p3-summary.css', 'employee-info-dialog.css'].map(async file => [file, await readFile(path.join(root, 'src/styles', file), 'utf8')]));
|
||||
const inlinedStyles = await Promise.all(['design-tokens.css', 'employee-card.css', 'summary-sections.css', 'org-chart-content.css', 'org-chart-readonly.css', 'org-chart-p3-summary.css', 'employee-info-dialog.css', 'org-chart-expanded.css'].map(async file => [file, await readFile(path.join(root, 'src/styles', file), 'utf8')]));
|
||||
let publicStyles = coreStyles;
|
||||
for (const [file, css] of inlinedStyles) publicStyles = publicStyles.replace(`@import './${file}';`, css);
|
||||
await writeFile(path.join(root, 'dist/style.css'), publicStyles);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { createEmployeeCard } from './employee-card.js';
|
||||
import { createHeading, type RenderContext } from './render.js';
|
||||
import { representativeCardAsset } from './representative-card-asset.js';
|
||||
|
||||
/** P5-1A presenter boundary. The shared Core supplies the initial engine groups;
|
||||
* this presenter never assigns coordinates or interprets parentId. A later
|
||||
* layout adapter can replace group placement without duplicating card or data
|
||||
* rendering in React and Vanilla consumers. */
|
||||
export function createExpandedOrgChartContent(
|
||||
document: Document, context: RenderContext, tree: HTMLElement, signal: AbortSignal,
|
||||
): HTMLElement {
|
||||
const content = document.createElement('div');
|
||||
content.className = 'hm-orgchart-expanded';
|
||||
content.dataset.component = 'ExpandedOrgChartContent';
|
||||
content.dataset.layoutMode = 'auto';
|
||||
content.dataset.layoutStatus = 'preview';
|
||||
|
||||
const heading = createHeading(document, context);
|
||||
const status = document.createElement('p');
|
||||
status.className = 'hm-orgchart-expanded-stage-note';
|
||||
status.textContent = 'P5-1A · 확장형 준비 화면';
|
||||
const description = document.createElement('p');
|
||||
description.className = 'hm-orgchart-expanded-description';
|
||||
description.textContent = '대표자와 1Depth 예시 그룹을 표시합니다. 하위 계층 자동 배치와 접기·펼치기, Zoom/Pan은 후속 단계에서 구현합니다.';
|
||||
content.append(heading, status, description);
|
||||
|
||||
const leaders = document.createElement('section');
|
||||
leaders.className = 'hm-orgchart-expanded-top-leaders';
|
||||
leaders.setAttribute('aria-label', '최상위 조직 리더');
|
||||
for (const id of context.topLeaderIds) {
|
||||
const employee = context.data.employees.find(item => item.id === id);
|
||||
if (!employee) continue;
|
||||
leaders.append(createEmployeeCard(document, employee, {
|
||||
kind: 'topLeader',
|
||||
workdeskPresentation: true,
|
||||
cardAsset: representativeCardAsset,
|
||||
affiliationName: context.data.affiliations.find(item => item.id === employee.affiliationId)?.name,
|
||||
}, signal));
|
||||
}
|
||||
if (leaders.childElementCount) content.append(leaders);
|
||||
|
||||
// Keep the stable Core viewport for keyboard access and ordinary scrolling.
|
||||
// This is a flow preview, with no fixed-stage scale observer or artwork.
|
||||
tree.setAttribute('aria-label', '확장형 조직도 초기 그룹 영역');
|
||||
content.append(tree);
|
||||
return content;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { expandedPreviewRoots } from './expanded-layout.js';
|
||||
import { createOrgChart } from './index.js';
|
||||
import type { OrganizationTreeNode } from '../data/index.js';
|
||||
|
||||
const node = (id: string, parentId: string | null, type: 'root' | 'office' = 'office', children: OrganizationTreeNode[] = []): OrganizationTreeNode => ({
|
||||
organization: { id, parentId, name: id, type, leaderId: null, displayOrder: 0 }, children,
|
||||
});
|
||||
|
||||
test('initial preview keeps actual first-depth ancestry and defers descendants without mutating hierarchy', () => {
|
||||
const grandchild = node('depth-2', 'depth-1');
|
||||
const child = node('depth-1', 'root', 'office', [grandchild]);
|
||||
const roots = [node('root', null, 'root', [child])];
|
||||
const before = structuredClone(roots);
|
||||
const projected = expandedPreviewRoots(roots);
|
||||
assert.deepEqual(projected.map(item => item.organization.id), ['depth-1']);
|
||||
assert.equal(projected[0]!.organization.parentId, 'root');
|
||||
assert.deepEqual(projected[0]!.children, []);
|
||||
assert.deepEqual(roots, before);
|
||||
});
|
||||
|
||||
test('independent roots and normalized order stay independent in the initial preview', () => {
|
||||
const roots = [node('first', null), node('root', null, 'root', [node('second', 'root'), node('third', 'root')])];
|
||||
assert.deepEqual(expandedPreviewRoots(roots).map(item => item.organization.id), ['first', 'second', 'third']);
|
||||
assert.deepEqual(expandedPreviewRoots([]), []);
|
||||
});
|
||||
|
||||
const options = { dataSource: { load: async () => ({}) }, stylesheetUrls: { engine: '/engine.css', core: '/style.css' } };
|
||||
|
||||
test('layout defaults remain compatible and displayMode is an independent public option', () => {
|
||||
for (const displayMode of ['interactive', 'readOnly'] as const) {
|
||||
for (const layoutMode of [undefined, 'fixed', 'auto'] as const) {
|
||||
const handle = createOrgChart({ ...options, displayMode, layoutMode });
|
||||
assert.equal(handle.getState().status, 'idle');
|
||||
handle.destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('invalid layout and unknown Auto company fail explicitly, while Fixed ignores company configuration', () => {
|
||||
assert.throws(() => createOrgChart({ ...options, layoutMode: 'invalid' as 'auto' }), /layoutMode/);
|
||||
assert.throws(() => createOrgChart({ ...options, layoutMode: 'auto', companyId: 'unknown' }), /companyId/);
|
||||
const fixed = createOrgChart({ ...options, layoutMode: 'fixed', companyId: 'unknown' });
|
||||
assert.equal(fixed.getState().status, 'idle');
|
||||
fixed.destroy();
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { OrganizationTreeNode } from '../data/index.js';
|
||||
|
||||
/** P5-1A preview projection only. Keep the validated hierarchy intact and
|
||||
* expose the root's immediate groups without pretending to place descendants.
|
||||
* P5-1B can replace this boundary with a recursive layout result shared by
|
||||
* every runtime; neither React nor the Story owns a layout algorithm. */
|
||||
export function expandedPreviewRoots(roots: readonly OrganizationTreeNode[]): OrganizationTreeNode[] {
|
||||
return roots.flatMap(root => root.organization.type === 'root' ? root.children : [root])
|
||||
.map(node => ({ organization: node.organization, children: [] }));
|
||||
}
|
||||
@@ -5,6 +5,9 @@ import type { EngineAdapter, EngineConstructor } from './engine.js';
|
||||
import { decorateNode, engineTree } from './render.js';
|
||||
import { createOrgChartTree } from './org-chart-tree.js';
|
||||
import { createOrgChartContent } from './org-chart-content.js';
|
||||
import { createExpandedOrgChartContent } from './expanded-content.js';
|
||||
import { expandedPreviewRoots } from './expanded-layout.js';
|
||||
import { getCompanyColorPreset } from '../company-color-presets.js';
|
||||
import { createPositionSummary } from './position-summary.js';
|
||||
import { createAffiliationSummary } from './affiliation-summary.js';
|
||||
import { createStatusSummary } from './status-summary.js';
|
||||
@@ -13,7 +16,7 @@ import type { EmployeeInfoDialog } from './employee-info-dialog.js';
|
||||
import type { RenderContext } from './render.js';
|
||||
import type { OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartState } from './types.js';
|
||||
|
||||
export type { OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartOrganizationLinks, OrgChartState } from './types.js';
|
||||
export type { OrgChartLayoutMode, OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartOrganizationLinks, OrgChartState } from './types.js';
|
||||
|
||||
const owners = new WeakMap<HTMLElement, symbol>();
|
||||
let instanceSequence = 0;
|
||||
@@ -47,6 +50,11 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
if (!options.stylesheetUrls?.core || (needsEngine && !options.stylesheetUrls.engine)) throw new TypeError('Provide the required OrgChart stylesheet URLs');
|
||||
const stylesheetUrls = { ...options.stylesheetUrls };
|
||||
if (options.displayMode !== undefined && options.displayMode !== 'interactive' && options.displayMode !== 'readOnly') throw new TypeError('displayMode must be "interactive" or "readOnly"');
|
||||
if (options.layoutMode !== undefined && options.layoutMode !== 'fixed' && options.layoutMode !== 'auto') throw new TypeError('layoutMode must be "fixed" or "auto"');
|
||||
const layoutMode = options.layoutMode ?? 'fixed';
|
||||
const expanded = view === 'composition' && layoutMode === 'auto';
|
||||
const expandedCompanyPreset = expanded ? getCompanyColorPreset(options.companyId ?? 'hanmac') : undefined;
|
||||
if (expanded && !expandedCompanyPreset) throw new TypeError('companyId must be a registered company preset in Auto mode');
|
||||
const readOnly = view === 'composition' && options.displayMode === 'readOnly';
|
||||
const organizationLinks: Record<string, string> = Object.create(null);
|
||||
if (options.onEmployeeClick !== undefined && typeof options.onEmployeeClick !== 'function') throw new TypeError('onEmployeeClick must be a function');
|
||||
@@ -200,14 +208,16 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
const normalized = normalizeOrgChartDataset(data);
|
||||
const summary = freeze(calculateSummary(data, policy));
|
||||
const topLeaderIds = new Set(view === 'composition' ? normalized.roots.filter(node => node.organization.type === 'root' && node.organization.leaderId).map(node => node.organization.leaderId!) : []);
|
||||
const nextContext: RenderContext = { data, summary, orderedOrganizations: [], employeesByOrganization: normalized.employeesByOrganization, topLeaderIds, readOnly, organizationLinks };
|
||||
const nextContext: RenderContext = { data, summary, orderedOrganizations: [], employeesByOrganization: normalized.employeesByOrganization, topLeaderIds, readOnly: readOnly || expanded, organizationLinks, expandedCompanyPreset };
|
||||
// Composition shows the root leader as the standalone top card, so the root's direct
|
||||
// organizations become the horizontal engine roots (data and parentId stay untouched).
|
||||
// A root with other direct employees keeps its node so those employees stay visible.
|
||||
const flattenRoot = (node: typeof normalized.roots[number]) => view === 'composition' && node.organization.type === 'root' && !!node.organization.leaderId
|
||||
&& (normalized.employeesByOrganization.get(node.organization.id) ?? []).every(employee => employee.id === node.organization.leaderId);
|
||||
const flattened = normalized.roots.some(flattenRoot);
|
||||
const tree = engineTree(flattened ? normalized.roots.flatMap(node => flattenRoot(node) ? node.children : [node]) : normalized.roots, prefix, nextContext, flattened ? 1 : 0);
|
||||
const roots = expanded ? expandedPreviewRoots(normalized.roots)
|
||||
: flattened ? normalized.roots.flatMap(node => flattenRoot(node) ? node.children : [node]) : normalized.roots;
|
||||
const tree = engineTree(roots, prefix, nextContext, expanded || flattened ? 1 : 0);
|
||||
const styleIssues = await styleReady;
|
||||
if (!active(version)) return;
|
||||
if (styleIssues) { fail(version, styleIssues); return; }
|
||||
@@ -233,7 +243,9 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
});
|
||||
}
|
||||
// If regeneration removed keyboard focus, offer the stable viewport next.
|
||||
const nextContent = view === 'composition' ? createOrgChartContent(document, nextContext, viewport!, contentGeneration.signal)
|
||||
const nextContent = view === 'composition' ? expanded
|
||||
? createExpandedOrgChartContent(document, nextContext, viewport!, contentGeneration.signal)
|
||||
: createOrgChartContent(document, nextContext, viewport!, contentGeneration.signal)
|
||||
: view === 'tree' ? viewport!
|
||||
: view === 'positionSummary' ? createPositionSummary(document, nextContext)
|
||||
: view === 'affiliationSummary' ? createAffiliationSummary(document, nextContext)
|
||||
@@ -306,6 +318,7 @@ export function createOrgChartView(options: OrgChartOptions, view: CoreView): Or
|
||||
cancelStyles = () => { for (const dispose of styleDisposers) dispose(); };
|
||||
shell = document.createElement('section');
|
||||
shell.className = 'hm-orgchart-shell';
|
||||
shell.dataset.layoutMode = view === 'composition' ? layoutMode : 'fixed';
|
||||
// The Core's Korean labels must shape identically in ko HTML and en Storybook hosts.
|
||||
shell.lang = 'ko';
|
||||
shell.tabIndex = -1;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createEmployeeCard } from './employee-card.js';
|
||||
import { applyOrganizationStyle, organizationStylePresets } from './style-presets.js';
|
||||
import { getDepartmentCardAsset } from './department-card-assets.js';
|
||||
import type { OrgChartOrganizationLinks } from './types.js';
|
||||
import type { CompanyColorPreset } from '../company-color-presets.js';
|
||||
|
||||
const organizationNames = { root: '최상위 조직', division: '부문', office: '실', div: 'Div', cell: '셀', team: '팀' } as const;
|
||||
|
||||
@@ -16,6 +17,8 @@ export interface RenderContext {
|
||||
/** View-only composition: no collapse controls or diagnostic text are created. */
|
||||
readOnly?: boolean;
|
||||
organizationLinks?: OrgChartOrganizationLinks;
|
||||
/** Only the Expanded presenter supplies company header colors. */
|
||||
expandedCompanyPreset?: CompanyColorPreset;
|
||||
}
|
||||
|
||||
export function engineTree(roots: OrganizationTreeNode[], prefix: string, context: RenderContext, startDepth = 0): RenderEngineNode[] {
|
||||
@@ -61,6 +64,14 @@ export function decorateNode(
|
||||
}
|
||||
node.dataset.organizationDepth = String(organizationDepth);
|
||||
applyOrganizationStyle(node, Number(value.groupVariant));
|
||||
if (context.expandedCompanyPreset) {
|
||||
const preset = context.expandedCompanyPreset;
|
||||
node.dataset.companyId = preset.companyId;
|
||||
node.style.setProperty('--hm-orgchart-current-gradient', preset.primary);
|
||||
node.style.setProperty('--hm-orgchart-expanded-primary', preset.primary);
|
||||
// Preserve every existing secondary value for the later Expanded theme.
|
||||
preset.secondary.forEach((color, index) => node.style.setProperty(`--hm-orgchart-expanded-secondary-${index + 1}`, color));
|
||||
}
|
||||
node.dataset.organizationType = organization.type;
|
||||
// Header role is styling-only: depth 0-1 are organization titles, deeper nodes are cell/team headers.
|
||||
node.dataset.headerRole = (context.readOnly ? organizationDepth : Number(value.depth)) <= 1 ? 'organization' : 'group';
|
||||
|
||||
@@ -10,6 +10,10 @@ export type OrgChartState =
|
||||
* controls or development text, compact summaries on a fitted design canvas. */
|
||||
export type OrgChartDisplayMode = 'interactive' | 'readOnly';
|
||||
|
||||
/** Layout is independent of displayMode. Auto currently exposes only the
|
||||
* P5-1A initial groups; recursive placement is reserved for P5-1B. */
|
||||
export type OrgChartLayoutMode = 'fixed' | 'auto';
|
||||
|
||||
/** Links are keyed by the existing organization ID. Only 1Depth titles use them. */
|
||||
export type OrgChartOrganizationLinks = Readonly<Record<string, string | undefined>>;
|
||||
|
||||
@@ -29,6 +33,11 @@ export interface OrgChartOptions {
|
||||
stylesheetUrls: { engine: string; core: string };
|
||||
/** Full composition presentation only; standalone views ignore it. */
|
||||
displayMode?: OrgChartDisplayMode;
|
||||
/** Full composition only. Omission preserves the existing Fixed renderer. */
|
||||
layoutMode?: OrgChartLayoutMode;
|
||||
/** Expanded-only company preset ID. Defaults to hanmac; ignored by Fixed.
|
||||
* Unknown IDs are rejected in Auto rather than silently replacing colors. */
|
||||
companyId?: string;
|
||||
/** 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 { OrgChartDisplayMode, OrgChartEmployeeClickContext, OrgChartHandle, OrgChartOptions, OrgChartOrganizationLinks, OrgChartState } from './core/types.js';
|
||||
export type { 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';
|
||||
@@ -0,0 +1,92 @@
|
||||
/* P5-1A flow preview only. Every rule is under the new presenter namespace;
|
||||
the fixed Workdesk composition and approved card internals stay unchanged.
|
||||
Ordinary flex wrapping is not the P5-1B automatic hierarchy layout. */
|
||||
.hm-orgchart-expanded {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: var(--hm-orgchart-resolved-spacing-sm);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
padding: var(--hm-orgchart-resolved-spacing-lg);
|
||||
background: var(--hm-orgchart-resolved-bg-content);
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-heading { margin: 0; }
|
||||
.hm-orgchart-expanded .hm-orgchart-expanded-stage-note {
|
||||
margin: 0;
|
||||
color: var(--hm-orgchart-resolved-fg-primary);
|
||||
font-weight: var(--hm-orgchart-resolved-font-weight-bold);
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-expanded-description {
|
||||
margin: 0;
|
||||
color: var(--hm-orgchart-resolved-fg-secondary);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-expanded-top-leaders {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: var(--hm-orgchart-resolved-spacing-lg);
|
||||
padding: var(--hm-orgchart-resolved-spacing-lg) 0;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-viewport {
|
||||
padding: var(--hm-orgchart-resolved-spacing-lg);
|
||||
overflow: auto;
|
||||
background: var(--hm-orgchart-resolved-bg-surface);
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-forest {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
gap: var(--hm-orgchart-resolved-spacing-xxl);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-tree { flex: 0 0 auto; }
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] {
|
||||
width: max-content;
|
||||
min-width: 146px;
|
||||
margin: 0;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-organization {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
/* Same approved 1Depth title geometry; color resolves from the auto-only
|
||||
company variables supplied by the shared node decorator. */
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-organization-name {
|
||||
align-self: stretch;
|
||||
min-width: 146px;
|
||||
height: 36px;
|
||||
min-height: 36px;
|
||||
margin: 0;
|
||||
padding: 0 12px;
|
||||
background: var(--hm-orgchart-current-gradient);
|
||||
color: var(--hm-orgchart-resolved-on-action-primary);
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
line-height: 36px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee { margin: 0; }
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employee.hm-orgchart-leader { margin: 8px 0 0; }
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employees {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.hm-orgchart-expanded .hm-orgchart-engine.orgchart .node[data-depth] .hm-orgchart-employees:empty { display: none; }
|
||||
@@ -5,6 +5,7 @@
|
||||
@import './org-chart-readonly.css';
|
||||
@import './org-chart-p3-summary.css';
|
||||
@import './employee-info-dialog.css';
|
||||
@import './org-chart-expanded.css';
|
||||
/* Standalone defaults are scoped to the shell; only OrgChart public variables
|
||||
inherit across the Shadow DOM boundary. No host HM Theme is required. */
|
||||
:host { display: block; min-width: 0; max-width: 100%; font: inherit; }
|
||||
|
||||
Reference in new issue
Block a user