@gravity-ui/page-constructor 8.15.0 → 8.17.0

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.
Files changed (129) hide show
  1. package/README.md +31 -9
  2. package/build/cjs/constructor-items.d.ts +1 -1
  3. package/build/cjs/demo/ComponentMap/ComponentMap.css +55 -0
  4. package/build/cjs/demo/ComponentMap/ComponentMap.d.ts +6 -0
  5. package/build/cjs/demo/ComponentMap/ComponentMap.js +286 -0
  6. package/build/cjs/demo/ComponentMap/ComponentMap.js.map +1 -0
  7. package/build/cjs/demo/ComponentMap/draw.d.ts +23 -0
  8. package/build/cjs/demo/ComponentMap/draw.js +134 -0
  9. package/build/cjs/demo/ComponentMap/draw.js.map +1 -0
  10. package/build/cjs/demo/ComponentMap/edgeGeometry.d.ts +20 -0
  11. package/build/cjs/demo/ComponentMap/edgeGeometry.js +146 -0
  12. package/build/cjs/demo/ComponentMap/edgeGeometry.js.map +1 -0
  13. package/build/cjs/demo/ComponentMap/graph.d.ts +9 -0
  14. package/build/cjs/demo/ComponentMap/graph.js +68 -0
  15. package/build/cjs/demo/ComponentMap/graph.js.map +1 -0
  16. package/build/cjs/demo/ComponentMap/layout.d.ts +12 -0
  17. package/build/cjs/demo/ComponentMap/layout.js +149 -0
  18. package/build/cjs/demo/ComponentMap/layout.js.map +1 -0
  19. package/build/cjs/demo/ComponentMap/model.d.ts +55 -0
  20. package/build/cjs/demo/ComponentMap/model.js +3 -0
  21. package/build/cjs/demo/ComponentMap/model.js.map +1 -0
  22. package/build/cjs/demo/ComponentMap/viewport.d.ts +7 -0
  23. package/build/cjs/demo/ComponentMap/viewport.js +41 -0
  24. package/build/cjs/demo/ComponentMap/viewport.js.map +1 -0
  25. package/build/cjs/demo/HeroAndMedia.stories.js +12 -23
  26. package/build/cjs/demo/HeroAndMedia.stories.js.map +1 -1
  27. package/build/cjs/demo/TokenizationHeader.stories.d.ts +13 -0
  28. package/build/cjs/demo/TokenizationHeader.stories.js +47 -0
  29. package/build/cjs/demo/TokenizationHeader.stories.js.map +1 -0
  30. package/build/cjs/demo/TokenizationHero.stories.d.ts +9 -0
  31. package/build/cjs/demo/TokenizationHero.stories.js +20 -0
  32. package/build/cjs/demo/TokenizationHero.stories.js.map +1 -0
  33. package/build/cjs/demo/tokenizationLanding.d.ts +2 -0
  34. package/build/cjs/demo/tokenizationLanding.js +21 -0
  35. package/build/cjs/demo/tokenizationLanding.js.map +1 -0
  36. package/build/cjs/models/constructor-items/sub-blocks.d.ts +3 -0
  37. package/build/cjs/models/constructor-items/sub-blocks.js.map +1 -1
  38. package/build/cjs/sub-blocks/LayoutItem/LayoutItem.css +15 -0
  39. package/build/cjs/sub-blocks/LayoutItem/LayoutItem.d.ts +1 -1
  40. package/build/cjs/sub-blocks/LayoutItem/LayoutItem.js +12 -2
  41. package/build/cjs/sub-blocks/LayoutItem/LayoutItem.js.map +1 -1
  42. package/build/cjs/sub-blocks/LayoutItem/schema.d.ts +10 -0
  43. package/build/cjs/sub-blocks/LayoutItem/schema.js +10 -0
  44. package/build/cjs/sub-blocks/LayoutItem/schema.js.map +1 -1
  45. package/build/docs/INDEX.md +191 -0
  46. package/build/docs/blocks/Banner.md +59 -0
  47. package/build/docs/blocks/CardLayout.md +79 -0
  48. package/build/docs/blocks/Companies.md +67 -0
  49. package/build/docs/blocks/ContentLayout.md +54 -0
  50. package/build/docs/blocks/ExtendedFeatures.md +74 -0
  51. package/build/docs/blocks/FilterBlock.md +112 -0
  52. package/build/docs/blocks/FoldableList.md +78 -0
  53. package/build/docs/blocks/Footer.md +110 -0
  54. package/build/docs/blocks/Form.md +89 -0
  55. package/build/docs/blocks/Header.md +70 -0
  56. package/build/docs/blocks/HeaderSlider.md +70 -0
  57. package/build/docs/blocks/Hero.md +67 -0
  58. package/build/docs/blocks/Icons.md +61 -0
  59. package/build/docs/blocks/Info.md +73 -0
  60. package/build/docs/blocks/Map.md +81 -0
  61. package/build/docs/blocks/Media.md +63 -0
  62. package/build/docs/blocks/PromoFeaturesBlock.md +74 -0
  63. package/build/docs/blocks/Questions.md +78 -0
  64. package/build/docs/blocks/Share.md +45 -0
  65. package/build/docs/blocks/Slider.md +89 -0
  66. package/build/docs/blocks/SliderOld.md +63 -0
  67. package/build/docs/blocks/Table.md +64 -0
  68. package/build/docs/blocks/Tabs.md +92 -0
  69. package/build/docs/guides/data-preparation-ru.md +257 -0
  70. package/build/docs/guides/data-preparation.md +259 -0
  71. package/build/docs/sub-blocks/BackgroundCard.md +63 -0
  72. package/build/docs/sub-blocks/BannerCard.md +57 -0
  73. package/build/docs/sub-blocks/BasicCard.md +60 -0
  74. package/build/docs/sub-blocks/Content.md +58 -0
  75. package/build/docs/sub-blocks/Divider.md +47 -0
  76. package/build/docs/sub-blocks/HubspotForm.md +47 -0
  77. package/build/docs/sub-blocks/ImageCard.md +59 -0
  78. package/build/docs/sub-blocks/LayoutItem.md +75 -0
  79. package/build/docs/sub-blocks/MediaCard.md +66 -0
  80. package/build/docs/sub-blocks/PriceCard.md +60 -0
  81. package/build/docs/sub-blocks/PriceDetailed.md +74 -0
  82. package/build/docs/sub-blocks/Quote.md +74 -0
  83. package/build/esm/constructor-items.d.ts +1 -1
  84. package/build/esm/demo/ComponentMap/ComponentMap.css +55 -0
  85. package/build/esm/demo/ComponentMap/ComponentMap.d.ts +7 -0
  86. package/build/esm/demo/ComponentMap/ComponentMap.js +283 -0
  87. package/build/esm/demo/ComponentMap/ComponentMap.js.map +1 -0
  88. package/build/esm/demo/ComponentMap/draw.d.ts +23 -0
  89. package/build/esm/demo/ComponentMap/draw.js +130 -0
  90. package/build/esm/demo/ComponentMap/draw.js.map +1 -0
  91. package/build/esm/demo/ComponentMap/edgeGeometry.d.ts +20 -0
  92. package/build/esm/demo/ComponentMap/edgeGeometry.js +136 -0
  93. package/build/esm/demo/ComponentMap/edgeGeometry.js.map +1 -0
  94. package/build/esm/demo/ComponentMap/graph.d.ts +9 -0
  95. package/build/esm/demo/ComponentMap/graph.js +62 -0
  96. package/build/esm/demo/ComponentMap/graph.js.map +1 -0
  97. package/build/esm/demo/ComponentMap/layout.d.ts +12 -0
  98. package/build/esm/demo/ComponentMap/layout.js +144 -0
  99. package/build/esm/demo/ComponentMap/layout.js.map +1 -0
  100. package/build/esm/demo/ComponentMap/model.d.ts +55 -0
  101. package/build/esm/demo/ComponentMap/model.js +2 -0
  102. package/build/esm/demo/ComponentMap/model.js.map +1 -0
  103. package/build/esm/demo/ComponentMap/viewport.d.ts +7 -0
  104. package/build/esm/demo/ComponentMap/viewport.js +34 -0
  105. package/build/esm/demo/ComponentMap/viewport.js.map +1 -0
  106. package/build/esm/demo/HeroAndMedia.stories.js +1 -12
  107. package/build/esm/demo/HeroAndMedia.stories.js.map +1 -1
  108. package/build/esm/demo/TokenizationHeader.stories.d.ts +13 -0
  109. package/build/esm/demo/TokenizationHeader.stories.js +43 -0
  110. package/build/esm/demo/TokenizationHeader.stories.js.map +1 -0
  111. package/build/esm/demo/TokenizationHero.stories.d.ts +9 -0
  112. package/build/esm/demo/TokenizationHero.stories.js +16 -0
  113. package/build/esm/demo/TokenizationHero.stories.js.map +1 -0
  114. package/build/esm/demo/tokenizationLanding.d.ts +2 -0
  115. package/build/esm/demo/tokenizationLanding.js +16 -0
  116. package/build/esm/demo/tokenizationLanding.js.map +1 -0
  117. package/build/esm/models/constructor-items/sub-blocks.d.ts +3 -0
  118. package/build/esm/models/constructor-items/sub-blocks.js.map +1 -1
  119. package/build/esm/sub-blocks/LayoutItem/LayoutItem.css +15 -0
  120. package/build/esm/sub-blocks/LayoutItem/LayoutItem.d.ts +1 -1
  121. package/build/esm/sub-blocks/LayoutItem/LayoutItem.js +14 -4
  122. package/build/esm/sub-blocks/LayoutItem/LayoutItem.js.map +1 -1
  123. package/build/esm/sub-blocks/LayoutItem/schema.d.ts +10 -0
  124. package/build/esm/sub-blocks/LayoutItem/schema.js +10 -0
  125. package/build/esm/sub-blocks/LayoutItem/schema.js.map +1 -1
  126. package/package.json +4 -2
  127. package/schema/index.js +1 -1
  128. package/server/models/constructor-items/sub-blocks.d.ts +3 -0
  129. package/widget/index.js +1 -1
package/README.md CHANGED
@@ -582,17 +582,39 @@ The Memory Bank is located in the `memory-bank/` directory and consists of regul
582
582
  - `usage/` - Component-specific usage documentation
583
583
  - `storybookComponents.md` - Storybook integration details
584
584
 
585
- ### For AI Agents
585
+ ## Tests
586
586
 
587
- When working with AI agents on this project, the Memory Bank serves as a comprehensive knowledge base that helps agents understand:
587
+ Comprehensive documentation is available at the provided [link](./test-utils/docs/README.md).
588
588
 
589
- - Project structure and patterns
590
- - Component APIs and usage examples
591
- - Development workflows and best practices
592
- - Current implementation status and next steps
589
+ ## License
593
590
 
594
- AI agents can read these files to quickly get up to speed with the project context and make more informed decisions about code changes and implementations.
591
+ Distributed under the MIT License. See [LICENSE](LICENSE) for details.
595
592
 
596
- ## Tests
593
+ ## For AI agents
597
594
 
598
- Comprehensive documentation is available at the provided [link](./test-utils/docs/README.md).
595
+ A library for rendering whole web pages or page sections from declarative JSON/YAML config, using a set of ready-made, orderable blocks — reach for it to build marketing/landing pages, not general application UI.
596
+
597
+ ### When to use
598
+
599
+ - Data-driven pages: render a `content` config of typed blocks with `PageConstructor` wrapped in `PageConstructorProvider`.
600
+ - Marketing, landing, and documentation pages assembled from prebuilt blocks (headers, media, cards, etc.).
601
+ - Server-side YFM processing of block text via the `@gravity-ui/page-constructor/server` utilities (`contentTransformer`, `fullTransform`).
602
+ - Reusing just the responsive grid (`Grid`/`Row`/`Col`) or `Navigation` component standalone.
603
+
604
+ ### When not to use
605
+
606
+ - General application UI (buttons, forms, modals) — use [`@gravity-ui/uikit`](https://github.com/gravity-ui/uikit).
607
+ - Editing Markdown/YFM content — use [`@gravity-ui/markdown-editor`](https://github.com/gravity-ui/markdown-editor).
608
+ - App navigation shells (aside header) — use [`@gravity-ui/navigation`](https://github.com/gravity-ui/navigation); this package's `Navigation` is a page-level top nav.
609
+
610
+ ### Common pitfalls
611
+
612
+ - **`PageConstructor` must be wrapped in `PageConstructorProvider`.** Rendering it bare breaks context (locale, theme, SSR, analytics).
613
+ - **The content prop is `content`, shaped `{blocks: [...]}`.** Each block object needs a `type` matching a known block plus its data fields; there is no `data`/`config` prop.
614
+ - **YFM in block text needs server processing.** Markdown-like fields render as plain text unless you run content through `contentTransformer`/`fullTransform` from `@gravity-ui/page-constructor/server`; `@diplodoc/transform` is a required peer dependency.
615
+ - **Import the SCSS styles.** Add `@gravity-ui/page-constructor/styles/styles.scss` (SCSS, not CSS); custom blocks import the same file to reuse mixins/variables.
616
+ - **Vite needs `vite-plugin-dynamic-import`.** Dynamic block imports fail under Vite without it.
617
+
618
+ ## Documentation for AI agents
619
+
620
+ Agent-readable documentation for the installed version is located in `node_modules/@gravity-ui/page-constructor/build/docs/INDEX.md`.
@@ -28,7 +28,7 @@ export declare const subBlockMap: {
28
28
  "price-detailed": (props: import("./models/index.js").PriceDetailedProps) => import("react/jsx-runtime").JSX.Element;
29
29
  "media-card": ({ border, analyticsEvents, ...mediaProps }: import("./models/index.js").MediaCardProps) => import("react/jsx-runtime").JSX.Element;
30
30
  "banner-card": (props: import("./models/index.js").BannerCardProps) => import("react/jsx-runtime").JSX.Element;
31
- "layout-item": ({ content: { links, ...content }, contentMargin, metaInfo, media, border, fullscreen, icon, className, analyticsEvents, controlPosition, }: import("./models/index.js").LayoutItemProps) => import("react/jsx-runtime").JSX.Element;
31
+ "layout-item": ({ content: { links, ...content }, contentMargin, metaInfo, media, border, fullscreen, icon, className, analyticsEvents, controlPosition, url, urlTitle, target, }: import("./models/index.js").LayoutItemProps) => import("react/jsx-runtime").JSX.Element;
32
32
  "background-card": (props: import("./models/index.js").BackgroundCardProps) => import("react/jsx-runtime").JSX.Element;
33
33
  "basic-card": (props: import("./models/index.js").BasicCardProps) => import("react/jsx-runtime").JSX.Element;
34
34
  content: (props: import("./sub-blocks/Content/Content.js").ContentProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,55 @@
1
+ .pc-component-map {
2
+ position: relative;
3
+ width: 100%;
4
+ height: min(760px, 100vh - 96px);
5
+ min-height: 480px;
6
+ overflow: hidden;
7
+ }
8
+ .pc-component-map__canvas {
9
+ display: block;
10
+ width: 100%;
11
+ height: 100%;
12
+ cursor: grab;
13
+ touch-action: none;
14
+ }
15
+ .pc-component-map__canvas_interactive {
16
+ cursor: pointer;
17
+ }
18
+ .pc-component-map__canvas_dragging {
19
+ cursor: grabbing;
20
+ }
21
+ .pc-component-map__canvas:focus-visible {
22
+ outline: 2px solid currentColor;
23
+ outline-offset: -3px;
24
+ }
25
+ .pc-component-map__toolbar {
26
+ position: absolute;
27
+ z-index: 1;
28
+ top: 12px;
29
+ right: 12px;
30
+ left: 12px;
31
+ display: flex;
32
+ justify-content: flex-end;
33
+ gap: 8px;
34
+ }
35
+ .pc-component-map__search {
36
+ flex: 1 1 200px;
37
+ width: 0;
38
+ max-width: 320px;
39
+ min-width: 0;
40
+ }
41
+ .pc-component-map__live {
42
+ position: absolute;
43
+ width: 1px;
44
+ height: 1px;
45
+ padding: 0;
46
+ margin: -1px;
47
+ overflow: hidden;
48
+ clip: rect(0 0 0 0);
49
+ white-space: nowrap;
50
+ border: 0;
51
+ }
52
+ .pc-component-map__empty {
53
+ margin: 0;
54
+ padding: 24px;
55
+ }
@@ -0,0 +1,6 @@
1
+ import type { ComponentMapData } from "./model.js";
2
+ interface ComponentMapProps {
3
+ data: ComponentMapData;
4
+ }
5
+ export declare function ComponentMap({ data }: ComponentMapProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ComponentMap = ComponentMap;
4
+ const tslib_1 = require("tslib");
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const React = tslib_1.__importStar(require("react"));
7
+ const uikit_1 = require("@gravity-ui/uikit");
8
+ const cn_1 = require("../../utils/cn.js");
9
+ const draw_1 = require("./draw.js");
10
+ const edgeGeometry_1 = require("./edgeGeometry.js");
11
+ const graph_1 = require("./graph.js");
12
+ const layout_1 = require("./layout.js");
13
+ const viewport_1 = require("./viewport.js");
14
+ const b = (0, cn_1.block)('component-map');
15
+ const { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } = React;
16
+ const TOOLBAR_ZOOM_FACTOR = 1.2;
17
+ const WHEEL_ZOOM_SENSITIVITY = 0.002;
18
+ const CLICK_MOVEMENT_THRESHOLD = 4;
19
+ const NON_PASSIVE_WHEEL_OPTIONS = { passive: false };
20
+ const EMPTY_SIZE = { width: 0, height: 0, devicePixelRatio: 1 };
21
+ function ComponentMap({ data }) {
22
+ const rootRef = useRef(null);
23
+ const canvasRef = useRef(null);
24
+ const animationFrameRef = useRef(null);
25
+ const pointerDragRef = useRef(null);
26
+ const layout = useMemo(() => (0, layout_1.layoutComponentMap)(data), [data]);
27
+ const [size, setSize] = useState(EMPTY_SIZE);
28
+ const [transform, setTransform] = useState(null);
29
+ const [searchQuery, setSearchQuery] = useState('');
30
+ const [selectedNodeId, setSelectedNodeId] = useState();
31
+ const [selectedEdgeKeys, setSelectedEdgeKeys] = useState(() => new Set());
32
+ const [hoveredNodeId, setHoveredNodeId] = useState();
33
+ const [hoveredEdgeKey, setHoveredEdgeKey] = useState();
34
+ const [dragging, setDragging] = useState(false);
35
+ const fit = useMemo(() => (size.width > 0 && size.height > 0 ? (0, viewport_1.fitToViewport)(layout.bounds, size) : undefined), [layout, size]);
36
+ const emphasis = useMemo(() => (0, graph_1.getEmphasis)(data, { selectedNodeId, searchQuery }), [data, searchQuery, selectedNodeId]);
37
+ const selectedNode = layout.nodes.find(({ id }) => id === selectedNodeId);
38
+ const validEdgeKeys = useMemo(() => new Set(layout.edges.map(({ source, target }) => (0, graph_1.getEdgeKey)(source.id, target.id))), [layout]);
39
+ useEffect(() => {
40
+ const root = rootRef.current;
41
+ if (!root) {
42
+ return undefined;
43
+ }
44
+ const updateSize = ({ width, height }) => {
45
+ setSize({
46
+ width,
47
+ height,
48
+ devicePixelRatio: window.devicePixelRatio || 1,
49
+ });
50
+ };
51
+ const observer = new ResizeObserver(([entry]) => updateSize(entry.contentRect));
52
+ observer.observe(root);
53
+ return () => observer.disconnect();
54
+ }, []);
55
+ useEffect(() => {
56
+ if (fit) {
57
+ setTransform(fit);
58
+ }
59
+ }, [fit]);
60
+ useEffect(() => {
61
+ setSelectedNodeId((current) => current && layout.nodes.some(({ id }) => id === current) ? current : undefined);
62
+ setSelectedEdgeKeys((current) => {
63
+ const next = new Set([...current].filter((key) => validEdgeKeys.has(key)));
64
+ return next.size === current.size ? current : next;
65
+ });
66
+ setHoveredNodeId((current) => current && layout.nodes.some(({ id }) => id === current) ? current : undefined);
67
+ setHoveredEdgeKey((current) => current && validEdgeKeys.has(current) ? current : undefined);
68
+ }, [layout, validEdgeKeys]);
69
+ useLayoutEffect(() => {
70
+ const canvas = canvasRef.current;
71
+ if (!canvas || size.width <= 0 || size.height <= 0) {
72
+ return;
73
+ }
74
+ canvas.width = Math.max(1, Math.round(size.width * size.devicePixelRatio));
75
+ canvas.height = Math.max(1, Math.round(size.height * size.devicePixelRatio));
76
+ }, [data.nodes.length, size]);
77
+ useEffect(() => {
78
+ const canvas = canvasRef.current;
79
+ if (!canvas || !transform || size.width <= 0 || size.height <= 0) {
80
+ return undefined;
81
+ }
82
+ if (animationFrameRef.current !== null) {
83
+ cancelAnimationFrame(animationFrameRef.current);
84
+ }
85
+ animationFrameRef.current = -1;
86
+ const frame = requestAnimationFrame(() => {
87
+ animationFrameRef.current = null;
88
+ const context = canvas.getContext('2d');
89
+ if (context) {
90
+ (0, draw_1.drawComponentMap)(context, layout, transform, {
91
+ viewport: size,
92
+ devicePixelRatio: size.devicePixelRatio,
93
+ selectedNodeId,
94
+ hoveredEdgeKey,
95
+ selectedEdgeKeys,
96
+ emphasis,
97
+ });
98
+ }
99
+ });
100
+ if (animationFrameRef.current !== null) {
101
+ animationFrameRef.current = frame;
102
+ }
103
+ return () => {
104
+ if (animationFrameRef.current !== null) {
105
+ cancelAnimationFrame(animationFrameRef.current);
106
+ animationFrameRef.current = null;
107
+ }
108
+ };
109
+ }, [emphasis, hoveredEdgeKey, layout, selectedEdgeKeys, selectedNodeId, size, transform]);
110
+ const getCanvasPoint = useCallback((clientX, clientY) => {
111
+ const bounds = canvasRef.current?.getBoundingClientRect();
112
+ return {
113
+ x: clientX - (bounds?.left ?? 0),
114
+ y: clientY - (bounds?.top ?? 0),
115
+ };
116
+ }, []);
117
+ const zoom = useCallback((factor, point) => {
118
+ if (!fit) {
119
+ return;
120
+ }
121
+ setTransform((current) => current
122
+ ? (0, viewport_1.zoomAtPoint)(current, factor, point, fit.scale * 0.5, fit.scale * 8)
123
+ : current);
124
+ }, [fit]);
125
+ const zoomFromToolbar = useCallback((factor) => zoom(factor, { x: size.width / 2, y: size.height / 2 }), [size, zoom]);
126
+ const selectNode = useCallback((nodeId) => {
127
+ if (nodeId) {
128
+ setSearchQuery('');
129
+ }
130
+ setSelectedNodeId(nodeId);
131
+ setSelectedEdgeKeys((current) => (current.size ? new Set() : current));
132
+ }, []);
133
+ const selectEdge = useCallback((edgeKey, additive) => {
134
+ setSelectedEdgeKeys((current) => {
135
+ if (!additive) {
136
+ return new Set([edgeKey]);
137
+ }
138
+ const next = new Set(current);
139
+ if (next.has(edgeKey)) {
140
+ next.delete(edgeKey);
141
+ }
142
+ else {
143
+ next.add(edgeKey);
144
+ }
145
+ return next;
146
+ });
147
+ }, []);
148
+ const getHitTarget = useCallback((point, camera) => {
149
+ const worldPoint = (0, viewport_1.screenToWorld)(point, camera);
150
+ const node = (0, layout_1.hitTest)(layout, worldPoint);
151
+ if (node) {
152
+ return { node, edge: undefined };
153
+ }
154
+ const orderedEdges = (0, edgeGeometry_1.orderEdgesForInteraction)(layout.edges, hoveredEdgeKey, selectedEdgeKeys);
155
+ return {
156
+ node: undefined,
157
+ edge: (0, edgeGeometry_1.hitTestEdge)(orderedEdges, worldPoint, (0, edgeGeometry_1.getEdgeHitTolerance)(camera.scale)),
158
+ };
159
+ }, [hoveredEdgeKey, layout, selectedEdgeKeys]);
160
+ const updateHover = useCallback((point, camera) => {
161
+ const { node, edge } = getHitTarget(point, camera);
162
+ setHoveredNodeId(node?.id);
163
+ setHoveredEdgeKey(edge ? (0, graph_1.getEdgeKey)(edge.source.id, edge.target.id) : undefined);
164
+ }, [getHitTarget]);
165
+ useEffect(() => {
166
+ const canvas = canvasRef.current;
167
+ if (!canvas) {
168
+ return undefined;
169
+ }
170
+ const handleWheel = (event) => {
171
+ event.preventDefault();
172
+ const factor = Math.exp(-event.deltaY * WHEEL_ZOOM_SENSITIVITY);
173
+ zoom(factor, getCanvasPoint(event.clientX, event.clientY));
174
+ };
175
+ canvas.addEventListener('wheel', handleWheel, NON_PASSIVE_WHEEL_OPTIONS);
176
+ return () => canvas.removeEventListener('wheel', handleWheel, false);
177
+ }, [data.nodes.length, getCanvasPoint, zoom]);
178
+ const handlePointerDown = useCallback((event) => {
179
+ if (!transform) {
180
+ return;
181
+ }
182
+ const point = getCanvasPoint(event.clientX, event.clientY);
183
+ pointerDragRef.current = {
184
+ pointerId: event.pointerId,
185
+ canvas: event.currentTarget,
186
+ start: point,
187
+ startTransform: transform,
188
+ moved: false,
189
+ };
190
+ event.currentTarget.setPointerCapture(event.pointerId);
191
+ }, [getCanvasPoint, transform]);
192
+ const handlePointerMove = useCallback((event) => {
193
+ const point = getCanvasPoint(event.clientX, event.clientY);
194
+ const drag = pointerDragRef.current;
195
+ if (!drag || drag.pointerId !== event.pointerId) {
196
+ if (transform) {
197
+ updateHover(point, transform);
198
+ }
199
+ return;
200
+ }
201
+ const deltaX = point.x - drag.start.x;
202
+ const deltaY = point.y - drag.start.y;
203
+ const moved = drag.moved || Math.hypot(deltaX, deltaY) >= CLICK_MOVEMENT_THRESHOLD;
204
+ if (!moved) {
205
+ return;
206
+ }
207
+ if (!drag.moved) {
208
+ drag.moved = true;
209
+ setHoveredNodeId(undefined);
210
+ setHoveredEdgeKey(undefined);
211
+ setDragging(true);
212
+ }
213
+ setTransform({
214
+ ...drag.startTransform,
215
+ x: drag.startTransform.x + deltaX,
216
+ y: drag.startTransform.y + deltaY,
217
+ });
218
+ }, [getCanvasPoint, transform, updateHover]);
219
+ const releasePointer = useCallback((canvas, pointerId) => {
220
+ if (canvas.hasPointerCapture(pointerId)) {
221
+ canvas.releasePointerCapture(pointerId);
222
+ }
223
+ }, []);
224
+ const finishPointer = useCallback((event, cancelled) => {
225
+ const drag = pointerDragRef.current;
226
+ if (!drag || drag.pointerId !== event.pointerId) {
227
+ return;
228
+ }
229
+ const point = getCanvasPoint(event.clientX, event.clientY);
230
+ const deltaX = point.x - drag.start.x;
231
+ const deltaY = point.y - drag.start.y;
232
+ const moved = drag.moved || Math.hypot(deltaX, deltaY) >= CLICK_MOVEMENT_THRESHOLD;
233
+ if (!cancelled) {
234
+ if (moved) {
235
+ setTransform({
236
+ ...drag.startTransform,
237
+ x: drag.startTransform.x + deltaX,
238
+ y: drag.startTransform.y + deltaY,
239
+ });
240
+ }
241
+ else {
242
+ const { node, edge } = getHitTarget(point, drag.startTransform);
243
+ if (node) {
244
+ selectNode(node.id);
245
+ }
246
+ else if (edge) {
247
+ selectEdge((0, graph_1.getEdgeKey)(edge.source.id, edge.target.id), event.ctrlKey || event.metaKey);
248
+ }
249
+ else {
250
+ selectNode(undefined);
251
+ }
252
+ }
253
+ }
254
+ releasePointer(event.currentTarget, event.pointerId);
255
+ pointerDragRef.current = null;
256
+ setDragging(false);
257
+ }, [getCanvasPoint, getHitTarget, releasePointer, selectEdge, selectNode]);
258
+ const handleKeyDown = useCallback((event) => {
259
+ if (event.key === 'Escape') {
260
+ event.preventDefault();
261
+ setSelectedNodeId(undefined);
262
+ setSelectedEdgeKeys(new Set());
263
+ }
264
+ }, []);
265
+ useEffect(() => () => {
266
+ const drag = pointerDragRef.current;
267
+ if (drag) {
268
+ releasePointer(drag.canvas, drag.pointerId);
269
+ }
270
+ pointerDragRef.current = null;
271
+ }, [releasePointer]);
272
+ if (!data.nodes.length) {
273
+ return ((0, jsx_runtime_1.jsx)("div", { ref: rootRef, className: b(), children: (0, jsx_runtime_1.jsx)("p", { className: b('empty'), children: "No significant components found" }) }));
274
+ }
275
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: b(), children: [(0, jsx_runtime_1.jsx)("canvas", { ref: canvasRef, className: b('canvas', {
276
+ dragging,
277
+ interactive: Boolean(hoveredNodeId || hoveredEdgeKey),
278
+ }), role: "img", "aria-label": "Significant component dependency map", tabIndex: 0, onKeyDown: handleKeyDown, onPointerCancel: (event) => finishPointer(event, true), onPointerDown: handlePointerDown, onPointerLeave: () => {
279
+ setHoveredNodeId(undefined);
280
+ setHoveredEdgeKey(undefined);
281
+ }, onPointerMove: handlePointerMove, onPointerUp: (event) => finishPointer(event, false) }), (0, jsx_runtime_1.jsxs)("div", { className: b('toolbar'), children: [(0, jsx_runtime_1.jsx)(uikit_1.TextInput, { className: b('search'), size: "l", hasClear: true, placeholder: "Search components", value: searchQuery, controlProps: { 'aria-label': 'Search components' }, onUpdate: setSearchQuery, onFocus: () => {
282
+ setSelectedNodeId(undefined);
283
+ setSelectedEdgeKeys(new Set());
284
+ } }), (0, jsx_runtime_1.jsx)(uikit_1.Button, { className: b('control'), size: "l", view: "outlined", "aria-label": "Zoom in", onClick: () => zoomFromToolbar(TOOLBAR_ZOOM_FACTOR), children: "+" }), (0, jsx_runtime_1.jsx)(uikit_1.Button, { className: b('control'), size: "l", view: "outlined", "aria-label": "Zoom out", onClick: () => zoomFromToolbar(1 / TOOLBAR_ZOOM_FACTOR), children: "\u2212" }), (0, jsx_runtime_1.jsx)(uikit_1.Button, { className: b('control'), size: "l", view: "outlined", "aria-label": "Fit graph", onClick: () => setTransform(fit ?? null), children: "Fit" })] }), (0, jsx_runtime_1.jsx)("output", { className: b('live'), "aria-live": "polite", children: selectedNode ? `Selected ${selectedNode.label}` : '' })] }));
285
+ }
286
+ //# sourceMappingURL=ComponentMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComponentMap.js","sourceRoot":"../../../../src","sources":["demo/ComponentMap/ComponentMap.tsx"],"names":[],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport {Button, TextInput} from '@gravity-ui/uikit';\n\nimport {block} from '../../utils/cn';\n\nimport {drawComponentMap} from './draw';\nimport {getEdgeHitTolerance, hitTestEdge, orderEdgesForInteraction} from './edgeGeometry';\nimport {getEdgeKey, getEmphasis} from './graph';\nimport {hitTest, layoutComponentMap} from './layout';\nimport type {ComponentMapData, Point, ViewportSize, ViewportTransform} from './model';\nimport {fitToViewport, screenToWorld, zoomAtPoint} from './viewport';\n\nimport './ComponentMap.scss';\n\nconst b = block('component-map');\nconst {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} = React;\nconst TOOLBAR_ZOOM_FACTOR = 1.2;\nconst WHEEL_ZOOM_SENSITIVITY = 0.002;\nconst CLICK_MOVEMENT_THRESHOLD = 4;\nconst NON_PASSIVE_WHEEL_OPTIONS = {passive: false} as const;\n\ninterface ComponentMapProps {\n data: ComponentMapData;\n}\n\ninterface CanvasSize extends ViewportSize {\n devicePixelRatio: number;\n}\n\ninterface PointerDrag {\n pointerId: number;\n canvas: HTMLCanvasElement;\n start: Point;\n startTransform: ViewportTransform;\n moved: boolean;\n}\n\nconst EMPTY_SIZE: CanvasSize = {width: 0, height: 0, devicePixelRatio: 1};\n\nexport function ComponentMap({data}: ComponentMapProps) {\n const rootRef = useRef<HTMLDivElement>(null);\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const animationFrameRef = useRef<number | null>(null);\n const pointerDragRef = useRef<PointerDrag | null>(null);\n const layout = useMemo(() => layoutComponentMap(data), [data]);\n const [size, setSize] = useState<CanvasSize>(EMPTY_SIZE);\n const [transform, setTransform] = useState<ViewportTransform | null>(null);\n const [searchQuery, setSearchQuery] = useState('');\n const [selectedNodeId, setSelectedNodeId] = useState<string>();\n const [selectedEdgeKeys, setSelectedEdgeKeys] = useState<Set<string>>(() => new Set());\n const [hoveredNodeId, setHoveredNodeId] = useState<string>();\n const [hoveredEdgeKey, setHoveredEdgeKey] = useState<string>();\n const [dragging, setDragging] = useState(false);\n const fit = useMemo(\n () => (size.width > 0 && size.height > 0 ? fitToViewport(layout.bounds, size) : undefined),\n [layout, size],\n );\n const emphasis = useMemo(\n () => getEmphasis(data, {selectedNodeId, searchQuery}),\n [data, searchQuery, selectedNodeId],\n );\n const selectedNode = layout.nodes.find(({id}) => id === selectedNodeId);\n const validEdgeKeys = useMemo(\n () => new Set(layout.edges.map(({source, target}) => getEdgeKey(source.id, target.id))),\n [layout],\n );\n\n useEffect(() => {\n const root = rootRef.current;\n if (!root) {\n return undefined;\n }\n\n const updateSize = ({width, height}: Pick<DOMRectReadOnly, 'width' | 'height'>) => {\n setSize({\n width,\n height,\n devicePixelRatio: window.devicePixelRatio || 1,\n });\n };\n const observer = new ResizeObserver(([entry]) => updateSize(entry.contentRect));\n observer.observe(root);\n\n return () => observer.disconnect();\n }, []);\n\n useEffect(() => {\n if (fit) {\n setTransform(fit);\n }\n }, [fit]);\n\n useEffect(() => {\n setSelectedNodeId((current) =>\n current && layout.nodes.some(({id}) => id === current) ? current : undefined,\n );\n setSelectedEdgeKeys((current) => {\n const next = new Set([...current].filter((key) => validEdgeKeys.has(key)));\n return next.size === current.size ? current : next;\n });\n setHoveredNodeId((current) =>\n current && layout.nodes.some(({id}) => id === current) ? current : undefined,\n );\n setHoveredEdgeKey((current) =>\n current && validEdgeKeys.has(current) ? current : undefined,\n );\n }, [layout, validEdgeKeys]);\n\n useLayoutEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || size.width <= 0 || size.height <= 0) {\n return;\n }\n\n canvas.width = Math.max(1, Math.round(size.width * size.devicePixelRatio));\n canvas.height = Math.max(1, Math.round(size.height * size.devicePixelRatio));\n }, [data.nodes.length, size]);\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas || !transform || size.width <= 0 || size.height <= 0) {\n return undefined;\n }\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n\n animationFrameRef.current = -1;\n const frame = requestAnimationFrame(() => {\n animationFrameRef.current = null;\n const context = canvas.getContext('2d');\n if (context) {\n drawComponentMap(context, layout, transform, {\n viewport: size,\n devicePixelRatio: size.devicePixelRatio,\n selectedNodeId,\n hoveredEdgeKey,\n selectedEdgeKeys,\n emphasis,\n });\n }\n });\n if (animationFrameRef.current !== null) {\n animationFrameRef.current = frame;\n }\n\n return () => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n };\n }, [emphasis, hoveredEdgeKey, layout, selectedEdgeKeys, selectedNodeId, size, transform]);\n\n const getCanvasPoint = useCallback((clientX: number, clientY: number) => {\n const bounds = canvasRef.current?.getBoundingClientRect();\n return {\n x: clientX - (bounds?.left ?? 0),\n y: clientY - (bounds?.top ?? 0),\n };\n }, []);\n\n const zoom = useCallback(\n (factor: number, point: Point) => {\n if (!fit) {\n return;\n }\n setTransform((current) =>\n current\n ? zoomAtPoint(current, factor, point, fit.scale * 0.5, fit.scale * 8)\n : current,\n );\n },\n [fit],\n );\n\n const zoomFromToolbar = useCallback(\n (factor: number) => zoom(factor, {x: size.width / 2, y: size.height / 2}),\n [size, zoom],\n );\n\n const selectNode = useCallback((nodeId?: string) => {\n if (nodeId) {\n setSearchQuery('');\n }\n setSelectedNodeId(nodeId);\n setSelectedEdgeKeys((current) => (current.size ? new Set() : current));\n }, []);\n\n const selectEdge = useCallback((edgeKey: string, additive: boolean) => {\n setSelectedEdgeKeys((current) => {\n if (!additive) {\n return new Set([edgeKey]);\n }\n const next = new Set(current);\n if (next.has(edgeKey)) {\n next.delete(edgeKey);\n } else {\n next.add(edgeKey);\n }\n return next;\n });\n }, []);\n\n const getHitTarget = useCallback(\n (point: Point, camera: ViewportTransform) => {\n const worldPoint = screenToWorld(point, camera);\n const node = hitTest(layout, worldPoint);\n if (node) {\n return {node, edge: undefined};\n }\n const orderedEdges = orderEdgesForInteraction(\n layout.edges,\n hoveredEdgeKey,\n selectedEdgeKeys,\n );\n return {\n node: undefined,\n edge: hitTestEdge(orderedEdges, worldPoint, getEdgeHitTolerance(camera.scale)),\n };\n },\n [hoveredEdgeKey, layout, selectedEdgeKeys],\n );\n\n const updateHover = useCallback(\n (point: Point, camera: ViewportTransform) => {\n const {node, edge} = getHitTarget(point, camera);\n setHoveredNodeId(node?.id);\n setHoveredEdgeKey(edge ? getEdgeKey(edge.source.id, edge.target.id) : undefined);\n },\n [getHitTarget],\n );\n\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) {\n return undefined;\n }\n\n const handleWheel = (event: WheelEvent) => {\n event.preventDefault();\n const factor = Math.exp(-event.deltaY * WHEEL_ZOOM_SENSITIVITY);\n zoom(factor, getCanvasPoint(event.clientX, event.clientY));\n };\n canvas.addEventListener('wheel', handleWheel, NON_PASSIVE_WHEEL_OPTIONS);\n\n return () => canvas.removeEventListener('wheel', handleWheel, false);\n }, [data.nodes.length, getCanvasPoint, zoom]);\n\n const handlePointerDown = useCallback(\n (event: React.PointerEvent<HTMLCanvasElement>) => {\n if (!transform) {\n return;\n }\n const point = getCanvasPoint(event.clientX, event.clientY);\n pointerDragRef.current = {\n pointerId: event.pointerId,\n canvas: event.currentTarget,\n start: point,\n startTransform: transform,\n moved: false,\n };\n event.currentTarget.setPointerCapture(event.pointerId);\n },\n [getCanvasPoint, transform],\n );\n\n const handlePointerMove = useCallback(\n (event: React.PointerEvent<HTMLCanvasElement>) => {\n const point = getCanvasPoint(event.clientX, event.clientY);\n const drag = pointerDragRef.current;\n if (!drag || drag.pointerId !== event.pointerId) {\n if (transform) {\n updateHover(point, transform);\n }\n return;\n }\n\n const deltaX = point.x - drag.start.x;\n const deltaY = point.y - drag.start.y;\n const moved = drag.moved || Math.hypot(deltaX, deltaY) >= CLICK_MOVEMENT_THRESHOLD;\n if (!moved) {\n return;\n }\n if (!drag.moved) {\n drag.moved = true;\n setHoveredNodeId(undefined);\n setHoveredEdgeKey(undefined);\n setDragging(true);\n }\n setTransform({\n ...drag.startTransform,\n x: drag.startTransform.x + deltaX,\n y: drag.startTransform.y + deltaY,\n });\n },\n [getCanvasPoint, transform, updateHover],\n );\n\n const releasePointer = useCallback((canvas: HTMLCanvasElement, pointerId: number) => {\n if (canvas.hasPointerCapture(pointerId)) {\n canvas.releasePointerCapture(pointerId);\n }\n }, []);\n\n const finishPointer = useCallback(\n (event: React.PointerEvent<HTMLCanvasElement>, cancelled: boolean) => {\n const drag = pointerDragRef.current;\n if (!drag || drag.pointerId !== event.pointerId) {\n return;\n }\n\n const point = getCanvasPoint(event.clientX, event.clientY);\n const deltaX = point.x - drag.start.x;\n const deltaY = point.y - drag.start.y;\n const moved = drag.moved || Math.hypot(deltaX, deltaY) >= CLICK_MOVEMENT_THRESHOLD;\n if (!cancelled) {\n if (moved) {\n setTransform({\n ...drag.startTransform,\n x: drag.startTransform.x + deltaX,\n y: drag.startTransform.y + deltaY,\n });\n } else {\n const {node, edge} = getHitTarget(point, drag.startTransform);\n if (node) {\n selectNode(node.id);\n } else if (edge) {\n selectEdge(\n getEdgeKey(edge.source.id, edge.target.id),\n event.ctrlKey || event.metaKey,\n );\n } else {\n selectNode(undefined);\n }\n }\n }\n\n releasePointer(event.currentTarget, event.pointerId);\n pointerDragRef.current = null;\n setDragging(false);\n },\n [getCanvasPoint, getHitTarget, releasePointer, selectEdge, selectNode],\n );\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLCanvasElement>) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n setSelectedNodeId(undefined);\n setSelectedEdgeKeys(new Set());\n }\n }, []);\n\n useEffect(\n () => () => {\n const drag = pointerDragRef.current;\n if (drag) {\n releasePointer(drag.canvas, drag.pointerId);\n }\n pointerDragRef.current = null;\n },\n [releasePointer],\n );\n\n if (!data.nodes.length) {\n return (\n <div ref={rootRef} className={b()}>\n <p className={b('empty')}>No significant components found</p>\n </div>\n );\n }\n\n return (\n <div ref={rootRef} className={b()}>\n <canvas\n ref={canvasRef}\n className={b('canvas', {\n dragging,\n interactive: Boolean(hoveredNodeId || hoveredEdgeKey),\n })}\n role=\"img\"\n aria-label=\"Significant component dependency map\"\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onPointerCancel={(event) => finishPointer(event, true)}\n onPointerDown={handlePointerDown}\n onPointerLeave={() => {\n setHoveredNodeId(undefined);\n setHoveredEdgeKey(undefined);\n }}\n onPointerMove={handlePointerMove}\n onPointerUp={(event) => finishPointer(event, false)}\n />\n <div className={b('toolbar')}>\n <TextInput\n className={b('search')}\n size=\"l\"\n hasClear\n placeholder=\"Search components\"\n value={searchQuery}\n controlProps={{'aria-label': 'Search components'}}\n onUpdate={setSearchQuery}\n onFocus={() => {\n setSelectedNodeId(undefined);\n setSelectedEdgeKeys(new Set());\n }}\n />\n <Button\n className={b('control')}\n size=\"l\"\n view=\"outlined\"\n aria-label=\"Zoom in\"\n onClick={() => zoomFromToolbar(TOOLBAR_ZOOM_FACTOR)}\n >\n +\n </Button>\n <Button\n className={b('control')}\n size=\"l\"\n view=\"outlined\"\n aria-label=\"Zoom out\"\n onClick={() => zoomFromToolbar(1 / TOOLBAR_ZOOM_FACTOR)}\n >\n −\n </Button>\n <Button\n className={b('control')}\n size=\"l\"\n view=\"outlined\"\n aria-label=\"Fit graph\"\n onClick={() => setTransform(fit ?? null)}\n >\n Fit\n </Button>\n </div>\n <output className={b('live')} aria-live=\"polite\">\n {selectedNode ? `Selected ${selectedNode.label}` : ''}\n </output>\n </div>\n );\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { ComponentMapEmphasis, ComponentMapLayout, ViewportSize, ViewportTransform } from "./model.js";
2
+ export declare const PALETTE: {
3
+ readonly bandHeading: "#4d5562";
4
+ readonly edge: "#7d8798";
5
+ readonly label: "#1f2733";
6
+ readonly nodeFill: {
7
+ readonly block: "#d9e8ff";
8
+ readonly 'sub-block': "#e5ddff";
9
+ readonly component: "#e4ebf2";
10
+ };
11
+ readonly nodeOutline: "#748094";
12
+ readonly selectedFill: "#ffbe5c";
13
+ readonly selectedOutline: "#9a5700";
14
+ };
15
+ export interface ComponentMapDrawOptions {
16
+ viewport: ViewportSize;
17
+ devicePixelRatio: number;
18
+ selectedNodeId?: string;
19
+ hoveredEdgeKey?: string;
20
+ selectedEdgeKeys: ReadonlySet<string>;
21
+ emphasis: ComponentMapEmphasis;
22
+ }
23
+ export declare function drawComponentMap(canvasContext: CanvasRenderingContext2D, layout: ComponentMapLayout, transform: ViewportTransform, options: ComponentMapDrawOptions): void;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PALETTE = void 0;
4
+ exports.drawComponentMap = drawComponentMap;
5
+ const edgeGeometry_1 = require("./edgeGeometry.js");
6
+ const graph_1 = require("./graph.js");
7
+ const layout_1 = require("./layout.js");
8
+ exports.PALETTE = {
9
+ bandHeading: '#4d5562',
10
+ edge: '#7d8798',
11
+ label: '#1f2733',
12
+ nodeFill: {
13
+ block: '#d9e8ff',
14
+ 'sub-block': '#e5ddff',
15
+ component: '#e4ebf2',
16
+ },
17
+ nodeOutline: '#748094',
18
+ selectedFill: '#ffbe5c',
19
+ selectedOutline: '#9a5700',
20
+ };
21
+ const NODE_RADIUS = 10;
22
+ const NODE_DIMMED_OPACITY = 0.16;
23
+ const EDGE_DIMMED_OPACITY = 0.1;
24
+ const EDGE_WIDTH = 1.5;
25
+ const INTERACTIVE_EDGE_WIDTH = 3;
26
+ const INTERACTIVE_EDGE_COLOR = '#ffbe5c';
27
+ function roundedRect(context, x, y, width, height, radius) {
28
+ const right = x + width;
29
+ const bottom = y + height;
30
+ context.beginPath();
31
+ context.moveTo(x + radius, y);
32
+ context.lineTo(right - radius, y);
33
+ context.quadraticCurveTo(right, y, right, y + radius);
34
+ context.lineTo(right, bottom - radius);
35
+ context.quadraticCurveTo(right, bottom, right - radius, bottom);
36
+ context.lineTo(x + radius, bottom);
37
+ context.quadraticCurveTo(x, bottom, x, bottom - radius);
38
+ context.lineTo(x, y + radius);
39
+ context.quadraticCurveTo(x, y, x + radius, y);
40
+ context.closePath();
41
+ }
42
+ function drawArrowhead(context, arrowhead) {
43
+ context.beginPath();
44
+ context.moveTo(arrowhead[0].x, arrowhead[0].y);
45
+ context.lineTo(arrowhead[1].x, arrowhead[1].y);
46
+ context.lineTo(arrowhead[2].x, arrowhead[2].y);
47
+ context.closePath();
48
+ context.fill();
49
+ }
50
+ function drawEdge(canvasContext, source, target, { active, hovered, selected }) {
51
+ const context = canvasContext;
52
+ const { start, end, firstControl, secondControl, arrowhead } = (0, edgeGeometry_1.getEdgeGeometry)(source, target);
53
+ const color = selected || hovered ? INTERACTIVE_EDGE_COLOR : exports.PALETTE.edge;
54
+ context.save();
55
+ context.globalAlpha = selected || active ? 1 : EDGE_DIMMED_OPACITY;
56
+ context.strokeStyle = color;
57
+ context.fillStyle = color;
58
+ context.lineWidth = selected || hovered ? INTERACTIVE_EDGE_WIDTH : EDGE_WIDTH;
59
+ context.beginPath();
60
+ context.moveTo(start.x, start.y);
61
+ context.bezierCurveTo(firstControl.x, firstControl.y, secondControl.x, secondControl.y, end.x, end.y);
62
+ context.stroke();
63
+ drawArrowhead(context, arrowhead);
64
+ context.restore();
65
+ }
66
+ function drawNode(canvasContext, node, active, selected) {
67
+ const context = canvasContext;
68
+ const { x, y, width, height } = node.rect;
69
+ context.save();
70
+ context.globalAlpha = active ? 1 : NODE_DIMMED_OPACITY;
71
+ context.fillStyle = selected ? exports.PALETTE.selectedFill : exports.PALETTE.nodeFill[node.kind];
72
+ context.strokeStyle = selected ? exports.PALETTE.selectedOutline : exports.PALETTE.nodeOutline;
73
+ context.lineWidth = selected ? 3 : 1.5;
74
+ roundedRect(context, x, y, width, height, NODE_RADIUS);
75
+ context.fill();
76
+ context.stroke();
77
+ context.restore();
78
+ }
79
+ function drawLabel(canvasContext, node, active) {
80
+ const context = canvasContext;
81
+ const { x, y, width, height } = node.rect;
82
+ context.save();
83
+ context.globalAlpha = active ? 1 : NODE_DIMMED_OPACITY;
84
+ context.fillStyle = exports.PALETTE.label;
85
+ context.font = layout_1.NODE_LABEL_FONT;
86
+ context.textAlign = 'center';
87
+ context.textBaseline = 'middle';
88
+ context.fillText(node.label, x + width / 2, y + height / 2);
89
+ context.restore();
90
+ }
91
+ function drawComponentMap(canvasContext, layout, transform, options) {
92
+ const context = canvasContext;
93
+ const { viewport, devicePixelRatio, selectedNodeId, hoveredEdgeKey, selectedEdgeKeys, emphasis } = options;
94
+ const hasEmphasis = emphasis.mode !== 'none';
95
+ context.resetTransform();
96
+ context.scale(devicePixelRatio, devicePixelRatio);
97
+ context.clearRect(0, 0, viewport.width, viewport.height);
98
+ context.translate(transform.x, transform.y);
99
+ context.scale(transform.scale, transform.scale);
100
+ context.save();
101
+ context.fillStyle = exports.PALETTE.bandHeading;
102
+ context.font = '700 18px sans-serif';
103
+ context.textAlign = 'left';
104
+ context.textBaseline = 'top';
105
+ for (const band of layout.bands) {
106
+ context.fillText(band.label, band.bounds.x, band.bounds.y);
107
+ }
108
+ context.restore();
109
+ const edgeState = ({ source, target }) => {
110
+ const key = (0, graph_1.getEdgeKey)(source.id, target.id);
111
+ return {
112
+ key,
113
+ selected: selectedEdgeKeys.has(key),
114
+ hovered: key === hoveredEdgeKey,
115
+ };
116
+ };
117
+ const orderedEdges = (0, edgeGeometry_1.orderEdgesForInteraction)(layout.edges, hoveredEdgeKey, selectedEdgeKeys);
118
+ for (const edge of orderedEdges) {
119
+ const { source, target } = edge;
120
+ const { key, selected, hovered } = edgeState(edge);
121
+ const active = emphasis.mode === 'none' ||
122
+ (emphasis.mode === 'selection' && emphasis.activeEdgeIds.has(key));
123
+ drawEdge(context, source, target, { active, hovered, selected });
124
+ }
125
+ for (const node of layout.nodes) {
126
+ const active = !hasEmphasis || emphasis.activeNodeIds.has(node.id);
127
+ drawNode(context, node, active, emphasis.mode === 'selection' && node.id === selectedNodeId);
128
+ }
129
+ for (const node of layout.nodes) {
130
+ const active = !hasEmphasis || emphasis.activeNodeIds.has(node.id);
131
+ drawLabel(context, node, active);
132
+ }
133
+ }
134
+ //# sourceMappingURL=draw.js.map