@likec4/diagram 1.23.1 → 1.24.1

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 (118) hide show
  1. package/dist/LikeC4Diagram.css.d.ts +5 -0
  2. package/dist/LikeC4Diagram.css.js +1 -0
  3. package/dist/LikeC4Diagram.d.ts +1 -1
  4. package/dist/LikeC4Diagram.js +6 -2
  5. package/dist/LikeC4Diagram.props.d.ts +6 -0
  6. package/dist/StaticLikeC4Diagram.js +2 -1
  7. package/dist/base/BaseXYFlow.d.ts +1 -1
  8. package/dist/base/BaseXYFlow.js +12 -3
  9. package/dist/base/const.d.ts +1 -1
  10. package/dist/base/const.js +1 -1
  11. package/dist/base/primitives/compound/CompoundActionButton.css.js +2 -0
  12. package/dist/base/primitives/compound/CompoundDetailsButton.css.js +2 -0
  13. package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -1
  14. package/dist/base/primitives/compound/CompoundNodeContainer.css.js +4 -4
  15. package/dist/base/primitives/compound/CompoundNodeContainer.js +35 -22
  16. package/dist/base/primitives/compound/CompoundTitle.css.js +3 -1
  17. package/dist/base/primitives/customNode.js +1 -1
  18. package/dist/base/primitives/edge/EdgeContainer.js +5 -4
  19. package/dist/base/primitives/edge/EdgeLabel.css.d.ts +0 -1
  20. package/dist/base/primitives/edge/EdgeLabel.css.js +2 -5
  21. package/dist/base/primitives/edge/EdgeLabel.js +5 -5
  22. package/dist/base/primitives/edge/edge.css.d.ts +1 -1
  23. package/dist/base/primitives/edge/edge.css.js +4 -5
  24. package/dist/base/primitives/element/ElementActionButtons.css.js +2 -0
  25. package/dist/base/primitives/element/ElementActionButtons.js +4 -3
  26. package/dist/base/primitives/element/ElementDetailsButton.css.js +2 -0
  27. package/dist/base/primitives/element/ElementNodeContainer.css.js +2 -0
  28. package/dist/base/primitives/element/ElementNodeContainer.js +3 -1
  29. package/dist/base/primitives/element/ElementShape.css.js +4 -2
  30. package/dist/base/primitives/element/ElementShape.js +2 -3
  31. package/dist/base/primitives/element/ElementTitle.css.js +2 -0
  32. package/dist/base/primitives/element/styles.css.js +2 -0
  33. package/dist/components/SearchControl.css.js +2 -0
  34. package/dist/context/FramerMotionConfig.d.ts +9 -1
  35. package/dist/context/FramerMotionConfig.js +5 -2
  36. package/dist/context/RootContainer.js +13 -1
  37. package/dist/diagram.css +1 -1
  38. package/dist/globals.css.d.ts +2 -0
  39. package/dist/hooks/safeContext.js +1 -0
  40. package/dist/hooks/useDiagram.d.ts +415 -693
  41. package/dist/hooks/useDiagram.js +1 -0
  42. package/dist/hooks/useIsReducedGraphics.d.ts +4 -0
  43. package/dist/hooks/useIsReducedGraphics.js +8 -0
  44. package/dist/hooks/useXYFlow.js +1 -1
  45. package/dist/likec4diagram/DiagramUI.d.ts +2 -1
  46. package/dist/likec4diagram/DiagramUI.js +5 -3
  47. package/dist/likec4diagram/custom/nodes/nodeTypes.js +2 -2
  48. package/dist/likec4diagram/ui/controls/styles.css.js +2 -0
  49. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.css.js +2 -0
  50. package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +3 -4
  51. package/dist/likec4diagram/ui/search/LikeC4Search.css.js +2 -0
  52. package/dist/overlays/element-details/ElementDetailsCard.js +12 -8
  53. package/dist/overlays/element-details/actor.d.ts +9084 -2569
  54. package/dist/overlays/overlay/Overlay.js +6 -3
  55. package/dist/overlays/relationship-details/RelationshipDetails.js +31 -17
  56. package/dist/overlays/relationship-details/actor.d.ts +228 -3
  57. package/dist/overlays/relationship-details/actor.js +86 -14
  58. package/dist/overlays/relationship-details/hooks.d.ts +3 -0
  59. package/dist/overlays/relationship-details/hooks.js +6 -2
  60. package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -3
  61. package/dist/overlays/relationships-browser/RelationshipsBrowser.js +12 -3
  62. package/dist/overlays/relationships-browser/actor.d.ts +271 -54
  63. package/dist/overlays/relationships-browser/actor.js +86 -23
  64. package/dist/state/DiagramActorProvider.js +12 -2
  65. package/dist/state/diagram-machine.d.ts +426 -694
  66. package/dist/state/diagram-machine.js +89 -68
  67. package/dist/state/persistence.d.ts +5 -0
  68. package/dist/state/persistence.js +23 -0
  69. package/package.json +9 -9
  70. package/src/LikeC4Diagram.css.ts +13 -5
  71. package/src/LikeC4Diagram.props.ts +7 -0
  72. package/src/LikeC4Diagram.tsx +33 -24
  73. package/src/StaticLikeC4Diagram.tsx +2 -1
  74. package/src/base/BaseXYFlow.tsx +14 -9
  75. package/src/base/const.ts +1 -1
  76. package/src/base/primitives/compound/CompoundActionButton.css.ts +6 -0
  77. package/src/base/primitives/compound/CompoundDetailsButton.css.ts +4 -0
  78. package/src/base/primitives/compound/CompoundNodeContainer.css.ts +41 -59
  79. package/src/base/primitives/compound/CompoundNodeContainer.tsx +42 -22
  80. package/src/base/primitives/compound/CompoundTitle.css.ts +14 -13
  81. package/src/base/primitives/customNode.ts +3 -2
  82. package/src/base/primitives/edge/EdgeContainer.tsx +4 -3
  83. package/src/base/primitives/edge/EdgeLabel.css.ts +17 -11
  84. package/src/base/primitives/edge/EdgeLabel.tsx +4 -4
  85. package/src/base/primitives/edge/edge.css.ts +47 -27
  86. package/src/base/primitives/element/ElementActionButtons.css.ts +4 -0
  87. package/src/base/primitives/element/ElementActionButtons.tsx +4 -2
  88. package/src/base/primitives/element/ElementDetailsButton.css.ts +6 -8
  89. package/src/base/primitives/element/ElementNodeContainer.css.ts +18 -21
  90. package/src/base/primitives/element/ElementNodeContainer.tsx +3 -1
  91. package/src/base/primitives/element/ElementShape.css.ts +10 -6
  92. package/src/base/primitives/element/ElementShape.tsx +2 -2
  93. package/src/components/SearchControl.css.ts +11 -4
  94. package/src/context/FramerMotionConfig.tsx +13 -2
  95. package/src/context/RootContainer.tsx +9 -2
  96. package/src/globals.css.ts +3 -0
  97. package/src/hooks/safeContext.tsx +2 -1
  98. package/src/hooks/useDiagram.ts +1 -0
  99. package/src/hooks/useIsReducedGraphics.ts +5 -0
  100. package/src/hooks/useXYFlow.ts +1 -1
  101. package/src/likec4diagram/DiagramUI.tsx +5 -3
  102. package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
  103. package/src/likec4diagram/ui/controls/styles.css.ts +11 -1
  104. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.css.ts +10 -3
  105. package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +6 -3
  106. package/src/likec4diagram/ui/search/LikeC4Search.css.ts +1 -0
  107. package/src/overlays/element-details/ElementDetailsCard.tsx +238 -231
  108. package/src/overlays/overlay/Overlay.css.ts +1 -9
  109. package/src/overlays/overlay/Overlay.tsx +6 -3
  110. package/src/overlays/relationship-details/RelationshipDetails.tsx +83 -66
  111. package/src/overlays/relationship-details/actor.ts +106 -13
  112. package/src/overlays/relationship-details/hooks.ts +6 -2
  113. package/src/overlays/relationships-browser/-useRelationshipsView.ts +10 -0
  114. package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +16 -8
  115. package/src/overlays/relationships-browser/actor.ts +92 -22
  116. package/src/state/DiagramActorProvider.tsx +13 -2
  117. package/src/state/diagram-machine.ts +88 -65
  118. package/src/state/persistence.ts +22 -0
@@ -1,17 +1,18 @@
1
- import { nonNullable, invariant, nonexhaustive, DiagramNode, getBBoxCenter, getParallelStepsPrefix } from "@likec4/core";
1
+ import { nonNullable, invariant, DiagramNode, nonexhaustive, getBBoxCenter, getParallelStepsPrefix } from "@likec4/core";
2
2
  import { getViewportForBounds, applyEdgeChanges, applyNodeChanges } from "@xyflow/react";
3
3
  import { nodeToRect } from "@xyflow/system";
4
4
  import { hasAtLeast, prop, clamp, first } from "remeda";
5
- import { setup, enqueueActions, assertEvent, stopChild, assign, sendTo, cancel, raise, spawnChild, and, or } from "xstate";
5
+ import { setup, enqueueActions, assign, assertEvent, stopChild, sendTo, cancel, raise, spawnChild, and, or } from "xstate";
6
6
  import { MinZoom } from "../base/const.js";
7
7
  import { AllDisabled } from "../context/DiagramFeatures.js";
8
8
  import { createLayoutConstraints } from "../likec4diagram/useLayoutConstraints.js";
9
9
  import { overlaysActorLogic } from "../overlays/overlaysActor.js";
10
10
  import { getAligner, toNodeRect } from "./aligners.js";
11
- import { resetEdgeControlPoints, updateEdgeData, updateNodeData, updateNavigationHistory, mergeXYNodesEdges, navigateForward, navigateBack, unfocusNodesEdges, updateActiveWalkthrough, focusNodesEdges, lastClickedNode } from "./assign.js";
11
+ import { lastClickedNode, resetEdgeControlPoints, updateEdgeData, updateNodeData, updateNavigationHistory, mergeXYNodesEdges, navigateForward, navigateBack, unfocusNodesEdges, updateActiveWalkthrough, focusNodesEdges } from "./assign.js";
12
12
  import { hotkeyActorLogic } from "./hotkeyActor.js";
13
+ import { DiagramToggledFeaturesPersistence } from "./persistence.js";
13
14
  import { syncManualLayoutActorLogic } from "./syncManualLayoutActor.js";
14
- import { typedSystem, focusedBounds } from "./utils.js";
15
+ import { typedSystem, findDiagramNode, focusedBounds } from "./utils.js";
15
16
  const diagramMachine = setup({
16
17
  types: {
17
18
  context: {},
@@ -40,7 +41,7 @@ const diagramMachine = setup({
40
41
  }
41
42
  nonexhaustive(event.type);
42
43
  },
43
- "click: selected node ": ({ event }) => {
44
+ "click: selected node": ({ event }) => {
44
45
  assertEvent(event, "xyflow.nodeClick");
45
46
  return event.node.selected === true;
46
47
  },
@@ -52,6 +53,10 @@ const diagramMachine = setup({
52
53
  "click: focused node": ({ context, event }) => {
53
54
  assertEvent(event, "xyflow.nodeClick");
54
55
  return context.focusedNode === event.node.id;
56
+ },
57
+ "click: node has connections": ({ context, event }) => {
58
+ assertEvent(event, "xyflow.nodeClick");
59
+ return context.xyedges.some((e) => e.source === event.node.id || e.target === event.node.id);
55
60
  }
56
61
  },
57
62
  actions: {
@@ -61,6 +66,24 @@ const diagramMachine = setup({
61
66
  },
62
67
  "trigger:OpenSource": (_, _params) => {
63
68
  },
69
+ "assign lastClickedNode": assign(({ context, event }) => {
70
+ assertEvent(event, "xyflow.nodeClick");
71
+ return {
72
+ lastClickedNode: lastClickedNode({ context, event })
73
+ };
74
+ }),
75
+ "open source of focused or last clicked node": enqueueActions(({ context, enqueue }) => {
76
+ var _a;
77
+ const nodeId = context.focusedNode ?? ((_a = context.lastClickedNode) == null ? void 0 : _a.id);
78
+ if (!nodeId || !context.features.enableVscode) return;
79
+ const diagramNode = findDiagramNode(context, nodeId);
80
+ if (!diagramNode) return;
81
+ if (DiagramNode.deploymentRef(diagramNode)) {
82
+ enqueue.raise({ type: "open.source", deployment: DiagramNode.deploymentRef(diagramNode) });
83
+ } else if (DiagramNode.modelRef(diagramNode)) {
84
+ enqueue.raise({ type: "open.source", element: DiagramNode.modelRef(diagramNode) });
85
+ }
86
+ }),
64
87
  "xyflow:fitDiagram": ({ context }, params) => {
65
88
  const {
66
89
  bounds = context.view.bounds,
@@ -166,7 +189,7 @@ const diagramMachine = setup({
166
189
  context: ({ input, self, spawn }) => ({
167
190
  ...input,
168
191
  features: { ...AllDisabled },
169
- toggledFeatures: {
192
+ toggledFeatures: DiagramToggledFeaturesPersistence.read() ?? {
170
193
  enableReadOnly: true
171
194
  },
172
195
  initialized: false,
@@ -225,9 +248,10 @@ const diagramMachine = setup({
225
248
  // TODO: xstate fails to infer the type of the guard
226
249
  guard: and([
227
250
  "enabled: FocusMode",
251
+ "click: node has connections",
228
252
  or([
229
253
  "click: same node",
230
- "click: selected node "
254
+ "click: selected node"
231
255
  ])
232
256
  ]),
233
257
  actions: [
@@ -239,9 +263,10 @@ const diagramMachine = setup({
239
263
  target: "focused"
240
264
  },
241
265
  {
242
- actions: assign({
243
- lastClickedNode
244
- })
266
+ actions: [
267
+ "assign lastClickedNode",
268
+ "open source of focused or last clicked node"
269
+ ]
245
270
  }
246
271
  ],
247
272
  "xyflow.paneClick": {
@@ -284,6 +309,7 @@ const diagramMachine = setup({
284
309
  ...focusNodesEdges(s),
285
310
  viewportBeforeFocus: { ...s.context.viewport }
286
311
  })),
312
+ "open source of focused or last clicked node",
287
313
  spawnChild("hotkeyActorLogic", { id: "hotkey" }),
288
314
  {
289
315
  type: "xyflow:fitDiagram",
@@ -316,6 +342,7 @@ const diagramMachine = setup({
316
342
  focusedNode: ({ event }) => event.node.id
317
343
  }),
318
344
  assign(focusNodesEdges),
345
+ "open source of focused or last clicked node",
319
346
  {
320
347
  type: "xyflow:fitDiagram",
321
348
  params: focusedBounds
@@ -329,6 +356,7 @@ const diagramMachine = setup({
329
356
  focusedNode: ({ event }) => event.nodeId
330
357
  }),
331
358
  assign(focusNodesEdges),
359
+ "open source of focused or last clicked node",
332
360
  {
333
361
  type: "xyflow:fitDiagram",
334
362
  params: focusedBounds
@@ -360,69 +388,60 @@ const diagramMachine = setup({
360
388
  },
361
389
  // Navigating to another view (after `navigateTo` event)
362
390
  navigating: {
363
- initial: "pending",
364
- type: "compound",
365
- states: {
366
- pending: {
367
- entry: enqueueActions(({ enqueue }) => {
368
- enqueue("closeAllOverlays");
369
- enqueue("stopSyncLayout");
370
- enqueue({
371
- type: "trigger:NavigateTo",
372
- params: ({ context }) => ({
373
- viewId: nonNullable(context.lastOnNavigate, "Invalid state, lastOnNavigate is null").toView
374
- })
375
- });
376
- }),
377
- on: {
378
- "update.view": {
379
- actions: enqueueActions(({ enqueue, context, event }) => {
380
- const { fromNode, toNode } = findCorrespondingNode(context, event);
381
- if (fromNode && toNode) {
382
- enqueue({
383
- type: "xyflow:alignNodeFromToAfterNavigate",
384
- params: {
385
- fromNode: fromNode.id,
386
- toPosition: {
387
- x: toNode.position[0],
388
- y: toNode.position[1]
389
- }
390
- }
391
- });
392
- enqueue.raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 80 });
393
- } else {
394
- enqueue({
395
- type: "xyflow:setViewportCenter",
396
- params: getBBoxCenter(event.view.bounds)
397
- });
398
- enqueue.raise({ type: "fitDiagram", duration: 200 }, { id: "fitDiagram", delay: 25 });
399
- }
400
- enqueue.assign(updateNavigationHistory);
401
- enqueue.assign(mergeXYNodesEdges);
402
- }),
403
- target: "done"
404
- }
405
- }
406
- },
407
- done: {
408
- type: "final"
391
+ entry: [
392
+ "closeAllOverlays",
393
+ "stopSyncLayout",
394
+ {
395
+ type: "trigger:NavigateTo",
396
+ params: ({ context }) => ({
397
+ viewId: nonNullable(context.lastOnNavigate, "Invalid state, lastOnNavigate is null").toView
398
+ })
409
399
  }
410
- },
411
- onDone: {
412
- target: "idle"
413
- },
400
+ ],
414
401
  exit: assign({
415
402
  syncLayoutActorRef: ({ self, context, spawn }) => spawn("syncManualLayoutActorLogic", {
416
403
  id: "syncLayout",
417
404
  systemId: "syncLayout",
418
405
  input: { parent: self, viewId: context.view.id }
419
- }),
420
- lastOnNavigate: null
421
- })
406
+ })
407
+ // lastOnNavigate: null,
408
+ }),
409
+ on: {
410
+ "update.view": {
411
+ actions: enqueueActions(({ enqueue, context, event }) => {
412
+ const { fromNode, toNode } = findCorrespondingNode(context, event);
413
+ if (fromNode && toNode) {
414
+ enqueue({
415
+ type: "xyflow:alignNodeFromToAfterNavigate",
416
+ params: {
417
+ fromNode: fromNode.id,
418
+ toPosition: {
419
+ x: toNode.position[0],
420
+ y: toNode.position[1]
421
+ }
422
+ }
423
+ });
424
+ enqueue.raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 80 });
425
+ } else {
426
+ enqueue({
427
+ type: "xyflow:setViewportCenter",
428
+ params: getBBoxCenter(event.view.bounds)
429
+ });
430
+ enqueue.raise({ type: "fitDiagram", duration: 200 }, { id: "fitDiagram", delay: 25 });
431
+ }
432
+ enqueue.assign(updateNavigationHistory);
433
+ enqueue.assign(mergeXYNodesEdges);
434
+ enqueue.assign({
435
+ lastOnNavigate: null
436
+ });
437
+ }),
438
+ target: "#idle"
439
+ }
440
+ }
422
441
  },
423
442
  walkthrough: {
424
443
  entry: [
425
- // spawnChild('hotkeyActor', { id: 'hotkey' }),
444
+ spawnChild("hotkeyActorLogic", { id: "hotkey" }),
426
445
  assign({
427
446
  viewportBeforeFocus: ({ context }) => context.viewport,
428
447
  activeWalkthrough: ({ context, event }) => {
@@ -682,10 +701,12 @@ const diagramMachine = setup({
682
701
  },
683
702
  "toggle.feature": {
684
703
  actions: assign({
685
- toggledFeatures: ({ context, event }) => ({
686
- ...context.toggledFeatures,
687
- [`enable${event.feature}`]: event.forceValue ?? !(context.toggledFeatures[`enable${event.feature}`] ?? context.features[`enable${event.feature}`])
688
- })
704
+ toggledFeatures: ({ context, event }) => {
705
+ return DiagramToggledFeaturesPersistence.write({
706
+ ...context.toggledFeatures,
707
+ [`enable${event.feature}`]: event.forceValue ?? !(context.toggledFeatures[`enable${event.feature}`] ?? context.features[`enable${event.feature}`])
708
+ });
709
+ }
689
710
  })
690
711
  }
691
712
  },
@@ -0,0 +1,5 @@
1
+ import { EnabledFeatures } from '../context/DiagramFeatures';
2
+ export declare const DiagramToggledFeaturesPersistence: {
3
+ read(): Partial<EnabledFeatures> | null;
4
+ write<F extends Partial<EnabledFeatures>>(toggledFeatures: F): F;
5
+ };
@@ -0,0 +1,23 @@
1
+ import { pickBy, isBoolean } from "remeda";
2
+ const key = `likec4:diagram:toggledFeatures`;
3
+ const DiagramToggledFeaturesPersistence = {
4
+ read() {
5
+ try {
6
+ let fromStorage = sessionStorage.getItem(key);
7
+ if (fromStorage) {
8
+ return JSON.parse(fromStorage);
9
+ }
10
+ throw new Error(`Workspace ${key} not found`);
11
+ } catch (e) {
12
+ console.error(`Error reading fromStorage ${key}:`, e);
13
+ return null;
14
+ }
15
+ },
16
+ write(toggledFeatures) {
17
+ sessionStorage.setItem(key, JSON.stringify(pickBy(toggledFeatures, isBoolean)));
18
+ return toggledFeatures;
19
+ }
20
+ };
21
+ export {
22
+ DiagramToggledFeaturesPersistence
23
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.23.1",
3
+ "version": "1.24.1",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -37,21 +37,21 @@
37
37
  "@tabler/icons-react": "3.29.0",
38
38
  "@vanilla-extract/dynamic": "^2.1.2",
39
39
  "@xstate/react": "^5.0.2",
40
- "@xyflow/react": "12.4.2",
41
- "@xyflow/system": "0.0.50",
40
+ "@xyflow/react": "12.4.4",
41
+ "@xyflow/system": "0.0.52",
42
42
  "bezier-js": "^6.1.4",
43
43
  "clsx": "^2.1.1",
44
44
  "d3-shape": "^3.2.0",
45
45
  "esm-env": "^1.2.2",
46
46
  "fast-equals": "^5.2.2",
47
- "framer-motion": "^12.4.2",
47
+ "framer-motion": "^12.4.7",
48
48
  "khroma": "^2.1.0",
49
49
  "nanostores": "0.11.3",
50
50
  "react-error-boundary": "^5.0.0",
51
- "remeda": "^2.20.1",
51
+ "remeda": "^2.20.2",
52
52
  "type-fest": "4.34.1",
53
53
  "xstate": "^5.19.2",
54
- "@likec4/core": "1.23.1"
54
+ "@likec4/core": "1.24.1"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "@mantine/core": "7.17.0",
@@ -72,14 +72,14 @@
72
72
  "typescript": "5.7.3",
73
73
  "vite": "6.1.0",
74
74
  "vite-plugin-dts": "^4.5.0",
75
- "vitest": "^3.0.4",
76
- "@likec4/tsconfig": "1.23.1"
75
+ "vitest": "^3.0.6",
76
+ "@likec4/tsconfig": "1.24.1"
77
77
  },
78
78
  "scripts": {
79
79
  "typecheck": "tsc --noEmit",
80
80
  "build": "vite build",
81
81
  "lint": "run -T eslint src/ --fix",
82
- "clean": "rm -r -f -g 'dist/*' .tsbuildinfo",
82
+ "clean": "rm -r -f -g \"dist/*\" .tsbuildinfo",
83
83
  "test": "vitest run --no-isolate"
84
84
  }
85
85
  }
@@ -1,4 +1,5 @@
1
1
  import { globalStyle, style } from '@vanilla-extract/css'
2
+ import { whereNotReducedGraphics, whereReducedGraphics } from './globals.css'
2
3
  import { vars, xyvars } from './theme-vars'
3
4
 
4
5
  export const cssReactFlow = style({
@@ -13,6 +14,10 @@ export const cssReactFlow = style({
13
14
  },
14
15
  })
15
16
 
17
+ export const reactFlowReducedGraphics = `${whereReducedGraphics} ${cssReactFlow}`
18
+ export const reactFlow = `${whereNotReducedGraphics} ${cssReactFlow}`
19
+ export { whereNotReducedGraphics, whereReducedGraphics }
20
+
16
21
  export const notInitialized = style({
17
22
  opacity: 0,
18
23
  })
@@ -30,11 +35,6 @@ globalStyle(`.react-flow${cssReactFlow}`, {
30
35
  globalStyle(`.react-flow${cssReactFlow} .react-flow__pane`, {
31
36
  WebkitUserSelect: 'none',
32
37
  })
33
-
34
- globalStyle(`.react-flow${cssReactFlow} .xyflow__viewport`, {
35
- willChange: 'transform',
36
- })
37
-
38
38
  export const cssDisablePan = style({})
39
39
 
40
40
  export const cssTransparentBg = style({})
@@ -58,3 +58,11 @@ export const hiddenIfZoomTooSmall = style({
58
58
  },
59
59
  },
60
60
  })
61
+
62
+ export const hiddenIfReducedGraphics = style({
63
+ selectors: {
64
+ [`${reactFlowReducedGraphics} &`]: {
65
+ visibility: 'hidden',
66
+ },
67
+ },
68
+ })
@@ -172,6 +172,13 @@ export interface LikeC4DiagramProperties {
172
172
  */
173
173
  experimentalEdgeEditing?: boolean | undefined
174
174
 
175
+ /**
176
+ * Improve performance by hiding certain elements and reducing visual effects (disable mix-blend, shadows, animations)
177
+ *
178
+ * @default 'auto' - will be set to true if view has more then 3000 * 2000 pixels
179
+ */
180
+ reduceGraphics?: 'auto' | boolean | undefined
181
+
175
182
  /**
176
183
  * Render icon for an element, bundled or remote
177
184
  * By default, if icon is http:// or https://, it will be rendered as an image
@@ -9,6 +9,7 @@ import {
9
9
  IconRendererProvider,
10
10
  RootContainer,
11
11
  } from './context'
12
+ import { ReducedGraphicsContext } from './hooks/useIsReducedGraphics'
12
13
  import { LikeC4CustomColors } from './LikeC4CustomColors'
13
14
  import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
14
15
  import type { Types } from './likec4diagram/types'
@@ -41,6 +42,7 @@ export function LikeC4Diagram({
41
42
  initialWidth,
42
43
  initialHeight,
43
44
  experimentalEdgeEditing = false,
45
+ reduceGraphics = 'auto',
44
46
  onCanvasClick,
45
47
  onCanvasContextMenu,
46
48
  onCanvasDblClick,
@@ -81,6 +83,11 @@ export function LikeC4Diagram({
81
83
  }
82
84
  }
83
85
 
86
+ // If view has more then 3000 * 2000 pixels - assume it is a big diagram
87
+ const isReducedGraphicsMode = reduceGraphics === 'auto'
88
+ ? (view.bounds?.width ?? 1) * (view.bounds?.height ?? 1) > 6_000_000
89
+ : reduceGraphics
90
+
84
91
  return (
85
92
  <EnsureMantine>
86
93
  <FramerMotionConfig>
@@ -119,30 +126,32 @@ export function LikeC4Diagram({
119
126
  onOpenSource,
120
127
  onBurgerMenuClick,
121
128
  }}>
122
- <RootContainer className={className}>
123
- {!isEmpty(view.customColorDefinitions) && (
124
- <LikeC4CustomColors customColors={view.customColorDefinitions} />
125
- )}
126
- <XYFlowProvider
127
- fitView={fitView}
128
- {...initialRef.current}
129
- >
130
- <DiagramActorProvider
131
- input={{
132
- view,
133
- pannable,
134
- zoomable,
135
- fitViewPadding,
136
- ...xyNodesEdges,
137
- }}>
138
- <LikeC4DiagramXYFlow
139
- nodesDraggable={nodesDraggable}
140
- nodesSelectable={nodesSelectable}
141
- background={background}
142
- />
143
- </DiagramActorProvider>
144
- </XYFlowProvider>
145
- </RootContainer>
129
+ <ReducedGraphicsContext value={isReducedGraphicsMode}>
130
+ <RootContainer className={className}>
131
+ {!isEmpty(view.customColorDefinitions) && (
132
+ <LikeC4CustomColors customColors={view.customColorDefinitions} />
133
+ )}
134
+ <XYFlowProvider
135
+ fitView={fitView}
136
+ {...initialRef.current}
137
+ >
138
+ <DiagramActorProvider
139
+ input={{
140
+ view,
141
+ pannable,
142
+ zoomable,
143
+ fitViewPadding,
144
+ ...xyNodesEdges,
145
+ }}>
146
+ <LikeC4DiagramXYFlow
147
+ nodesDraggable={nodesDraggable}
148
+ nodesSelectable={nodesSelectable}
149
+ background={background}
150
+ />
151
+ </DiagramActorProvider>
152
+ </XYFlowProvider>
153
+ </RootContainer>
154
+ </ReducedGraphicsContext>
146
155
  </DiagramEventHandlers>
147
156
  </DiagramFeatures>
148
157
  </IconRendererProvider>
@@ -43,11 +43,12 @@ export function StaticLikeC4Diagram({
43
43
  enableRelationshipDetails={enableRelationshipDetails}
44
44
  enableRelationshipBrowser={enableRelationshipBrowser}
45
45
  enableDynamicViewWalkthrough={false}
46
+ showNavigationButtons={false}
46
47
  experimentalEdgeEditing={false}
47
48
  enableFocusMode={false}
49
+ enableSearch={false}
48
50
  nodesSelectable={false}
49
51
  nodesDraggable={false}
50
- enableSearch={false}
51
52
  {...rest}
52
53
  />
53
54
  )
@@ -8,9 +8,11 @@ import {
8
8
  useStoreApi,
9
9
  } from '@xyflow/react'
10
10
  import clsx from 'clsx'
11
+ import { shallowEqual } from 'fast-equals'
11
12
  import { useMemo } from 'react'
12
13
  import type { SetRequired, Simplify } from 'type-fest'
13
- import { useUpdateEffect } from '../hooks'
14
+ import { useIsReducedGraphics } from '../hooks/useIsReducedGraphics'
15
+ import { useUpdateEffect } from '../hooks/useUpdateEffect'
14
16
  import { useIsZoomTooSmall } from '../hooks/useXYFlow'
15
17
  import * as css from '../LikeC4Diagram.css'
16
18
  import { stopPropagation } from '../utils/xyflow'
@@ -41,10 +43,8 @@ export type BaseXYFlowProps<NodeType extends Base.Node, EdgeType extends Base.Ed
41
43
  | 'zoomActivationKeyCode'
42
44
  | 'zoomOnScroll'
43
45
  | 'elementsSelectable'
44
- | 'onNodeMouseEnter'
45
- | 'onNodeMouseLeave'
46
- | 'onEdgeMouseEnter'
47
- | 'onEdgeMouseLeave'
46
+ | 'onNodeDoubleClick'
47
+ | 'onEdgeDoubleClick'
48
48
  | 'fitViewOptions'
49
49
  >,
50
50
  // Required props
@@ -79,6 +79,7 @@ export const BaseXYFlow = <
79
79
  }: BaseXYFlowProps<NodeType, EdgeType>) => {
80
80
  const isBgWithPattern = background !== 'transparent' && background !== 'solid'
81
81
  const isZoomTooSmall = useIsZoomTooSmall()
82
+ const reduceGraphics = useIsReducedGraphics()
82
83
  return (
83
84
  <ReactFlow<NodeType, EdgeType>
84
85
  colorMode={colorMode}
@@ -90,7 +91,12 @@ export const BaseXYFlow = <
90
91
  background === 'transparent' && css.cssTransparentBg,
91
92
  className,
92
93
  )}
93
- data-likec4-zoom-small={isZoomTooSmall}
94
+ {...isZoomTooSmall && {
95
+ ['data-likec4-zoom-small']: true,
96
+ }}
97
+ {...reduceGraphics && {
98
+ ['data-likec4-reduced-graphics']: true,
99
+ }}
94
100
  zoomOnPinch={zoomable}
95
101
  zoomOnScroll={!pannable && zoomable}
96
102
  {...(!zoomable && {
@@ -115,8 +121,7 @@ export const BaseXYFlow = <
115
121
  {...(!pannable && {
116
122
  selectionKeyCode: null,
117
123
  })}
118
- // TODO: benchmarks first
119
- //onlyRenderVisibleElements={nodes.length > 75}
124
+ onlyRenderVisibleElements={reduceGraphics}
120
125
  elementsSelectable={nodesSelectable}
121
126
  nodesFocusable={nodesDraggable || nodesSelectable}
122
127
  edgesFocusable={false}
@@ -202,7 +207,7 @@ const ViewportResizeHanlder = ({
202
207
  }: {
203
208
  onViewportResize: () => void
204
209
  }) => {
205
- const { width, height } = useStore(selectDimensions)
210
+ const { width, height } = useStore(selectDimensions, shallowEqual)
206
211
  useUpdateEffect(onViewportResize, [width, height])
207
212
 
208
213
  return <></>
package/src/base/const.ts CHANGED
@@ -5,5 +5,5 @@ export const ZIndexes = {
5
5
  Max: 8,
6
6
  } as const
7
7
 
8
- export const MinZoom = 0.1
8
+ export const MinZoom = 0.05
9
9
  export const MaxZoom = 2
@@ -1,4 +1,5 @@
1
1
  import { style } from '@vanilla-extract/css'
2
+ import { reactFlowReducedGraphics } from '../../../LikeC4Diagram.css'
2
3
  import { transitions, vars } from '../../../theme-vars'
3
4
 
4
5
  export const container = style({
@@ -45,11 +46,16 @@ export const actionIcon = style({
45
46
  },
46
47
  [`:where([data-hovered='true']) &`]: {
47
48
  opacity: 1,
49
+ transitionDelay: '100ms',
50
+ transitionDuration: '300ms',
48
51
  // boxShadow: '1px 1px 3px 0px rgba(0, 0, 0, 0.15)',
49
52
  // color: `var(--ai-color-hover)`,
50
53
  // vars: {
51
54
  // '--ai-bg': `var(--ai-bg-hover)`,
52
55
  // },
53
56
  },
57
+ [`${reactFlowReducedGraphics} &`]: {
58
+ boxShadow: 'none',
59
+ },
54
60
  },
55
61
  })
@@ -1,4 +1,5 @@
1
1
  import { createVar, globalStyle, style } from '@vanilla-extract/css'
2
+ import { reactFlowReducedGraphics } from '../../../LikeC4Diagram.css'
2
3
  import { vars } from '../../../theme-vars'
3
4
 
4
5
  export const container = style({
@@ -47,6 +48,9 @@ export const actionIcon = style({
47
48
  '--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`,
48
49
  },
49
50
  },
51
+ [`${reactFlowReducedGraphics} &`]: {
52
+ boxShadow: 'none',
53
+ },
50
54
  },
51
55
  })
52
56