@likec4/diagram 1.22.0 → 1.22.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.
@@ -3,6 +3,7 @@ import { ReactFlowInstance, useStoreApi, GeneralHelpers, ViewportHelperFunctions
3
3
  import { EdgeChange, NodeChange, Rect, Viewport, EdgeMarkerType, HandleType } from '@xyflow/system';
4
4
  import { ActorRefFromLogic, SnapshotFrom, StateMachine, CallbackActorLogic, AnyEventObject, NonReducibleUnknown, EventObject, MetaObject, AnyActorLogic, ConditionalRequired, InputFrom, RequiredLogicInput, IsNotNever, ActorRef, MachineSnapshot, AnyActorRef, StateValue, ActionFunction, ActorLogic, Values, RequiredActorOptions, GetConcreteByKey, StopAction, CancelAction } from 'xstate';
5
5
  import { EnabledFeatures } from '../../context';
6
+ import { FeatureName } from '../../context/DiagramFeatures';
6
7
  import { OpenSourceParams } from '../../LikeC4Diagram.props';
7
8
  import { Types } from '../types';
8
9
  import { AlignmentMode } from './aligners';
@@ -31,8 +32,10 @@ export interface Input {
31
32
  pannable: boolean;
32
33
  fitViewPadding: number;
33
34
  }
35
+ type ToggledFeatures = Partial<EnabledFeatures>;
34
36
  export interface DiagramContext extends Input {
35
37
  features: EnabledFeatures;
38
+ toggledFeatures: ToggledFeatures;
36
39
  initialized: boolean;
37
40
  viewport: Viewport;
38
41
  viewportChangedManually: boolean;
@@ -156,6 +159,10 @@ export type Events = HotKeyEvent | {
156
159
  direction: 'next' | 'previous';
157
160
  } | {
158
161
  type: 'walkthrough.end';
162
+ } | {
163
+ type: 'toggle.feature';
164
+ feature: FeatureName;
165
+ forceValue?: boolean;
159
166
  };
160
167
  export type ActionArg = {
161
168
  context: DiagramContext;
@@ -614,6 +621,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
614
621
  toPosition: XYPoint;
615
622
  };
616
623
  };
624
+ updateFeatures: {
625
+ type: "updateFeatures";
626
+ params: NonReducibleUnknown;
627
+ };
617
628
  }>, Values<{
618
629
  "enabled: FitView": {
619
630
  type: "enabled: FitView";
@@ -1444,6 +1455,9 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
1444
1455
  enableLikeC4Model: boolean;
1445
1456
  enableVscode: boolean;
1446
1457
  };
1458
+ toggledFeatures: {
1459
+ enableReadOnly: true;
1460
+ };
1447
1461
  initialized: false;
1448
1462
  viewportChangedManually: false;
1449
1463
  lastOnNavigate: null;
@@ -1831,6 +1845,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
1831
1845
  toPosition: XYPoint;
1832
1846
  };
1833
1847
  };
1848
+ updateFeatures: {
1849
+ type: "updateFeatures";
1850
+ params: NonReducibleUnknown;
1851
+ };
1834
1852
  }>, Values<{
1835
1853
  "enabled: FitView": {
1836
1854
  type: "enabled: FitView";
@@ -4531,6 +4549,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
4531
4549
  toPosition: XYPoint;
4532
4550
  };
4533
4551
  };
4552
+ updateFeatures: {
4553
+ type: "updateFeatures";
4554
+ params: NonReducibleUnknown;
4555
+ };
4534
4556
  }>, Values<{
4535
4557
  "enabled: FitView": {
4536
4558
  type: "enabled: FitView";
@@ -5775,6 +5797,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
5775
5797
  toPosition: XYPoint;
5776
5798
  };
5777
5799
  };
5800
+ updateFeatures: {
5801
+ type: "updateFeatures";
5802
+ params: NonReducibleUnknown;
5803
+ };
5778
5804
  }>, Values<{
5779
5805
  "enabled: FitView": {
5780
5806
  type: "enabled: FitView";
@@ -6080,6 +6106,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
6080
6106
  toPosition: XYPoint;
6081
6107
  };
6082
6108
  };
6109
+ updateFeatures: {
6110
+ type: "updateFeatures";
6111
+ params: NonReducibleUnknown;
6112
+ };
6083
6113
  }>, Values<{
6084
6114
  "enabled: FitView": {
6085
6115
  type: "enabled: FitView";
@@ -7246,6 +7276,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
7246
7276
  toPosition: XYPoint;
7247
7277
  };
7248
7278
  };
7279
+ updateFeatures: {
7280
+ type: "updateFeatures";
7281
+ params: NonReducibleUnknown;
7282
+ };
7249
7283
  }>, Values<{
7250
7284
  "enabled: FitView": {
7251
7285
  type: "enabled: FitView";
@@ -7559,6 +7593,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
7559
7593
  toPosition: XYPoint;
7560
7594
  };
7561
7595
  };
7596
+ updateFeatures: {
7597
+ type: "updateFeatures";
7598
+ params: NonReducibleUnknown;
7599
+ };
7562
7600
  }>, Values<{
7563
7601
  "enabled: FitView": {
7564
7602
  type: "enabled: FitView";
@@ -9385,6 +9423,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
9385
9423
  toPosition: XYPoint;
9386
9424
  };
9387
9425
  };
9426
+ updateFeatures: {
9427
+ type: "updateFeatures";
9428
+ params: NonReducibleUnknown;
9429
+ };
9388
9430
  }>, Values<{
9389
9431
  "enabled: FitView": {
9390
9432
  type: "enabled: FitView";
@@ -9640,10 +9682,10 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
9640
9682
  }>, never, never, never, never>;
9641
9683
  };
9642
9684
  readonly 'update.features': {
9643
- readonly actions: ActionFunction<DiagramContext, {
9644
- type: "update.features";
9645
- features: EnabledFeatures;
9646
- }, Events, undefined, Values<{
9685
+ readonly actions: "updateFeatures";
9686
+ };
9687
+ readonly 'update.nodeData': {
9688
+ readonly actions: ActionFunction<DiagramContext, Events, Events, undefined, Values<{
9647
9689
  hotkeyActor: {
9648
9690
  src: "hotkeyActor";
9649
9691
  logic: CallbackActorLogic<AnyEventObject, NonReducibleUnknown, HotKeyEvent>;
@@ -9854,7 +9896,7 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
9854
9896
  };
9855
9897
  }>, never, never, never, never>;
9856
9898
  };
9857
- readonly 'update.nodeData': {
9899
+ readonly 'update.edgeData': {
9858
9900
  readonly actions: ActionFunction<DiagramContext, Events, Events, undefined, Values<{
9859
9901
  hotkeyActor: {
9860
9902
  src: "hotkeyActor";
@@ -10066,8 +10108,27 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
10066
10108
  };
10067
10109
  }>, never, never, never, never>;
10068
10110
  };
10069
- readonly 'update.edgeData': {
10070
- readonly actions: ActionFunction<DiagramContext, Events, Events, undefined, Values<{
10111
+ readonly 'layout.align': {
10112
+ readonly guard: "not readonly";
10113
+ readonly actions: readonly [{
10114
+ readonly type: "layout.align";
10115
+ readonly params: ({ event }: {
10116
+ context: DiagramContext;
10117
+ event: {
10118
+ type: "layout.align";
10119
+ mode: AlignmentMode;
10120
+ };
10121
+ }) => {
10122
+ mode: AlignmentMode;
10123
+ };
10124
+ }, ActionFunction<DiagramContext, {
10125
+ type: "layout.align";
10126
+ mode: AlignmentMode;
10127
+ }, Events, undefined, never, never, never, never, never>];
10128
+ };
10129
+ readonly 'layout.resetEdgeControlPoints': {
10130
+ readonly guard: "not readonly";
10131
+ readonly actions: readonly [ ActionFunction<DiagramContext, Events, Events, undefined, Values<{
10071
10132
  hotkeyActor: {
10072
10133
  src: "hotkeyActor";
10073
10134
  logic: CallbackActorLogic<AnyEventObject, NonReducibleUnknown, HotKeyEvent>;
@@ -10276,29 +10337,26 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
10276
10337
  }, "active", any, any, any>, Events, Input, any, any>;
10277
10338
  id: "relationshipsBrowser";
10278
10339
  };
10279
- }>, never, never, never, never>;
10340
+ }>, never, never, never, never>, ActionFunction<DiagramContext, {
10341
+ type: "layout.resetEdgeControlPoints";
10342
+ }, Events, undefined, never, never, never, never, never>];
10280
10343
  };
10281
- readonly 'layout.align': {
10282
- readonly guard: "not readonly";
10283
- readonly actions: readonly [{
10284
- readonly type: "layout.align";
10285
- readonly params: ({ event }: {
10286
- context: DiagramContext;
10287
- event: {
10288
- type: "layout.align";
10289
- mode: AlignmentMode;
10290
- };
10291
- }) => {
10292
- mode: AlignmentMode;
10293
- };
10294
- }, ActionFunction<DiagramContext, {
10295
- type: "layout.align";
10296
- mode: AlignmentMode;
10344
+ readonly 'xyflow.resized': {
10345
+ readonly guard: ({ context }: {
10346
+ context: DiagramContext;
10347
+ }) => boolean;
10348
+ readonly actions: readonly [ CancelAction<DiagramContext, {
10349
+ type: "xyflow.resized";
10350
+ }, undefined, Events>, ActionFunction<DiagramContext, {
10351
+ type: "xyflow.resized";
10297
10352
  }, Events, undefined, never, never, never, never, never>];
10298
10353
  };
10299
- readonly 'layout.resetEdgeControlPoints': {
10300
- readonly guard: "not readonly";
10301
- readonly actions: readonly [ ActionFunction<DiagramContext, Events, Events, undefined, Values<{
10354
+ readonly 'open.elementDetails': {
10355
+ readonly actions: ActionFunction<DiagramContext, {
10356
+ type: "open.elementDetails";
10357
+ fqn: Fqn;
10358
+ fromNode?: NodeId | undefined;
10359
+ }, Events, undefined, Values<{
10302
10360
  hotkeyActor: {
10303
10361
  src: "hotkeyActor";
10304
10362
  logic: CallbackActorLogic<AnyEventObject, NonReducibleUnknown, HotKeyEvent>;
@@ -10507,25 +10565,37 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
10507
10565
  }, "active", any, any, any>, Events, Input, any, any>;
10508
10566
  id: "relationshipsBrowser";
10509
10567
  };
10510
- }>, never, never, never, never>, ActionFunction<DiagramContext, {
10511
- type: "layout.resetEdgeControlPoints";
10512
- }, Events, undefined, never, never, never, never, never>];
10568
+ }>, never, never, never, never>;
10569
+ readonly target: ".overlay.elementDetails";
10513
10570
  };
10514
- readonly 'xyflow.resized': {
10515
- readonly guard: ({ context }: {
10516
- context: DiagramContext;
10517
- }) => boolean;
10518
- readonly actions: readonly [ CancelAction<DiagramContext, {
10519
- type: "xyflow.resized";
10520
- }, undefined, Events>, ActionFunction<DiagramContext, {
10521
- type: "xyflow.resized";
10522
- }, Events, undefined, never, never, never, never, never>];
10571
+ readonly 'open.relationshipDetails': {
10572
+ readonly target: ".overlay.relationshipDetails";
10523
10573
  };
10524
- readonly 'open.elementDetails': {
10574
+ readonly 'open.relationshipsBrowser': {
10575
+ readonly target: ".overlay.relationshipsBrowser";
10576
+ };
10577
+ readonly 'open.source': {
10578
+ readonly actions: {
10579
+ readonly type: "trigger:OpenSource";
10580
+ readonly params: (data: {
10581
+ context: DiagramContext;
10582
+ event: {
10583
+ type: "open.source";
10584
+ } & OpenSourceParams;
10585
+ }) => {
10586
+ type: "open.source";
10587
+ } & OpenSourceParams;
10588
+ };
10589
+ };
10590
+ readonly 'walkthrough.start': {
10591
+ readonly guard: "is dynamic view";
10592
+ readonly target: ".walkthrough";
10593
+ };
10594
+ readonly 'toggle.feature': {
10525
10595
  readonly actions: ActionFunction<DiagramContext, {
10526
- type: "open.elementDetails";
10527
- fqn: Fqn;
10528
- fromNode?: NodeId | undefined;
10596
+ type: "toggle.feature";
10597
+ feature: FeatureName;
10598
+ forceValue?: boolean;
10529
10599
  }, Events, undefined, Values<{
10530
10600
  hotkeyActor: {
10531
10601
  src: "hotkeyActor";
@@ -10736,30 +10806,6 @@ export declare const diagramMachine: StateMachine<DiagramContext, Events, {
10736
10806
  id: "relationshipsBrowser";
10737
10807
  };
10738
10808
  }>, never, never, never, never>;
10739
- readonly target: ".overlay.elementDetails";
10740
- };
10741
- readonly 'open.relationshipDetails': {
10742
- readonly target: ".overlay.relationshipDetails";
10743
- };
10744
- readonly 'open.relationshipsBrowser': {
10745
- readonly target: ".overlay.relationshipsBrowser";
10746
- };
10747
- readonly 'open.source': {
10748
- readonly actions: {
10749
- readonly type: "trigger:OpenSource";
10750
- readonly params: (data: {
10751
- context: DiagramContext;
10752
- event: {
10753
- type: "open.source";
10754
- } & OpenSourceParams;
10755
- }) => {
10756
- type: "open.source";
10757
- } & OpenSourceParams;
10758
- };
10759
- };
10760
- readonly 'walkthrough.start': {
10761
- readonly guard: "is dynamic view";
10762
- readonly target: ".walkthrough";
10763
10809
  };
10764
10810
  };
10765
10811
  readonly exit: readonly [ StopAction<DiagramContext, Events, undefined, Events>, StopAction<DiagramContext, Events, undefined, Events>, ActionFunction<DiagramContext, Events, Events, undefined, Values<{
@@ -2,7 +2,7 @@ import { nonNullable, invariant, nonexhaustive, getBBoxCenter, getParallelStepsP
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, assertEvent, stopChild, assign, cancel, raise, enqueueActions, spawnChild, sendTo, and, or } from "xstate";
5
+ import { setup, assign, assertEvent, stopChild, cancel, raise, enqueueActions, spawnChild, sendTo, and, or } from "xstate";
6
6
  import { MinZoom } from "../../base/const.js";
7
7
  import { AllDisabled } from "../../context/DiagramFeatures.js";
8
8
  import { relationshipDetailsActor } from "../../overlays/relationship-details/actor.js";
@@ -143,13 +143,24 @@ const diagramMachine = setup({
143
143
  };
144
144
  });
145
145
  });
146
- }
146
+ },
147
+ "updateFeatures": assign(({ event }) => {
148
+ assertEvent(event, "update.features");
149
+ return {
150
+ features: {
151
+ ...event.features
152
+ }
153
+ };
154
+ })
147
155
  }
148
156
  }).createMachine({
149
157
  initial: "initializing",
150
158
  context: ({ input, self, spawn }) => ({
151
159
  ...input,
152
160
  features: { ...AllDisabled },
161
+ toggledFeatures: {
162
+ enableReadOnly: true
163
+ },
153
164
  initialized: false,
154
165
  viewportChangedManually: false,
155
166
  lastOnNavigate: null,
@@ -699,9 +710,7 @@ const diagramMachine = setup({
699
710
  actions: assign(({ event }) => ({ ...event.inputs }))
700
711
  },
701
712
  "update.features": {
702
- actions: assign({
703
- features: ({ event }) => event.features
704
- })
713
+ actions: "updateFeatures"
705
714
  },
706
715
  "update.nodeData": {
707
716
  actions: assign(updateNodeData)
@@ -758,6 +767,14 @@ const diagramMachine = setup({
758
767
  "walkthrough.start": {
759
768
  guard: "is dynamic view",
760
769
  target: ".walkthrough"
770
+ },
771
+ "toggle.feature": {
772
+ actions: assign({
773
+ toggledFeatures: ({ context, event }) => ({
774
+ ...context.toggledFeatures,
775
+ [`enable${event.feature}`]: event.forceValue ?? !(context.toggledFeatures[`enable${event.feature}`] ?? context.features[`enable${event.feature}`])
776
+ })
777
+ })
761
778
  }
762
779
  },
763
780
  exit: [
@@ -98,12 +98,7 @@ function createViewChange(parentContext) {
98
98
  const { view, xystore, xyflow } = parentContext;
99
99
  const { nodeLookup } = xystore.getState();
100
100
  const movedNodes = /* @__PURE__ */ new Set();
101
- let bounds = {
102
- x: 0,
103
- y: 0,
104
- width: 1,
105
- height: 1
106
- };
101
+ let bounds;
107
102
  const nodes = reduce([...nodeLookup.values()], (acc, node) => {
108
103
  const dimensions = getNodeDimensions(node);
109
104
  if (!isSamePoint(node.internals.positionAbsolute, node.data.position) || node.initialWidth !== dimensions.width || node.initialHeight !== dimensions.height) {
@@ -116,7 +111,7 @@ function createViewChange(parentContext) {
116
111
  width: Math.ceil(dimensions.width),
117
112
  height: Math.ceil(dimensions.height)
118
113
  };
119
- bounds = getBoundsOfRects(bounds, rect);
114
+ bounds = bounds ? getBoundsOfRects(bounds, rect) : rect;
120
115
  return acc;
121
116
  }, {});
122
117
  const edges = reduce((xyflow == null ? void 0 : xyflow.getEdges()) ?? [], (acc, { source, target, data }) => {
@@ -159,9 +154,10 @@ function createViewChange(parentContext) {
159
154
  x2: Math.ceil(Math.max(...allX)),
160
155
  y2: Math.ceil(Math.max(...allY))
161
156
  });
162
- bounds = getBoundsOfRects(bounds, rect);
157
+ bounds = bounds ? getBoundsOfRects(bounds, rect) : rect;
163
158
  return acc;
164
159
  }, {});
160
+ bounds ?? (bounds = view.bounds);
165
161
  return {
166
162
  op: "save-manual-layout",
167
163
  layout: {
@@ -14,6 +14,7 @@ import { LayoutDriftNotification } from "./LayoutDriftNotification.js";
14
14
  import { ManualLayoutToolsButton } from "./ManualLayoutToolsButton.js";
15
15
  import { NavigationButtons } from "./NavigationButtons.js";
16
16
  import { navigationButtons, actionIconGroup, panel } from "./styles.css.js";
17
+ import { ToggleReadonly } from "./ToggleReadonly.js";
17
18
  import { useDiagramEventHandlers } from "../../../context/DiagramEventHandlers.js";
18
19
  import { useEnabledFeatures } from "../../../context/DiagramFeatures.js";
19
20
  import { useMantinePortalProps } from "../../../hooks/useMantinePortalProps.js";
@@ -82,6 +83,7 @@ const Controls = memo(() => {
82
83
  children: /* @__PURE__ */ jsx(IconFileSymlink, { stroke: 1.5 })
83
84
  }
84
85
  ) }),
86
+ enableControls && /* @__PURE__ */ jsx(ToggleReadonly, {}),
85
87
  enableControls && hasLayoutDrift && /* @__PURE__ */ jsx(LayoutDriftNotification, { ...portalProps }),
86
88
  enableControls && notReadOnly && /* @__PURE__ */ jsxs(Fragment, { children: [
87
89
  /* @__PURE__ */ jsx(ChangeAutoLayoutButton, { ...portalProps }),
@@ -0,0 +1 @@
1
+ export declare const ToggleReadonly: () => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,29 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { IconLock, IconLockOpen2 } from "@tabler/icons-react";
3
+ import { useDiagram } from "../../../hooks/useDiagram.js";
4
+ import { useDiagramContext } from "../../../hooks/useDiagramContext.js";
5
+ import { Tooltip, ActionIcon } from "./_shared.js";
6
+ const selector = (state) => ({
7
+ visible: state.features.enableReadOnly !== true,
8
+ isReadOnly: state.toggledFeatures.enableReadOnly ?? state.features.enableReadOnly
9
+ });
10
+ const ToggleReadonly = () => {
11
+ const { visible, isReadOnly } = useDiagramContext(selector);
12
+ const diagram = useDiagram();
13
+ if (!visible) {
14
+ return null;
15
+ }
16
+ return /* @__PURE__ */ jsx(Tooltip, { label: isReadOnly ? "Enable editing" : "Disable editing", children: /* @__PURE__ */ jsx(
17
+ ActionIcon,
18
+ {
19
+ onClick: (e) => {
20
+ e.stopPropagation();
21
+ diagram.toggleFeature("ReadOnly");
22
+ },
23
+ children: isReadOnly ? /* @__PURE__ */ jsx(IconLock, {}) : /* @__PURE__ */ jsx(IconLockOpen2, {})
24
+ }
25
+ ) });
26
+ };
27
+ export {
28
+ ToggleReadonly
29
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@likec4/diagram",
3
- "version": "1.22.0",
3
+ "version": "1.22.1",
4
4
  "license": "MIT",
5
5
  "bugs": "https://github.com/likec4/likec4/issues",
6
6
  "homepage": "https://likec4.dev",
@@ -39,14 +39,13 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@dagrejs/dagre": "1.1.4",
42
- "@likec4/core": "1.22.0",
42
+ "@likec4/core": "1.22.1",
43
43
  "@mantine/vanilla-extract": "7.16.3",
44
44
  "@nanostores/react": "0.8.4",
45
45
  "@react-hookz/web": "^25.0.1",
46
46
  "@tabler/icons-react": "3.29.0",
47
47
  "@vanilla-extract/dynamic": "^2.1.2",
48
48
  "@xstate/react": "^5.0.2",
49
- "@xstate/store": "^2.6.2",
50
49
  "@xyflow/react": "12.4.2",
51
50
  "@xyflow/system": "0.0.50",
52
51
  "bezier-js": "^6.1.4",
@@ -69,7 +68,7 @@
69
68
  "react-dom": "^18.0.0 || ^19.0.0"
70
69
  },
71
70
  "devDependencies": {
72
- "@likec4/tsconfig": "1.22.0",
71
+ "@likec4/tsconfig": "1.22.1",
73
72
  "@mantine/core": "7.16.3",
74
73
  "@mantine/hooks": "7.16.3",
75
74
  "@statelyai/inspect": "^0.4.0",
@@ -28,7 +28,7 @@ const FeatureNames = [
28
28
  */
29
29
  'Vscode',
30
30
  ] as const
31
- type FeatureName = typeof FeatureNames[number]
31
+ export type FeatureName = typeof FeatureNames[number]
32
32
  export type EnabledFeatures = {
33
33
  [P in `enable${FeatureName}`]: boolean
34
34
  }
@@ -1,6 +1,7 @@
1
1
  import type { EdgeId, Fqn, NodeId, ViewId } from '@likec4/core'
2
2
  import { useMemo, useTransition } from 'react'
3
3
  import type { PartialDeep } from 'type-fest'
4
+ import type { FeatureName } from '../context/DiagramFeatures'
4
5
  import { useDiagramActor } from '../hooks/useDiagramActor'
5
6
  import type { OpenSourceParams } from '../LikeC4Diagram.props'
6
7
  import type { AlignmentMode } from '../likec4diagram/state/aligners'
@@ -125,5 +126,9 @@ export function useDiagram() {
125
126
  stopWalkthrough: () => {
126
127
  actor.send({ type: 'walkthrough.end' })
127
128
  },
129
+
130
+ toggleFeature: (feature: FeatureName, forceValue?: boolean) => {
131
+ actor.send({ type: 'toggle.feature', feature, ...(forceValue !== undefined && { forceValue }) })
132
+ },
128
133
  }), [actor])
129
134
  }
@@ -2,11 +2,12 @@ import type { ViewId } from '@likec4/core'
2
2
  import { useCallbackRef } from '@mantine/hooks'
3
3
  import { useStoreApi } from '@xyflow/react'
4
4
  import { type PropsWithChildren, useEffect } from 'react'
5
- import { useDiagramEventHandlers, useEnabledFeatures } from '../context'
5
+ import { DiagramFeatures, useDiagramEventHandlers, useEnabledFeatures } from '../context'
6
6
  import { useDiagramActor } from '../hooks/useDiagramActor'
7
7
  import { useUpdateEffect } from '../hooks/useUpdateEffect'
8
8
  import { LikeC4ViewMachineContextProvider } from './state/actorContext'
9
9
  // import { useInspector } from './state/inspector'
10
+ import { useDiagramContext } from '../hooks/useDiagramContext'
10
11
  import { type Input, diagramMachine } from './state/machine'
11
12
  import { syncManualLayoutActor } from './state/syncManualLayoutActor'
12
13
  import type { Types } from './types'
@@ -54,7 +55,9 @@ export function DiagramActor({ input, children }: PropsWithChildren<{ input: Act
54
55
  }}
55
56
  >
56
57
  <SyncStore input={input} />
57
- {children}
58
+ <DiagramActorToggledFeatures>
59
+ {children}
60
+ </DiagramActorToggledFeatures>
58
61
  </LikeC4ViewMachineContextProvider>
59
62
  )
60
63
  )
@@ -77,3 +80,13 @@ const SyncStore = ({ input: { view, xyedges, xynodes, ...inputs } }: { input: Ac
77
80
 
78
81
  return null
79
82
  }
83
+
84
+ function DiagramActorToggledFeatures({ children }: PropsWithChildren) {
85
+ const toggledFeatures = useDiagramContext(s => s.toggledFeatures)
86
+ return (
87
+ <DiagramFeatures
88
+ overrides={toggledFeatures}>
89
+ {children}
90
+ </DiagramFeatures>
91
+ )
92
+ }
@@ -24,6 +24,13 @@ const selectXYProps = (ctx: DiagramContext) => ({
24
24
  fitViewPadding: ctx.fitViewPadding,
25
25
  enableFitView: ctx.features.enableFitView,
26
26
  enableReadOnly: ctx.features.enableReadOnly,
27
+ ...(!ctx.features.enableFitView && {
28
+ viewport: {
29
+ x: -Math.min(ctx.view.bounds.x, 0),
30
+ y: -Math.min(ctx.view.bounds.y, 0),
31
+ zoom: 1,
32
+ },
33
+ }),
27
34
  })
28
35
  const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof selectXYProps>): boolean =>
29
36
  a.initialized === b.initialized &&
@@ -33,7 +40,8 @@ const equalsXYProps = (a: ReturnType<typeof selectXYProps>, b: ReturnType<typeof
33
40
  a.enableFitView === b.enableFitView &&
34
41
  a.enableReadOnly === b.enableReadOnly &&
35
42
  shallowEqual(a.nodes, b.nodes) &&
36
- shallowEqual(a.edges, b.edges)
43
+ shallowEqual(a.edges, b.edges) &&
44
+ shallowEqual(a.viewport, b.viewport)
37
45
 
38
46
  type Picked = EnforceOptional<
39
47
  Pick<
@@ -41,7 +41,7 @@ import {
41
41
  } from 'xstate'
42
42
  import { MinZoom } from '../../base/const'
43
43
  import type { EnabledFeatures } from '../../context'
44
- import { AllDisabled } from '../../context/DiagramFeatures'
44
+ import { type FeatureName, AllDisabled } from '../../context/DiagramFeatures'
45
45
  import type { OpenSourceParams } from '../../LikeC4Diagram.props'
46
46
  import { relationshipDetailsActor } from '../../overlays/relationship-details/actor'
47
47
  import { relationshipsBrowserActor } from '../../overlays/relationships-browser/actor'
@@ -86,8 +86,12 @@ export interface Input {
86
86
  fitViewPadding: number
87
87
  }
88
88
 
89
+ type ToggledFeatures = Partial<EnabledFeatures>
90
+
89
91
  export interface DiagramContext extends Input {
90
92
  features: EnabledFeatures
93
+ // This is used to override features from props
94
+ toggledFeatures: ToggledFeatures
91
95
  initialized: boolean
92
96
  viewport: Viewport
93
97
  viewportChangedManually: boolean
@@ -165,6 +169,7 @@ export type Events =
165
169
  | { type: 'walkthrough.start'; stepId?: StepEdgeId }
166
170
  | { type: 'walkthrough.step'; direction: 'next' | 'previous' }
167
171
  | { type: 'walkthrough.end' }
172
+ | { type: 'toggle.feature'; feature: FeatureName; forceValue?: boolean }
168
173
 
169
174
  export type ActionArg = { context: DiagramContext; event: Events }
170
175
 
@@ -306,12 +311,24 @@ export const diagramMachine = setup({
306
311
  })
307
312
  })
308
313
  },
314
+
315
+ 'updateFeatures': assign(({ event }) => {
316
+ assertEvent(event, 'update.features')
317
+ return {
318
+ features: {
319
+ ...event.features,
320
+ },
321
+ }
322
+ }),
309
323
  },
310
324
  }).createMachine({
311
325
  initial: 'initializing',
312
326
  context: ({ input, self, spawn }) => ({
313
327
  ...input,
314
328
  features: { ...AllDisabled },
329
+ toggledFeatures: {
330
+ enableReadOnly: true,
331
+ },
315
332
  initialized: false,
316
333
  viewportChangedManually: false,
317
334
  lastOnNavigate: null,
@@ -863,9 +880,7 @@ export const diagramMachine = setup({
863
880
  actions: assign(({ event }) => ({ ...event.inputs })),
864
881
  },
865
882
  'update.features': {
866
- actions: assign({
867
- features: ({ event }) => event.features,
868
- }),
883
+ actions: 'updateFeatures',
869
884
  },
870
885
  'update.nodeData': {
871
886
  actions: assign(updateNodeData),
@@ -921,6 +936,15 @@ export const diagramMachine = setup({
921
936
  guard: 'is dynamic view',
922
937
  target: '.walkthrough',
923
938
  },
939
+ 'toggle.feature': {
940
+ actions: assign({
941
+ toggledFeatures: ({ context, event }) => ({
942
+ ...context.toggledFeatures,
943
+ [`enable${event.feature}`]: event.forceValue ??
944
+ !(context.toggledFeatures[`enable${event.feature}`] ?? context.features[`enable${event.feature}`]),
945
+ }),
946
+ }),
947
+ },
924
948
  },
925
949
  exit: [
926
950
  stopChild('layout'),