@transitrix/diagrams 1.5.0 → 1.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/capability-map/CapabilityCardNode.d.ts +19 -0
- package/dist/capability-map/CapabilityCardNode.js +66 -0
- package/dist/capability-map/CapabilityMapView.d.ts +59 -0
- package/dist/capability-map/CapabilityMapView.js +204 -0
- package/dist/capability-map/CapabilityRootNode.d.ts +12 -0
- package/dist/capability-map/CapabilityRootNode.js +23 -0
- package/dist/capability-map/__tests__/CapabilityMapView.test.d.ts +1 -0
- package/dist/capability-map/__tests__/CapabilityMapView.test.js +217 -0
- package/dist/capability-map/__tests__/address.test.d.ts +1 -0
- package/dist/capability-map/__tests__/address.test.js +49 -0
- package/dist/capability-map/__tests__/dsm-layout.test.d.ts +1 -0
- package/dist/capability-map/__tests__/dsm-layout.test.js +46 -0
- package/dist/capability-map/__tests__/dsm-mutations.test.d.ts +1 -0
- package/dist/capability-map/__tests__/dsm-mutations.test.js +125 -0
- package/dist/capability-map/__tests__/dsm-validate.test.d.ts +1 -0
- package/dist/capability-map/__tests__/dsm-validate.test.js +56 -0
- package/dist/capability-map/address.d.ts +12 -0
- package/dist/capability-map/address.js +48 -0
- package/dist/capability-map/dsm-layout.d.ts +9 -0
- package/dist/capability-map/dsm-layout.js +125 -0
- package/dist/capability-map/dsm-mutations.d.ts +28 -0
- package/dist/capability-map/dsm-mutations.js +196 -0
- package/dist/capability-map/dsm-schema.d.ts +71 -0
- package/dist/capability-map/dsm-schema.js +14 -0
- package/dist/capability-map/dsm-theme.d.ts +20 -0
- package/dist/capability-map/dsm-theme.js +36 -0
- package/dist/capability-map/dsm-validate.d.ts +2 -0
- package/dist/capability-map/dsm-validate.js +93 -0
- package/dist/capability-map/index.d.ts +9 -0
- package/dist/capability-map/index.js +6 -0
- package/dist/codex/__tests__/example.test.d.ts +1 -0
- package/dist/codex/__tests__/example.test.js +33 -0
- package/dist/codex/__tests__/validate.test.d.ts +1 -0
- package/dist/codex/__tests__/validate.test.js +68 -0
- package/dist/codex/index.d.ts +4 -0
- package/dist/codex/index.js +2 -0
- package/dist/codex/types.d.ts +7 -0
- package/dist/codex/types.js +8 -0
- package/dist/codex/validate.d.ts +11 -0
- package/dist/codex/validate.js +103 -0
- package/dist/compliance/__tests__/canon-catalog.test.d.ts +1 -0
- package/dist/compliance/__tests__/canon-catalog.test.js +60 -0
- package/dist/compliance/__tests__/trace.test.d.ts +1 -0
- package/dist/compliance/__tests__/trace.test.js +144 -0
- package/dist/compliance/__tests__/view-resolution.test.d.ts +1 -0
- package/dist/compliance/__tests__/view-resolution.test.js +59 -0
- package/dist/compliance/canon-catalog.d.ts +19 -0
- package/dist/compliance/canon-catalog.js +59 -0
- package/dist/compliance/classify.js +7 -1
- package/dist/compliance/coverage-metric.d.ts +12 -0
- package/dist/compliance/coverage-metric.js +35 -0
- package/dist/compliance/impact.d.ts +7 -1
- package/dist/compliance/impact.js +53 -0
- package/dist/compliance/index.d.ts +8 -5
- package/dist/compliance/index.js +4 -2
- package/dist/compliance/reverse-index.js +4 -1
- package/dist/compliance/trace.d.ts +26 -0
- package/dist/compliance/trace.js +89 -0
- package/dist/compliance/types.d.ts +94 -1
- package/dist/constraint/__tests__/example.test.d.ts +1 -0
- package/dist/constraint/__tests__/example.test.js +14 -0
- package/dist/constraint/__tests__/validate.test.d.ts +1 -0
- package/dist/constraint/__tests__/validate.test.js +58 -0
- package/dist/constraint/index.d.ts +4 -0
- package/dist/constraint/index.js +2 -0
- package/dist/constraint/types.d.ts +4 -0
- package/dist/constraint/types.js +2 -0
- package/dist/constraint/validate.d.ts +7 -0
- package/dist/constraint/validate.js +115 -0
- package/dist/fgca/__tests__/parse-canonical.test.js +31 -0
- package/dist/fgca/parse-canonical.d.ts +1 -1
- package/dist/fgca/parse-canonical.js +18 -4
- package/dist/goals/GoalNode.d.ts +19 -0
- package/dist/goals/GoalNode.js +96 -0
- package/dist/goals/GoalTreeView.d.ts +59 -0
- package/dist/goals/GoalTreeView.js +245 -0
- package/dist/goals/__tests__/GoalTreeView.test.d.ts +1 -0
- package/dist/goals/__tests__/GoalTreeView.test.js +337 -0
- package/dist/goals/__tests__/mutations.test.js +39 -0
- package/dist/goals/__tests__/parse-canonical.test.js +2 -0
- package/dist/goals/index.d.ts +3 -0
- package/dist/goals/index.js +1 -0
- package/dist/goals/layout.js +2 -1
- package/dist/goals/mutations.js +6 -6
- package/dist/goals/parse-canonical.d.ts +5 -0
- package/dist/goals/parse-canonical.js +5 -0
- package/dist/goals/theme.d.ts +22 -0
- package/dist/goals/theme.js +32 -0
- package/dist/goals/types.d.ts +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -1
- package/dist/repo-validate/types.js +2 -3
- package/dist/webview/__tests__/render-goals.test.js +17 -3
- package/dist/webview/render-goals.js +2 -1
- package/package.json +4 -1
- package/src/capability-map/CapabilityCardNode.tsx +146 -0
- package/src/capability-map/CapabilityMapView.tsx +331 -0
- package/src/capability-map/CapabilityRootNode.tsx +38 -0
- package/src/capability-map/__tests__/CapabilityMapView.test.tsx +232 -0
- package/src/capability-map/__tests__/address.test.ts +59 -0
- package/src/capability-map/__tests__/dsm-layout.test.ts +52 -0
- package/src/capability-map/__tests__/dsm-mutations.test.ts +141 -0
- package/src/capability-map/__tests__/dsm-validate.test.ts +65 -0
- package/src/capability-map/address.ts +48 -0
- package/src/capability-map/dsm-layout.ts +142 -0
- package/src/capability-map/dsm-mutations.ts +208 -0
- package/src/capability-map/dsm-schema.ts +80 -0
- package/src/capability-map/dsm-theme.ts +53 -0
- package/src/capability-map/dsm-validate.ts +103 -0
- package/src/capability-map/index.ts +35 -0
- package/src/codex/__tests__/example.test.ts +40 -0
- package/src/codex/__tests__/validate.test.ts +84 -0
- package/src/codex/index.ts +4 -0
- package/src/codex/types.ts +13 -0
- package/src/codex/validate.ts +125 -0
- package/src/compliance/__tests__/canon-catalog.test.ts +68 -0
- package/src/compliance/__tests__/trace.test.ts +157 -0
- package/src/compliance/__tests__/view-resolution.test.ts +80 -0
- package/src/compliance/canon-catalog.ts +76 -0
- package/src/compliance/classify.ts +7 -1
- package/src/compliance/coverage-metric.ts +50 -0
- package/src/compliance/impact.ts +67 -1
- package/src/compliance/index.ts +16 -1
- package/src/compliance/reverse-index.ts +3 -1
- package/src/compliance/trace.ts +118 -0
- package/src/compliance/types.ts +101 -1
- package/src/constraint/__tests__/example.test.ts +19 -0
- package/src/constraint/__tests__/validate.test.ts +68 -0
- package/src/constraint/index.ts +4 -0
- package/src/constraint/types.ts +5 -0
- package/src/constraint/validate.ts +131 -0
- package/src/fgca/__tests__/parse-canonical.test.ts +34 -0
- package/src/fgca/parse-canonical.ts +17 -4
- package/src/goals/GoalNode.tsx +219 -0
- package/src/goals/GoalTreeView.tsx +384 -0
- package/src/goals/__tests__/GoalTreeView.test.tsx +361 -0
- package/src/goals/__tests__/mutations.test.ts +42 -0
- package/src/goals/__tests__/parse-canonical.test.ts +2 -0
- package/src/goals/index.ts +4 -0
- package/src/goals/layout.ts +2 -1
- package/src/goals/mutations.ts +6 -6
- package/src/goals/parse-canonical.ts +6 -0
- package/src/goals/theme.ts +54 -0
- package/src/goals/types.ts +2 -0
- package/src/index.ts +29 -1
- package/src/repo-validate/types.ts +2 -3
- package/src/webview/__tests__/render-goals.test.ts +17 -3
- package/src/webview/render-goals.ts +2 -1
package/dist/goals/mutations.js
CHANGED
|
@@ -16,12 +16,12 @@ function getTypeForLevel(tree, level) {
|
|
|
16
16
|
const gt = tree.goal_types.find(t => t.level === level);
|
|
17
17
|
return gt?.name ?? '';
|
|
18
18
|
}
|
|
19
|
-
function updateDescendantLevels(
|
|
20
|
-
for (const g of goals) {
|
|
19
|
+
function updateDescendantLevels(clone, id, levelDelta, maxLevel) {
|
|
20
|
+
for (const g of clone.goals) {
|
|
21
21
|
if (g.parent_id === id) {
|
|
22
|
-
g.level
|
|
23
|
-
g.type =
|
|
24
|
-
updateDescendantLevels(
|
|
22
|
+
g.level = Math.min(g.level + levelDelta, maxLevel);
|
|
23
|
+
g.type = getTypeForLevel(clone, g.level);
|
|
24
|
+
updateDescendantLevels(clone, g.id, levelDelta, maxLevel);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -67,7 +67,7 @@ export function reparent(tree, sourceId, targetId) {
|
|
|
67
67
|
source.level = newLevel;
|
|
68
68
|
source.type = getTypeForLevel(clone, newLevel);
|
|
69
69
|
if (levelDelta !== 0)
|
|
70
|
-
updateDescendantLevels(clone
|
|
70
|
+
updateDescendantLevels(clone, sourceId, levelDelta, maxLevel);
|
|
71
71
|
const v = validateGoalTree(clone);
|
|
72
72
|
if (!v.valid)
|
|
73
73
|
return refusalError(v.errors[0]?.message ?? 'Validation failed after reparent');
|
|
@@ -26,4 +26,9 @@ export interface CanonicalGoalsResult extends ValidationResult {
|
|
|
26
26
|
/** On success, the internal-form GoalTree derived from the canonical input. */
|
|
27
27
|
parsed?: GoalTree;
|
|
28
28
|
}
|
|
29
|
+
/** Display id for previews — canonical `GOAL-…` when present, else internal numeric id. */
|
|
30
|
+
export declare function displayGoalId(goal: {
|
|
31
|
+
id: number;
|
|
32
|
+
canonical_id?: string;
|
|
33
|
+
}): string;
|
|
29
34
|
export declare function parseCanonicalGoals(input: unknown): CanonicalGoalsResult;
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
*/
|
|
23
23
|
const GOAL_ID_RE = /^GOAL(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
24
24
|
const GOALS_DOC_ID_RE = /^GOALS(-[A-Z0-9][A-Z0-9_]*)*-\d+$/;
|
|
25
|
+
/** Display id for previews — canonical `GOAL-…` when present, else internal numeric id. */
|
|
26
|
+
export function displayGoalId(goal) {
|
|
27
|
+
return goal.canonical_id ?? String(goal.id);
|
|
28
|
+
}
|
|
25
29
|
function isNonEmptyString(v) {
|
|
26
30
|
return typeof v === 'string' && v.trim().length > 0;
|
|
27
31
|
}
|
|
@@ -240,6 +244,7 @@ export function parseCanonicalGoals(input) {
|
|
|
240
244
|
const parentId = isNonEmptyString(parent) && idToInternal.has(parent) ? idToInternal.get(parent) : 0;
|
|
241
245
|
return {
|
|
242
246
|
id: idToInternal.get(o['id']),
|
|
247
|
+
canonical_id: o['id'],
|
|
243
248
|
name: o['name'],
|
|
244
249
|
type: o['type'],
|
|
245
250
|
level: o['level'],
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme tokens for GoalTreeView — replaces DSM's Redux selectors
|
|
3
|
+
* (selectDiagramParams, selectGoalLevelColors) with a plain prop. Hosts
|
|
4
|
+
* that already carry Transitrix's --ts-* CSS custom properties (Studio)
|
|
5
|
+
* can pass those through; hosts with their own theme store (DSM) map
|
|
6
|
+
* their own values onto this shape.
|
|
7
|
+
*/
|
|
8
|
+
export interface ThemeTokens {
|
|
9
|
+
/** Card fill colour per goal level (0..7). Falls back to DEFAULT_GOAL_LEVEL_COLORS for missing levels. */
|
|
10
|
+
goalLevelColors?: Record<number, string>;
|
|
11
|
+
cardBorderColor?: string;
|
|
12
|
+
cardBorderWidth?: number;
|
|
13
|
+
cardTextColor?: string;
|
|
14
|
+
cardMetaColor?: string;
|
|
15
|
+
edgeColor?: string;
|
|
16
|
+
edgeWidth?: number;
|
|
17
|
+
/** Border colour a card takes on while it's a highlighted drop target during drag. */
|
|
18
|
+
dropTargetBorderColor?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare const DEFAULT_GOAL_LEVEL_COLORS: Record<number, string>;
|
|
21
|
+
export declare const DEFAULT_THEME: Required<ThemeTokens>;
|
|
22
|
+
export declare function resolveTheme(theme?: ThemeTokens): Required<ThemeTokens>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export const DEFAULT_GOAL_LEVEL_COLORS = {
|
|
2
|
+
0: '#e0e7ff',
|
|
3
|
+
1: '#c7d2fe',
|
|
4
|
+
2: '#a5b4fc',
|
|
5
|
+
3: '#93c5fd',
|
|
6
|
+
4: '#bae6fd',
|
|
7
|
+
5: '#bbf7d0',
|
|
8
|
+
6: '#fef08a',
|
|
9
|
+
7: '#fed7aa',
|
|
10
|
+
};
|
|
11
|
+
export const DEFAULT_THEME = {
|
|
12
|
+
goalLevelColors: DEFAULT_GOAL_LEVEL_COLORS,
|
|
13
|
+
cardBorderColor: '#94a3b8',
|
|
14
|
+
cardBorderWidth: 1,
|
|
15
|
+
cardTextColor: '#0f172a',
|
|
16
|
+
cardMetaColor: '#64748b',
|
|
17
|
+
edgeColor: '#94a3b8',
|
|
18
|
+
edgeWidth: 1.5,
|
|
19
|
+
dropTargetBorderColor: '#2563eb',
|
|
20
|
+
};
|
|
21
|
+
export function resolveTheme(theme) {
|
|
22
|
+
return {
|
|
23
|
+
goalLevelColors: theme?.goalLevelColors ?? DEFAULT_THEME.goalLevelColors,
|
|
24
|
+
cardBorderColor: theme?.cardBorderColor ?? DEFAULT_THEME.cardBorderColor,
|
|
25
|
+
cardBorderWidth: theme?.cardBorderWidth ?? DEFAULT_THEME.cardBorderWidth,
|
|
26
|
+
cardTextColor: theme?.cardTextColor ?? DEFAULT_THEME.cardTextColor,
|
|
27
|
+
cardMetaColor: theme?.cardMetaColor ?? DEFAULT_THEME.cardMetaColor,
|
|
28
|
+
edgeColor: theme?.edgeColor ?? DEFAULT_THEME.edgeColor,
|
|
29
|
+
edgeWidth: theme?.edgeWidth ?? DEFAULT_THEME.edgeWidth,
|
|
30
|
+
dropTargetBorderColor: theme?.dropTargetBorderColor ?? DEFAULT_THEME.dropTargetBorderColor,
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/goals/types.d.ts
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,8 @@ export * from "./geometry";
|
|
|
6
6
|
export * from "./applications/index";
|
|
7
7
|
export * from "./assertion/index";
|
|
8
8
|
export * from "./blocks/index";
|
|
9
|
-
export
|
|
9
|
+
export type { CapabilityType, CapabilityNode, CapabilityMapHeader, CapabilityMapFile, CapabilityTreeNode, CapabilityTreeEdge, CapabilityTreeLevelCounts, CapabilityTreeLayout, RenderCapabilityTreeOptions, } from "./capability-map/index";
|
|
10
|
+
export { validateCapabilityMap, layoutCapabilityTree, TREE_NODE_WIDTH, TREE_NODE_HEIGHT, TREE_RANK_SEP, TREE_NODE_SEP, renderCapabilityTreeSvg, } from "./capability-map/index";
|
|
10
11
|
export * from "./change/index";
|
|
11
12
|
export * from "./compliance/index";
|
|
12
13
|
export * from "./compliance-matrix/index";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ export * from "./geometry";
|
|
|
6
6
|
export * from "./applications/index";
|
|
7
7
|
export * from "./assertion/index";
|
|
8
8
|
export * from "./blocks/index";
|
|
9
|
-
export
|
|
9
|
+
export { validateCapabilityMap, layoutCapabilityTree, TREE_NODE_WIDTH, TREE_NODE_HEIGHT, TREE_RANK_SEP, TREE_NODE_SEP, renderCapabilityTreeSvg, } from "./capability-map/index";
|
|
10
10
|
export * from "./change/index";
|
|
11
11
|
export * from "./compliance/index";
|
|
12
12
|
export * from "./compliance-matrix/index";
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
// referential integrity, atomicity, ID uniqueness, policy — the checks that the
|
|
5
5
|
// methodology's Python `.validators/lint.py` owns today. This converges those
|
|
6
6
|
// checks onto the shared TypeScript model so there is a single validation
|
|
7
|
-
// runtime (methodology ADR
|
|
8
|
-
//
|
|
9
|
-
// `docs/decisions/2026-06-11-validation-runtime-convergence.md`).
|
|
7
|
+
// runtime (methodology ADR "Validation Two-Axis Model"; Studio decision
|
|
8
|
+
// "Validation Runtime Convergence").
|
|
10
9
|
//
|
|
11
10
|
// The finding shape is deliberately minimal — `{ scope, id, message }`. The
|
|
12
11
|
// richer `target` / `category` reporting taxonomy is explicitly DEFERRED per the
|
|
@@ -58,8 +58,22 @@ describe('renderGoalsSvg', () => {
|
|
|
58
58
|
expect(svg).toContain('width="0"');
|
|
59
59
|
expect(svg).toContain('height="0"');
|
|
60
60
|
});
|
|
61
|
-
it('
|
|
62
|
-
const
|
|
63
|
-
|
|
61
|
+
it('shows canonical goal ids in node labels when present', () => {
|
|
62
|
+
const tree = {
|
|
63
|
+
goal_types: [{ name: 'Strategy', level: 0 }],
|
|
64
|
+
goals: [
|
|
65
|
+
{
|
|
66
|
+
id: 1,
|
|
67
|
+
canonical_id: 'GOAL-REVENUE-1',
|
|
68
|
+
name: 'Triple revenue',
|
|
69
|
+
type: 'Strategy',
|
|
70
|
+
level: 0,
|
|
71
|
+
parent_id: 0,
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
const svg = renderGoalsSvg(tree);
|
|
76
|
+
expect(svg).toContain('GOAL-REVENUE-1');
|
|
77
|
+
expect(svg).not.toMatch(/>\s*1\s*</);
|
|
64
78
|
});
|
|
65
79
|
});
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* Step 4 will follow the same shape for the remaining ten notations.
|
|
12
12
|
*/
|
|
13
13
|
import { layoutGoalTree } from '../goals/layout.js';
|
|
14
|
+
import { displayGoalId } from '../goals/parse-canonical.js';
|
|
14
15
|
import { horizontalCubicEdgePath, DEFAULT_EDGE_CURVATURE } from '../edge-path.js';
|
|
15
16
|
import { generateSvgEmbedCss } from '../theme/index.js';
|
|
16
17
|
import { escXml } from './render-util.js';
|
|
@@ -102,7 +103,7 @@ export function renderGoalsLayoutSvg(layout, options = {}) {
|
|
|
102
103
|
const typeY = twoLines ? y + 43 : y + 35;
|
|
103
104
|
const idY = twoLines ? y + 59 : y + 55;
|
|
104
105
|
const typeLabel = n.data.type ?? '';
|
|
105
|
-
const idText =
|
|
106
|
+
const idText = displayGoalId(n.data);
|
|
106
107
|
return `<g>
|
|
107
108
|
<rect class="diagram-node level-${level}" x="${x}" y="${y}" width="${n.width}" height="${n.height}" rx="${ENTITY_NODE_RX}"/>
|
|
108
109
|
<text class="text-primary" x="${x + n.width / 2}" y="${nameY1}" text-anchor="middle" dominant-baseline="central">${escXml(line1)}</text>${twoLines ? `
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@transitrix/diagrams",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Shared rendering, validation, and pure mutations for Transitrix custom diagram formats.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,8 +43,11 @@
|
|
|
43
43
|
"reactflow": "^11.0.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@testing-library/dom": "^10.4.1",
|
|
47
|
+
"@testing-library/react": "^16.3.2",
|
|
46
48
|
"@types/react": "^18.3.28",
|
|
47
49
|
"@types/react-dom": "^19.2.3",
|
|
50
|
+
"jsdom": "^29.1.1",
|
|
48
51
|
"react": "^19.2.6",
|
|
49
52
|
"react-dom": "^19.2.6",
|
|
50
53
|
"reactflow": "^11.11.4",
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React, { memo } from 'react';
|
|
2
|
+
import { Handle, Position } from 'reactflow';
|
|
3
|
+
import type { Capability } from './dsm-schema.js';
|
|
4
|
+
import type { ThemeTokens } from './dsm-theme.js';
|
|
5
|
+
|
|
6
|
+
const CARD_WIDTH = 250;
|
|
7
|
+
const CARD_HEIGHT = 64;
|
|
8
|
+
|
|
9
|
+
function latestMaturityLevel(cap: Capability): number | undefined {
|
|
10
|
+
if (!cap.maturity || cap.maturity.length === 0) return undefined;
|
|
11
|
+
return [...cap.maturity].sort((a, b) => b.date.localeCompare(a.date))[0].level;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CapabilityCardNodeData {
|
|
15
|
+
capability: Capability;
|
|
16
|
+
theme: Required<ThemeTokens>;
|
|
17
|
+
maturityColours: Record<number, string>;
|
|
18
|
+
readOnly: boolean;
|
|
19
|
+
isDropTarget: boolean;
|
|
20
|
+
hasHiddenChildren: boolean;
|
|
21
|
+
isCollapsed: boolean;
|
|
22
|
+
onAddChild: (parentId: number) => void;
|
|
23
|
+
onDelete: (id: number) => void;
|
|
24
|
+
onToggleCollapse: (id: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const CapabilityCardNode = memo(({ data }: { data: CapabilityCardNodeData }) => {
|
|
28
|
+
const { capability, theme, maturityColours, readOnly, isDropTarget, hasHiddenChildren, isCollapsed } = data;
|
|
29
|
+
const maturity = latestMaturityLevel(capability);
|
|
30
|
+
const dotColour = maturity !== undefined ? maturityColours[maturity] : undefined;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
style={{
|
|
35
|
+
position: 'relative',
|
|
36
|
+
width: CARD_WIDTH,
|
|
37
|
+
height: CARD_HEIGHT,
|
|
38
|
+
background: theme.cardFill,
|
|
39
|
+
borderRadius: 8,
|
|
40
|
+
borderStyle: 'solid',
|
|
41
|
+
borderWidth: isDropTarget ? 2 : theme.cardBorderWidth,
|
|
42
|
+
borderColor: isDropTarget ? theme.dropTargetBorderColor : theme.cardBorderColor,
|
|
43
|
+
boxShadow: '0 1px 4px rgba(0,0,0,0.12)',
|
|
44
|
+
padding: '6px 10px',
|
|
45
|
+
display: 'flex',
|
|
46
|
+
flexDirection: 'column',
|
|
47
|
+
justifyContent: 'center',
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Handle type="target" position={Position.Left} style={{ opacity: 0 }} />
|
|
51
|
+
<Handle type="source" position={Position.Right} style={{ opacity: 0 }} />
|
|
52
|
+
|
|
53
|
+
{dotColour && (
|
|
54
|
+
<span
|
|
55
|
+
title={`Maturity ${maturity}`}
|
|
56
|
+
aria-label={`Maturity ${maturity}`}
|
|
57
|
+
style={{
|
|
58
|
+
position: 'absolute', top: 6, left: 6, width: 8, height: 8, borderRadius: '50%', background: dotColour,
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
|
|
63
|
+
<div
|
|
64
|
+
title={capability.name}
|
|
65
|
+
style={{
|
|
66
|
+
fontWeight: 600,
|
|
67
|
+
fontSize: 12,
|
|
68
|
+
color: theme.cardTextColor,
|
|
69
|
+
marginLeft: dotColour ? 12 : 0,
|
|
70
|
+
overflow: 'hidden',
|
|
71
|
+
display: '-webkit-box',
|
|
72
|
+
WebkitLineClamp: 2,
|
|
73
|
+
WebkitBoxOrient: 'vertical',
|
|
74
|
+
wordBreak: 'break-word',
|
|
75
|
+
lineHeight: 1.2,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{capability.name}
|
|
79
|
+
</div>
|
|
80
|
+
<div style={{ fontSize: 10, color: theme.cardMetaColor, marginTop: 2, marginLeft: dotColour ? 12 : 0 }}>
|
|
81
|
+
{capability.address}
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{!readOnly && (
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
aria-label="Add child capability"
|
|
88
|
+
title="Add child capability"
|
|
89
|
+
onClick={(e) => {
|
|
90
|
+
e.stopPropagation();
|
|
91
|
+
data.onAddChild(capability.id);
|
|
92
|
+
}}
|
|
93
|
+
style={{
|
|
94
|
+
position: 'absolute', top: -8, right: -8, width: 16, height: 16,
|
|
95
|
+
borderRadius: '50%', border: '1px solid #86efac', background: '#ffffff',
|
|
96
|
+
color: '#16a34a', fontSize: 11, lineHeight: '14px', padding: 0, cursor: 'pointer',
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
+
|
|
100
|
+
</button>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{!readOnly && (
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
aria-label="Delete capability"
|
|
107
|
+
title="Delete capability and descendants"
|
|
108
|
+
onClick={(e) => {
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
data.onDelete(capability.id);
|
|
111
|
+
}}
|
|
112
|
+
style={{
|
|
113
|
+
position: 'absolute', bottom: -8, right: -8, width: 16, height: 16,
|
|
114
|
+
borderRadius: '50%', border: '1px solid #fca5a5', background: '#ffffff',
|
|
115
|
+
color: '#dc2626', fontSize: 11, lineHeight: '14px', padding: 0, cursor: 'pointer',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
×
|
|
119
|
+
</button>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{hasHiddenChildren && (
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
aria-label={isCollapsed ? 'Expand branch' : 'Collapse branch'}
|
|
126
|
+
title={isCollapsed ? 'Expand branch' : 'Collapse branch'}
|
|
127
|
+
onClick={(e) => {
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
data.onToggleCollapse(capability.id);
|
|
130
|
+
}}
|
|
131
|
+
style={{
|
|
132
|
+
position: 'absolute', top: '50%', right: -8, transform: 'translateY(-50%)',
|
|
133
|
+
width: 16, height: 16, borderRadius: '50%', border: `1px solid ${theme.cardBorderColor}`,
|
|
134
|
+
background: '#ffffff', color: theme.cardBorderColor, fontSize: 11, lineHeight: '14px', padding: 0, cursor: 'pointer',
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{isCollapsed ? '+' : '−'}
|
|
138
|
+
</button>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
CapabilityCardNode.displayName = 'CapabilityCardNode';
|
|
145
|
+
|
|
146
|
+
export default CapabilityCardNode;
|