likec4 0.44.2 → 0.46.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 (44) hide show
  1. package/dist/@likec4/core/utils/relations.js +11 -2
  2. package/dist/@likec4/diagrams/components/primitives/fullscreen/FullscreenDiagram.js +8 -7
  3. package/dist/@likec4/diagrams/diagram/Diagram.js +63 -45
  4. package/dist/@likec4/diagrams/diagram/Edges.js +32 -16
  5. package/dist/@likec4/diagrams/diagram/Nodes.js +66 -70
  6. package/dist/@likec4/diagrams/diagram/icons/ZoomIn.js +2 -3
  7. package/dist/@likec4/diagrams/diagram/shapes/Browser.js +43 -9
  8. package/dist/@likec4/diagrams/diagram/shapes/Compound.js +8 -10
  9. package/dist/@likec4/diagrams/diagram/shapes/Cylinder.js +3 -1
  10. package/dist/@likec4/diagrams/diagram/shapes/Edge.js +10 -10
  11. package/dist/@likec4/diagrams/diagram/shapes/Mobile.js +19 -4
  12. package/dist/@likec4/diagrams/diagram/shapes/NodeIcon.js +47 -9
  13. package/dist/@likec4/diagrams/diagram/shapes/NodeLabel.js +31 -54
  14. package/dist/@likec4/diagrams/diagram/shapes/Person.js +3 -1
  15. package/dist/@likec4/diagrams/diagram/shapes/Queue.js +3 -1
  16. package/dist/@likec4/diagrams/diagram/shapes/Rectangle.js +3 -1
  17. package/dist/@likec4/diagrams/diagram/shapes/index.js +1 -1
  18. package/dist/@likec4/diagrams/diagram/shapes/utils.js +1 -1
  19. package/dist/@likec4/diagrams/diagram/state/atoms.js +6 -0
  20. package/dist/@likec4/diagrams/diagram/state/hooks.js +10 -1
  21. package/dist/@likec4/diagrams/hooks/useDiagramApi.js +19 -22
  22. package/dist/@likec4/diagrams/hooks/useImageLoader.js +7 -1
  23. package/dist/__app__/index.html +1 -1
  24. package/dist/__app__/likec4.css +23 -0
  25. package/dist/__app__/src/App.jsx +27 -5
  26. package/dist/__app__/src/components/DiagramNotFound.jsx +10 -4
  27. package/dist/__app__/src/components/sidebar/Sidebar.jsx +1 -1
  28. package/dist/__app__/src/components/view-page/DisplayModeSelector.jsx +1 -1
  29. package/dist/__app__/src/components/view-page/ExportDiagram.jsx +13 -7
  30. package/dist/__app__/src/components/view-page/ViewActionsToolbar.jsx +12 -3
  31. package/dist/__app__/src/data/atoms.js +0 -11
  32. package/dist/__app__/src/pages/embed.page.jsx +14 -0
  33. package/dist/__app__/src/pages/export.page.jsx +4 -13
  34. package/dist/__app__/src/pages/index.js +1 -0
  35. package/dist/__app__/src/pages/useTransparentBackground.js +16 -0
  36. package/dist/__app__/src/pages/view-page.module.css +30 -0
  37. package/dist/__app__/src/pages/view.page.jsx +44 -8
  38. package/dist/__app__/src/router.js +14 -12
  39. package/dist/__app__/tailwind.config.cjs +1 -3
  40. package/dist/__app__/tsconfig.json +1 -7
  41. package/dist/cli/index.js +199 -206
  42. package/package.json +9 -10
  43. package/dist/@likec4/diagrams/hooks/useDarkMode.js +0 -5
  44. package/dist/@likec4/diagrams/index.mjs +0 -1927
@@ -1,7 +1,16 @@
1
- import { compareFqnHierarchically, isAncestor } from "./fqn.js";
1
+ import { commonAncestor, compareFqnHierarchically, isAncestor } from "./fqn.js";
2
2
  import { either } from "rambdax";
3
3
  export const compareRelations = (a, b) => {
4
- return compareFqnHierarchically(a.source, b.source) || compareFqnHierarchically(a.target, b.target);
4
+ const parentA = commonAncestor(a.source, a.target);
5
+ const parentB = commonAncestor(b.source, b.target);
6
+ if (parentA && !parentB) {
7
+ return 1;
8
+ }
9
+ if (!parentA && parentB) {
10
+ return -1;
11
+ }
12
+ const compareParents = parentA && parentB ? compareFqnHierarchically(parentA, parentB) : 0;
13
+ return compareParents || compareFqnHierarchically(a.source, b.source) || compareFqnHierarchically(a.target, b.target);
5
14
  };
6
15
  const isInside = (parent) => {
7
16
  const prefix = parent + ".";
@@ -12,23 +12,24 @@ const StyleOverlay = {
12
12
  left: 0,
13
13
  right: 0,
14
14
  bottom: 0,
15
- backgroundColor: "var(--likec4-browser-overlay-bg, rgba(18,18,18,0.8))",
15
+ backgroundColor: "var(--likec4-browser-overlay-bg, rgba(18,18,18,0.9))",
16
16
  backdropFilter: "var(--likec4-browser-backdrop, blur(4px))",
17
17
  zIndex: "var(--likec4-overlay-z-index, 100)",
18
- display: "flex",
19
- placeContent: "strech",
20
- placeItems: "strech",
21
- touchAction: "pan-x pan-y pinch-zoom",
22
18
  boxSizing: "border-box",
23
19
  margin: 0,
24
20
  padding: 0,
25
21
  border: "0 solid transparent"
26
22
  };
27
23
  const StyleContainer = {
24
+ position: "absolute",
25
+ top: 0,
26
+ left: 0,
27
+ right: 0,
28
+ bottom: 0,
28
29
  margin: 0,
29
30
  padding: 0,
30
- flex: "1 1 100%",
31
- overflow: "hidden"
31
+ overflow: "hidden",
32
+ touchAction: "pan-x pan-y pinch-zoom"
32
33
  };
33
34
  export function FullscreenDiagram({
34
35
  diagram,
@@ -1,14 +1,14 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { nonNullable, defaultTheme as theme } from "@likec4/core";
3
3
  import { useHookableRef, useUpdateEffect } from "@react-hookz/web/esm";
4
- import { easings, useSpring } from "@react-spring/konva";
4
+ import { useSpring } from "@react-spring/konva";
5
5
  import { clamp } from "rambdax";
6
6
  import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
7
7
  import { AnimatedStage, Layer } from "../konva.js";
8
8
  import { Edges } from "./Edges.js";
9
9
  import { createUseGesture, dragAction, pinchAction } from "@use-gesture/react";
10
10
  import { Nodes } from "./Nodes.js";
11
- import { DiagramGesture } from "./state/index.js";
11
+ import { DiagramGesture, useResetHoveredStates } from "./state/index.js";
12
12
  const useGesture = createUseGesture([dragAction, pinchAction]);
13
13
  const useSyncedRef = (value) => {
14
14
  const ref = useRef(value);
@@ -30,7 +30,7 @@ function diagramNodeId(konvaNode) {
30
30
  export const Diagram = /* @__PURE__ */ forwardRef(
31
31
  ({
32
32
  diagram,
33
- padding: _padding = NoPadding,
33
+ padding = NoPadding,
34
34
  pannable = true,
35
35
  zoomable = true,
36
36
  animate = true,
@@ -42,6 +42,8 @@ export const Diagram = /* @__PURE__ */ forwardRef(
42
42
  onStageContextMenu,
43
43
  width: _width,
44
44
  height: _height,
45
+ minZoom = 0.2,
46
+ maxZoom = 1.1,
45
47
  ...props
46
48
  }, ref) => {
47
49
  const immediate = !animate;
@@ -54,15 +56,15 @@ export const Diagram = /* @__PURE__ */ forwardRef(
54
56
  }
55
57
  return value;
56
58
  });
57
- const width = _width ?? diagram.width;
58
- const height = _height ?? diagram.height;
59
+ const [paddingTop, paddingRight, paddingBottom, paddingLeft] = Array.isArray(padding) ? padding : [padding, padding, padding, padding];
60
+ const width = _width ?? diagram.width + paddingLeft + paddingRight;
61
+ const height = _height ?? diagram.height + paddingTop + paddingBottom;
59
62
  const toCenterOnRect = (centerTo) => {
60
- const [paddingTop, paddingRight, paddingBottom, paddingLeft] = Array.isArray(_padding) ? _padding : [_padding, _padding, _padding, _padding];
61
- const container = stageRef.current?.container();
63
+ const container = containerRef.current;
62
64
  const viewRect = {
63
65
  width: Math.min(container?.clientWidth ?? width, width) - paddingLeft - paddingRight,
64
66
  height: Math.min(container?.clientHeight ?? height, height) - paddingTop - paddingBottom
65
- }, viewScale = Math.min(viewRect.width / centerTo.width, viewRect.height / centerTo.height), scale = clamp(0.1, 1.1, viewScale), centeringAjustment = {
67
+ }, viewScale = Math.min(viewRect.width / centerTo.width, viewRect.height / centerTo.height), scale = clamp(minZoom, maxZoom, viewScale), centeringAjustment = {
66
68
  x: ((width - centerTo.width) * scale + viewRect.width) / 2,
67
69
  y: ((height - centerTo.height) * scale + viewRect.height) / 2
68
70
  }, finalPosition = {
@@ -78,11 +80,13 @@ export const Diagram = /* @__PURE__ */ forwardRef(
78
80
  const [stageProps, stageSpringApi] = useSpring(
79
81
  () => initialPosition ? {
80
82
  from: initialPosition,
81
- to: toFitDiagram()
83
+ to: toFitDiagram(),
84
+ immediate
82
85
  } : {
83
- from: toFitDiagram(),
86
+ to: toFitDiagram(),
84
87
  immediate
85
- }
88
+ },
89
+ []
86
90
  );
87
91
  const centerOnRect = (centerTo) => {
88
92
  stageSpringApi.start({
@@ -91,14 +95,10 @@ export const Diagram = /* @__PURE__ */ forwardRef(
91
95
  });
92
96
  return;
93
97
  };
94
- const centerAndFit = (delay = 70, durationMs) => {
98
+ const centerAndFit = (delay = 0) => {
95
99
  stageSpringApi.start({
96
100
  to: toFitDiagram(),
97
101
  delay,
98
- config: durationMs ? {
99
- duration: durationMs,
100
- easing: easings.easeInOutCubic
101
- } : {},
102
102
  immediate
103
103
  });
104
104
  return;
@@ -126,13 +126,13 @@ export const Diagram = /* @__PURE__ */ forwardRef(
126
126
  ref,
127
127
  () => ({
128
128
  get stage() {
129
- return nonNullable(stageRef.current, "not mounted");
129
+ return stageRef.current;
130
130
  },
131
131
  get diagramView() {
132
132
  return refs.current.diagram;
133
133
  },
134
134
  get container() {
135
- return nonNullable(stageRef.current?.container(), "not mounted");
135
+ return stageRef.current?.container() ?? null;
136
136
  },
137
137
  resetStageZoom: (_immediate) => {
138
138
  refs.current.resetStageZoom(_immediate);
@@ -145,11 +145,18 @@ export const Diagram = /* @__PURE__ */ forwardRef(
145
145
  }),
146
146
  centerAndFit: () => refs.current.centerAndFit()
147
147
  }),
148
- [refs, id, stageRef]
148
+ [refs, stageRef]
149
149
  );
150
+ const resetHoveredStates = useResetHoveredStates();
150
151
  useUpdateEffect(() => {
151
- refs.current.centerAndFit(80, 650);
152
- }, [id, height, width]);
152
+ resetHoveredStates();
153
+ }, [id]);
154
+ useUpdateEffect(() => {
155
+ refs.current.centerAndFit(200);
156
+ }, [id]);
157
+ useUpdateEffect(() => {
158
+ refs.current.centerAndFit(50);
159
+ }, [height, width]);
153
160
  useEffect(() => {
154
161
  if (!zoomable) {
155
162
  return;
@@ -164,39 +171,41 @@ export const Diagram = /* @__PURE__ */ forwardRef(
164
171
  }, [zoomable]);
165
172
  useGesture(
166
173
  {
167
- onDragEnd: () => {
168
- DiagramGesture.isDragging = false;
169
- },
170
174
  onDrag: (state) => {
171
175
  const {
172
- pinching,
176
+ first,
177
+ last,
173
178
  down,
174
- cancel,
175
179
  intentional,
176
180
  offset: [x, y]
177
181
  } = state;
178
- if (pinching) {
179
- return cancel();
182
+ if (!intentional) {
183
+ return;
180
184
  }
181
- if (intentional) {
182
- DiagramGesture.isDragging = true;
183
- stageSpringApi.start({
184
- to: {
185
- x,
186
- y
187
- },
188
- immediate: immediate || down
189
- });
185
+ if (first || last) {
186
+ DiagramGesture.isDragging = first && !last;
190
187
  }
188
+ stageSpringApi.start({
189
+ to: {
190
+ x,
191
+ y
192
+ },
193
+ delay: 0,
194
+ immediate: immediate || down && !last
195
+ });
191
196
  },
192
197
  onPinch: ({ memo, first, last, origin: [ox, oy], movement: [ms], offset: [scale] }) => {
193
198
  if (first) {
199
+ DiagramGesture.isDragging = true;
194
200
  const stage = nonNullable(stageRef.current);
195
201
  const { x: x2, y: y2 } = stage.getAbsolutePosition();
196
202
  const tx = Math.round(ox - x2);
197
203
  const ty = Math.round(oy - y2);
198
204
  memo = [stage.x(), stage.y(), tx, ty];
199
205
  }
206
+ if (last) {
207
+ DiagramGesture.isDragging = false;
208
+ }
200
209
  const x = Math.round(memo[0] - (ms - 1) * memo[2]);
201
210
  const y = Math.round(memo[1] - (ms - 1) * memo[3]);
202
211
  stageSpringApi.start({
@@ -205,7 +214,7 @@ export const Diagram = /* @__PURE__ */ forwardRef(
205
214
  y,
206
215
  scale
207
216
  },
208
- immediate: immediate || !last || !first
217
+ delay: 0
209
218
  });
210
219
  return memo;
211
220
  }
@@ -213,21 +222,30 @@ export const Diagram = /* @__PURE__ */ forwardRef(
213
222
  {
214
223
  target: containerRef,
215
224
  drag: {
225
+ target: containerRef,
216
226
  enabled: pannable,
217
227
  threshold: 4,
218
- from: () => [stageProps.x.get(), stageProps.y.get()],
219
- pointer: {
220
- buttons: -1,
221
- keys: false
222
- }
228
+ from: () => [stageRef.current?.x() ?? 0, stageRef.current?.y() ?? 0],
229
+ pointer: Object.assign(
230
+ {
231
+ keys: false,
232
+ mouse: true,
233
+ capture: true
234
+ },
235
+ !onNodeContextMenu && !onStageContextMenu && {
236
+ buttons: -1
237
+ }
238
+ )
223
239
  },
224
240
  pinch: {
241
+ target: containerRef,
225
242
  pointer: {
226
243
  touch: true
227
244
  },
245
+ from: () => [stageRef.current?.scaleX() ?? 1, 0],
228
246
  enabled: zoomable,
229
- scaleBounds: { min: 0.1, max: 1.4 },
230
- rubberband: 0.05,
247
+ scaleBounds: { min: minZoom, max: maxZoom + 0.4 },
248
+ rubberband: 0.045,
231
249
  pinchOnWheel: true
232
250
  }
233
251
  }
@@ -1,11 +1,12 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { DefaultRelationshipColor } from "@likec4/core";
3
+ import { isEqualSimple } from "@react-hookz/deep-equal/esnext";
3
4
  import { useTransition } from "@react-spring/konva";
4
5
  import { scale, toHex } from "khroma";
5
6
  import { memoize } from "rambdax";
6
- import { useCallback } from "react";
7
- import { Group } from "../konva.js";
8
- import { EdgeShape } from "./shapes/Edge.js";
7
+ import { memo, useCallback } from "react";
8
+ import { AnimatedGroup } from "../konva.js";
9
+ import { Edge } from "./shapes/Edge.js";
9
10
  import { mouseDefault, mousePointer } from "./shapes/utils.js";
10
11
  import { DiagramGesture, useHoveredEdgeId, useSetHoveredEdge } from "./state/index.js";
11
12
  const edgeColors = memoize((colors, isHovered) => {
@@ -13,13 +14,13 @@ const edgeColors = memoize((colors, isHovered) => {
13
14
  return {
14
15
  lineColor: toHex(
15
16
  scale(colors.lineColor, {
16
- l: 25,
17
- s: -5
17
+ l: 30
18
18
  })
19
19
  ),
20
20
  labelColor: toHex(
21
21
  scale(colors.labelColor, {
22
- l: 40
22
+ l: 40,
23
+ s: 5
23
24
  })
24
25
  ),
25
26
  labelBgColor: toHex(
@@ -34,7 +35,6 @@ const edgeColors = memoize((colors, isHovered) => {
34
35
  });
35
36
  export function Edges({ animate, theme, diagram, onEdgeClick }) {
36
37
  const hoveredEdgeId = useHoveredEdgeId();
37
- const setHoveredEdge = useSetHoveredEdge();
38
38
  const edgeSprings = useCallback(
39
39
  (edge, isHovered = false) => {
40
40
  return {
@@ -83,9 +83,25 @@ export function Edges({ animate, theme, diagram, onEdgeClick }) {
83
83
  // to avoid any issues with diagram-to-diagram transitions
84
84
  keys: (e) => e.id + diagram.id
85
85
  });
86
- return edgeTransitions((springs, edge, { key }) => /* @__PURE__ */ jsx(
87
- Group,
86
+ return edgeTransitions((_, edge, { key, ctrl }) => /* @__PURE__ */ jsx(
87
+ EdgeShape,
88
+ {
89
+ animate,
90
+ edge,
91
+ isHovered: hoveredEdgeId === edge.id,
92
+ theme,
93
+ ctrl,
94
+ onEdgeClick
95
+ },
96
+ key
97
+ ));
98
+ }
99
+ const EdgeShape = memo(({ animate, edge, ctrl, theme, isHovered, onEdgeClick }) => {
100
+ const setHoveredEdge = useSetHoveredEdge();
101
+ return /* @__PURE__ */ jsx(
102
+ AnimatedGroup,
88
103
  {
104
+ opacity: ctrl.springs.opacity,
89
105
  onPointerClick: (e) => {
90
106
  if (!onEdgeClick || DiagramGesture.isDragging || e.evt.button !== 0) {
91
107
  return;
@@ -104,16 +120,16 @@ export function Edges({ animate, theme, diagram, onEdgeClick }) {
104
120
  mouseDefault(e);
105
121
  },
106
122
  children: /* @__PURE__ */ jsx(
107
- EdgeShape,
123
+ Edge,
108
124
  {
109
125
  animate,
110
126
  edge,
111
- isHovered: hoveredEdgeId === edge.id,
127
+ isHovered,
112
128
  theme,
113
- springs
129
+ springs: ctrl.springs
114
130
  }
115
131
  )
116
- },
117
- key
118
- ));
119
- }
132
+ }
133
+ );
134
+ }, isEqualSimple);
135
+ EdgeShape.displayName = "EdgeShape";
@@ -1,7 +1,8 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { nonexhaustive } from "@likec4/core";
3
+ import { isEqualSimple } from "@react-hookz/deep-equal/esnext";
3
4
  import { useTransition } from "@react-spring/konva";
4
- import { useRef } from "react";
5
+ import { memo, useRef } from "react";
5
6
  import { AnimatedGroup } from "../konva.js";
6
7
  import { Portal } from "../konva-portal.js";
7
8
  import { ZoomInIcon } from "./icons/index.js";
@@ -115,7 +116,7 @@ export function Nodes({ animate, theme, diagram, onNodeClick }) {
115
116
  keys: keyOf
116
117
  });
117
118
  return nodeTransitions((_, node, { key, ctrl, expired }) => /* @__PURE__ */ jsx(
118
- NodeSnape,
119
+ NodeShape,
119
120
  {
120
121
  animate,
121
122
  node,
@@ -128,26 +129,27 @@ export function Nodes({ animate, theme, diagram, onNodeClick }) {
128
129
  key
129
130
  ));
130
131
  }
131
- function NodeSnape({
132
- animate,
133
- node,
134
- ctrl,
135
- theme,
136
- isHovered,
137
- expired,
138
- onNodeClick
139
- }) {
140
- const setHoveredNode = useSetHoveredNode();
141
- const _isCompound = isCompound(node);
142
- const isNavigatable = !!node.navigateTo && !!onNodeClick;
143
- const Shape = nodeShape(node);
144
- const springs = ctrl.springs;
145
- return /* @__PURE__ */ jsx(Portal, { selector: ".top", enabled: isHovered && !_isCompound, children: /* @__PURE__ */ jsxs(
146
- AnimatedGroup,
147
- {
148
- name: node.id,
149
- visible: expired !== true,
150
- ...animate && {
132
+ const NodeShape = memo(
133
+ ({ animate, node, ctrl, theme, isHovered, expired, onNodeClick }) => {
134
+ const setHoveredNode = useSetHoveredNode();
135
+ const _isCompound = isCompound(node);
136
+ const isNavigatable = animate && !!node.navigateTo && !!onNodeClick;
137
+ const Shape = nodeShape(node);
138
+ const springs = ctrl.springs;
139
+ let zoomInIconY;
140
+ switch (node.shape) {
141
+ case "browser":
142
+ case "mobile":
143
+ zoomInIconY = node.size.height - 20;
144
+ break;
145
+ default:
146
+ zoomInIconY = node.size.height - 16;
147
+ }
148
+ return /* @__PURE__ */ jsx(Portal, { selector: ".top", enabled: isHovered && !_isCompound, children: /* @__PURE__ */ jsxs(
149
+ AnimatedGroup,
150
+ {
151
+ name: node.id,
152
+ visible: expired !== true,
151
153
  onPointerEnter: (e) => {
152
154
  setHoveredNode(node);
153
155
  if (isNavigatable) {
@@ -157,52 +159,46 @@ function NodeSnape({
157
159
  onPointerLeave: (e) => {
158
160
  setHoveredNode(null);
159
161
  mouseDefault(e);
160
- }
161
- },
162
- ...onNodeClick && {
163
- onPointerClick: (e) => {
164
- if (DiagramGesture.isDragging || e.evt.button !== 0) {
165
- return;
166
- }
167
- e.cancelBubble = true;
168
- onNodeClick(node, e);
169
- }
170
- },
171
- x: springs.x,
172
- y: springs.y,
173
- offsetX: springs.offsetX,
174
- offsetY: springs.offsetY,
175
- width: springs.width,
176
- height: springs.height,
177
- scaleX: springs.scaleX,
178
- scaleY: springs.scaleY,
179
- opacity: springs.opacity,
180
- children: [
181
- _isCompound && /* @__PURE__ */ jsxs(Fragment, { children: [
182
- /* @__PURE__ */ jsx(
183
- CompoundShape,
184
- {
185
- node,
186
- theme,
187
- springs,
188
- labelOffsetX: isNavigatable ? -12 : 4
189
- }
190
- ),
191
- isNavigatable && /* @__PURE__ */ jsx(ZoomInIcon, { fill: "#BABABA", opacity: 0.9, size: 16, x: 16, y: 17 })
192
- ] }),
193
- !_isCompound && /* @__PURE__ */ jsxs(Fragment, { children: [
194
- /* @__PURE__ */ jsx(Shape, { node, theme, springs, isHovered }),
195
- isNavigatable && /* @__PURE__ */ jsx(
196
- ZoomInIcon,
197
- {
198
- fill: "#BABABA",
199
- size: 16,
200
- x: node.size.width / 2,
201
- y: node.size.height - 20
162
+ },
163
+ ...onNodeClick && {
164
+ onPointerClick: (e) => {
165
+ if (DiagramGesture.isDragging || e.evt.button !== 0) {
166
+ return;
202
167
  }
203
- )
204
- ] })
205
- ]
206
- }
207
- ) });
208
- }
168
+ e.cancelBubble = true;
169
+ onNodeClick(node, e);
170
+ }
171
+ },
172
+ x: springs.x,
173
+ y: springs.y,
174
+ offsetX: springs.offsetX,
175
+ offsetY: springs.offsetY,
176
+ width: springs.width,
177
+ height: springs.height,
178
+ scaleX: springs.scaleX,
179
+ scaleY: springs.scaleY,
180
+ opacity: springs.opacity,
181
+ children: [
182
+ _isCompound && /* @__PURE__ */ jsxs(Fragment, { children: [
183
+ /* @__PURE__ */ jsx(
184
+ CompoundShape,
185
+ {
186
+ node,
187
+ theme,
188
+ springs,
189
+ labelOffsetX: isNavigatable ? -18 : 0
190
+ }
191
+ ),
192
+ isNavigatable && /* @__PURE__ */ jsx(ZoomInIcon, { opacity: 0.9, size: 18, x: 18, y: 22 })
193
+ ] }),
194
+ !_isCompound && /* @__PURE__ */ jsxs(Fragment, { children: [
195
+ /* @__PURE__ */ jsx(Shape, { node, theme, springs, isHovered }),
196
+ isNavigatable && /* @__PURE__ */ jsx(ZoomInIcon, { size: 16, x: node.size.width / 2, y: zoomInIconY })
197
+ ] })
198
+ ]
199
+ }
200
+ ) });
201
+ },
202
+ isEqualSimple
203
+ );
204
+ NodeShape.displayName = "NodeShape";
@@ -1,6 +1,6 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { Path } from "../../konva.js";
3
- export const ZoomInIcon = ({ fill, opacity = 1, size = 20, x, y }) => {
3
+ export const ZoomInIcon = ({ fill = "#BABABA", opacity = 1, size = 20, x, y }) => {
4
4
  const originalSize = 15;
5
5
  const scale = size / originalSize;
6
6
  const offsetIcon = originalSize / 2;
@@ -20,8 +20,7 @@ export const ZoomInIcon = ({ fill, opacity = 1, size = 20, x, y }) => {
20
20
  width: originalSize,
21
21
  height: originalSize,
22
22
  opacity,
23
- globalCompositeOperation: "luminosity",
24
- hitStrokeWidth: 5
23
+ globalCompositeOperation: "luminosity"
25
24
  }
26
25
  );
27
26
  };
@@ -1,9 +1,9 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { AnimatedCircle, AnimatedRect } from "../../konva.js";
2
3
  import { useShadowSprings } from "../springs.js";
3
- import { AnimatedRect, Circle } from "../../konva.js";
4
+ import { NodeIcon } from "./NodeIcon.js";
4
5
  import { NodeLabels } from "./NodeLabel.js";
5
6
  export function BrowserShape({ node, theme, springs, isHovered }) {
6
- const colors = theme.elements[node.color];
7
7
  return /* @__PURE__ */ jsxs(Fragment, { children: [
8
8
  /* @__PURE__ */ jsx(
9
9
  AnimatedRect,
@@ -13,12 +13,43 @@ export function BrowserShape({ node, theme, springs, isHovered }) {
13
13
  strokeEnabled: false,
14
14
  width: springs.width,
15
15
  height: springs.height,
16
- fill: springs.stroke
16
+ fill: springs.stroke,
17
+ perfectDrawEnabled: false
18
+ }
19
+ ),
20
+ /* @__PURE__ */ jsx(
21
+ AnimatedCircle,
22
+ {
23
+ x: 16,
24
+ y: 15,
25
+ radius: 7,
26
+ fill: springs.fill,
27
+ listening: false,
28
+ perfectDrawEnabled: false
29
+ }
30
+ ),
31
+ /* @__PURE__ */ jsx(
32
+ AnimatedCircle,
33
+ {
34
+ x: 36,
35
+ y: 15,
36
+ radius: 7,
37
+ fill: springs.fill,
38
+ listening: false,
39
+ perfectDrawEnabled: false
40
+ }
41
+ ),
42
+ /* @__PURE__ */ jsx(
43
+ AnimatedCircle,
44
+ {
45
+ x: 56,
46
+ y: 15,
47
+ radius: 7,
48
+ fill: springs.fill,
49
+ listening: false,
50
+ perfectDrawEnabled: false
17
51
  }
18
52
  ),
19
- /* @__PURE__ */ jsx(Circle, { x: 16, y: 15, radius: 7, fill: colors.fill, listening: false }),
20
- /* @__PURE__ */ jsx(Circle, { x: 36, y: 15, radius: 7, fill: colors.fill, listening: false }),
21
- /* @__PURE__ */ jsx(Circle, { x: 56, y: 15, radius: 7, fill: colors.fill, listening: false }),
22
53
  /* @__PURE__ */ jsx(
23
54
  AnimatedRect,
24
55
  {
@@ -28,7 +59,8 @@ export function BrowserShape({ node, theme, springs, isHovered }) {
28
59
  width: springs.width.to((w) => w - 80),
29
60
  height: 16,
30
61
  fill: springs.fill,
31
- listening: false
62
+ listening: false,
63
+ perfectDrawEnabled: false
32
64
  }
33
65
  ),
34
66
  /* @__PURE__ */ jsx(
@@ -40,9 +72,11 @@ export function BrowserShape({ node, theme, springs, isHovered }) {
40
72
  width: springs.width.to((w) => w - 18),
41
73
  height: springs.height.to((h) => h - 40),
42
74
  fill: springs.fill,
43
- listening: false
75
+ listening: false,
76
+ perfectDrawEnabled: false
44
77
  }
45
78
  ),
46
- /* @__PURE__ */ jsx(NodeLabels, { node, theme, offsetY: -8 })
79
+ /* @__PURE__ */ jsx(NodeLabels, { node, theme, offsetY: -6 }),
80
+ /* @__PURE__ */ jsx(NodeIcon, { node, paddingY: 42 })
47
81
  ] });
48
82
  }
@@ -1,6 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { AnimatedRect, AnimatedText } from "../../konva.js";
3
- export function CompoundShape({ node, theme, springs, labelOffsetX = 4 }) {
2
+ import { AnimatedRect, Text } from "../../konva.js";
3
+ export function CompoundShape({ node, theme, springs, labelOffsetX = 0 }) {
4
4
  const { labels } = node;
5
5
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6
6
  /* @__PURE__ */ jsx(
@@ -21,25 +21,23 @@ export function CompoundShape({ node, theme, springs, labelOffsetX = 4 }) {
21
21
  }
22
22
  ),
23
23
  labels.map(({ pt: [x, y], ...label }, i) => /* @__PURE__ */ jsx(
24
- AnimatedText,
24
+ Text,
25
25
  {
26
- x,
27
- y: y - 4,
26
+ x: x - 6,
27
+ y: y - 8,
28
28
  offsetX: labelOffsetX,
29
- offsetY: label.fontSize / 2,
30
- width: springs.width.to((v) => v - x - 4),
29
+ width: node.size.width - x - 8 - 6 + labelOffsetX,
31
30
  fill: "#BABABA",
32
31
  fontFamily: theme.font,
33
32
  fontSize: label.fontSize,
34
33
  fontStyle: label.fontStyle ?? "normal",
35
- letterSpacing: 0.8,
34
+ letterSpacing: 0.75,
36
35
  align: label.align,
37
36
  text: label.text,
38
37
  wrap: "none",
39
38
  ellipsis: true,
40
39
  perfectDrawEnabled: false,
41
- padding: 6,
42
- hitStrokeWidth: 3,
40
+ padding: 8,
43
41
  globalCompositeOperation: "luminosity"
44
42
  },
45
43
  i