@likec4/diagram 1.7.3 → 1.8.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.
package/dist/index.mjs CHANGED
@@ -12,16 +12,16 @@ import { invariant, hasAtLeast, whereOperatorAsPredicate, extractStep, nonNullab
12
12
  import { useFirstMountState, useCustomCompareEffect, useDebouncedEffect } from "@react-hookz/web";
13
13
  import { getNodeDimensions, getBoundsOfRects, boxToRect, getViewportForBounds as getViewportForBounds$1 } from "@xyflow/system";
14
14
  import { DEV } from "esm-env";
15
- import { isArray, hasAtLeast as hasAtLeast$1, isNullish, entries, reduce, isTruthy, first, last, clamp, isNumber, isNonNullish, keys, takeWhile } from "remeda";
15
+ import { isArray, hasAtLeast as hasAtLeast$1, isNullish, entries, reduce, map, prop, isTruthy, first, last, clamp, isNumber, isNonNullish, ceil, keys, takeWhile } from "remeda";
16
16
  import { subscribeWithSelector, devtools } from "zustand/middleware";
17
17
  import { shallow } from "zustand/shallow";
18
18
  import { Bezier } from "bezier-js";
19
- import { MantineContext, Box, ActionIcon as ActionIcon$2, Text as Text$1, HoverCard, HoverCardTarget, UnstyledButton, HoverCardDropdown, Stack, Group, Anchor, CopyButton, Button, Card, CardSection, Spoiler, ButtonGroup, rem, Divider, Space, TooltipGroup, Flex, Tooltip as Tooltip$2, ColorSwatch, CheckIcon, Select, Slider, SegmentedControl, Popover, PopoverTarget, PopoverDropdown, FloatingIndicator, Notification, Code } from "@mantine/core";
19
+ import { MantineContext, Box, ActionIcon as ActionIcon$2, Text as Text$1, HoverCard, HoverCardTarget, UnstyledButton, HoverCardDropdown, Stack, Group, Anchor, CopyButton, Button, Card, CardSection, Spoiler, ButtonGroup, Tooltip as Tooltip$2, ThemeIcon, Paper, Tabs, TabsList, TabsTab, TabsPanel, ScrollAreaAutosize, Badge, rem, Divider, Space, TooltipGroup, Flex, ColorSwatch, CheckIcon, Select, Slider, SegmentedControl, Popover, PopoverTarget, PopoverDropdown, FloatingIndicator, Notification, Code } from "@mantine/core";
20
20
  import { useHotkeys, useLocalStorage } from "@mantine/hooks";
21
21
  import { assignInlineVars } from "@vanilla-extract/dynamic";
22
22
  import { line, curveCatmullRomOpen } from "d3-shape";
23
23
  import { Solver, Operator, Expression, Strength, Variable } from "@lume/kiwi";
24
- import { IconZoomScan, IconLink, IconMenu, IconPlayerPlayFilled, IconPlayerSkipBackFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled, IconFileSymlink, IconLayoutDashboard, IconArrowBigDownLines, IconFocusCentered, IconMenu2, IconChevronLeft, IconChevronRight, IconAlertTriangle } from "@tabler/icons-react";
24
+ import { IconZoomScan, IconLink, IconMenu, IconPlayerPlayFilled, IconPlayerSkipBackFilled, IconPlayerStopFilled, IconPlayerSkipForwardFilled, IconAlertTriangle, IconHelpCircle, IconArrowDownRight, IconFileSymlink, IconLayoutDashboard, IconArrowBigDownLines, IconFocusCentered, IconMenu2, IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
25
25
  var rootClassName = "likec4-diagram-root";
26
26
  const noop = () => {
27
27
  }, depsShallowEqual = (d1, d2) => {
@@ -55,7 +55,7 @@ const useXYStoreApi = useStoreApi, ZIndexes = {
55
55
  Compound: 2,
56
56
  Edge: 4,
57
57
  Element: 6
58
- }, MinZoom = 0.1;
58
+ }, MinZoom = 0.1, MaxZoom = 2;
59
59
  function toDomPrecision(v) {
60
60
  return v === null ? 0.01 : Math.round(v * 100) / 100;
61
61
  }
@@ -103,9 +103,9 @@ function diagramViewToXYFlowData(view, opts) {
103
103
  const ns = "", nodeById = (id) => nonNullable(nodeLookup.get(id), `Node not found: ${id}`);
104
104
  let next;
105
105
  for (; next = traverse.shift(); ) {
106
- const { node, parent } = next;
107
- node.children.length > 0 && traverse.push(...node.children.map((child) => ({ node: nodeById(child), parent: node })));
108
- const isCompound = hasAtLeast$1(node.children, 1), position = {
106
+ const { node, parent } = next, isCompound = hasAtLeast$1(node.children, 1);
107
+ isCompound && traverse.push(...node.children.map((child) => ({ node: nodeById(child), parent: node })));
108
+ const position = {
109
109
  x: node.position[0],
110
110
  y: node.position[1]
111
111
  };
@@ -120,6 +120,7 @@ function diagramViewToXYFlowData(view, opts) {
120
120
  },
121
121
  draggable,
122
122
  selectable: opts.selectable,
123
+ focusable: opts.selectable && !isCompound,
123
124
  deletable: !1,
124
125
  position,
125
126
  zIndex: isCompound ? ZIndexes.Compound : ZIndexes.Element,
@@ -137,7 +138,11 @@ function diagramViewToXYFlowData(view, opts) {
137
138
  }
138
139
  for (const edge of view.edges) {
139
140
  const source = edge.source, target = edge.target, id = ns + edge.id;
140
- invariant(hasAtLeast$1(edge.points, 2), "edge should have at least 2 points"), xyedges.push({
141
+ if (!hasAtLeast$1(edge.points, 2)) {
142
+ console.error("edge should have at least 2 points", edge);
143
+ continue;
144
+ }
145
+ xyedges.push({
141
146
  id,
142
147
  type: "relationship",
143
148
  source: ns + source,
@@ -650,6 +655,13 @@ function createDiagramStore(props) {
650
655
  noReplace,
651
656
  "onInit"
652
657
  );
658
+ },
659
+ highlightByElementNotation: (notation, onlyOfKind) => {
660
+ const { xynodes: xynodes2, xyedges: xyedges2 } = get(), dimmed2 = new StringSet(map(xyedges2, prop("id")));
661
+ xynodes2.forEach(({ id, data }) => {
662
+ const node = data.element;
663
+ (node.shape !== notation.shape || node.color !== notation.color || !notation.kinds.includes(node.kind) || onlyOfKind && node.kind !== onlyOfKind) && dimmed2.add(id);
664
+ }), set({ dimmed: dimmed2 }, noReplace, "highlightByElementNotation");
653
665
  }
654
666
  }),
655
667
  {
@@ -754,7 +766,7 @@ function FitViewOnViewportResize({ diagramApi }) {
754
766
  1e3
755
767
  ), null;
756
768
  }
757
- function selector$3({ view, viewportChanged, fitViewPadding }) {
769
+ function selector$4({ view, viewportChanged, fitViewPadding }) {
758
770
  return {
759
771
  layoutId: view.id + "_" + view.autoLayout + "_" + fitViewPadding,
760
772
  viewportNotMoved: !viewportChanged
@@ -764,7 +776,7 @@ function FitViewOnDiagramChange() {
764
776
  const {
765
777
  layoutId,
766
778
  viewportNotMoved
767
- } = useDiagramState(selector$3, shallowEqual), diagramApi = useDiagramStoreApi(), [currentLayoutId, setCurrent] = useState(layoutId), requiresFit = layoutId !== currentLayoutId;
779
+ } = useDiagramState(selector$4, shallowEqual), diagramApi = useDiagramStoreApi(), [currentLayoutId, setCurrent] = useState(layoutId), requiresFit = layoutId !== currentLayoutId;
768
780
  return useDebouncedEffect(
769
781
  () => {
770
782
  layoutId !== currentLayoutId && (setCurrent(layoutId), diagramApi.getState().fitDiagram(450));
@@ -1012,7 +1024,7 @@ const Open = (props) => /* @__PURE__ */ jsx(
1012
1024
  Dot,
1013
1025
  ODot
1014
1026
  };
1015
- var container$4 = "bexn7q1", dimmed$2 = "bexn7q2", edgePathBg = "bexn7q3", markerContext = "bexn7q4", controlPoint = "bexn7q5", controlDragging = "bexn7q6", cssEdgePath = "bexn7q8", stepEdgeNumber = "bexn7q9", varLabelX = "var(--bexn7qa)", varLabelY = "var(--bexn7qb)", edgeLabel = "bexn7qd", edgeLabelText = "bexn7qe";
1027
+ var container$5 = "bexn7q1", dimmed$2 = "bexn7q2", edgePathBg = "bexn7q3", markerContext = "bexn7q4", controlPoint = "bexn7q5", controlDragging = "bexn7q6", cssEdgePath = "bexn7q8", stepEdgeNumber = "bexn7q9", varLabelX = "var(--bexn7qa)", varLabelY = "var(--bexn7qb)", edgeLabel = "bexn7qd", edgeLabelText = "bexn7qe";
1016
1028
  function getNodeIntersectionFromCenterToPoint(intersectionNode, { x: x1, y: y1 }) {
1017
1029
  const {
1018
1030
  width: intersectionNodeWidth,
@@ -1188,7 +1200,7 @@ const isSame = (a, b) => Math.abs(a - b) < 2.5, isSamePoint = (a, b) => {
1188
1200
  "g",
1189
1201
  {
1190
1202
  className: clsx(
1191
- container$4,
1203
+ container$5,
1192
1204
  isDimmed && dimmed$2,
1193
1205
  isControlPointDragging && controlDragging
1194
1206
  ),
@@ -1298,7 +1310,7 @@ const isSame = (a, b) => Math.abs(a - b) < 2.5, isSamePoint = (a, b) => {
1298
1310
  {
1299
1311
  className: clsx([
1300
1312
  "nodrag nopan",
1301
- container$4,
1313
+ container$5,
1302
1314
  edgeLabel,
1303
1315
  isDimmed && dimmed$2
1304
1316
  ]),
@@ -1338,6 +1350,7 @@ function NavigateToBtn({ xynodeId, className }) {
1338
1350
  onClick: (event) => {
1339
1351
  event.stopPropagation(), diagramApi.getState().triggerOnNavigateTo(xynodeId, event);
1340
1352
  },
1353
+ role: "button",
1341
1354
  onDoubleClick: (event) => event.stopPropagation(),
1342
1355
  children: /* @__PURE__ */ jsx(
1343
1356
  IconZoomScan,
@@ -1351,7 +1364,7 @@ function NavigateToBtn({ xynodeId, className }) {
1351
1364
  }
1352
1365
  );
1353
1366
  }
1354
- var container$3 = "lbtzdo0", nodeHandlerInCenter = "lbtzdo1", dimmed$1 = "lbtzdo2", varOpacity = "var(--lbtzdo3)", varBorderTransparency = "var(--lbtzdo4)", compoundBody = "lbtzdo5", transparent = "lbtzdo7", title$2 = "lbtzdo8", titleWithNavigation = "lbtzdo9", indicator$1 = "lbtzdod", navigateBtn = "lbtzdof";
1367
+ var container$4 = "lbtzdo0", nodeHandlerInCenter = "lbtzdo1", dimmed$1 = "lbtzdo2", varOpacity = "var(--lbtzdo3)", varBorderTransparency = "var(--lbtzdo4)", compoundBody = "lbtzdo5", transparent = "lbtzdo7", title$2 = "lbtzdo8", titleWithNavigation = "lbtzdo9", indicator$1 = "lbtzdod", navigateBtn = "lbtzdof";
1355
1368
  const isEqualProps$1 = (prev, next) => prev.id === next.id && deepEqual(prev.data.element, next.data.element), CompoundNodeMemo = /* @__PURE__ */ memo(function({
1356
1369
  id,
1357
1370
  data: {
@@ -1382,7 +1395,7 @@ const isEqualProps$1 = (prev, next) => prev.id === next.id && deepEqual(prev.dat
1382
1395
  Box,
1383
1396
  {
1384
1397
  className: clsx(
1385
- container$3,
1398
+ container$4,
1386
1399
  "likec4-compound-node",
1387
1400
  opacity < 1 && "likec4-compound-transparent",
1388
1401
  isDimmed && dimmed$1
@@ -1446,8 +1459,8 @@ const isEqualProps$1 = (prev, next) => prev.id === next.id && deepEqual(prev.dat
1446
1459
  )
1447
1460
  ] });
1448
1461
  }, isEqualProps$1);
1449
- var container$2 = "_1b6tkn1", handleCenter = "_1b6tkn2", containerAnimated = "_1b6tkn3", dimmed = "_1b6tkn4", indicator = "_1b6tkn7", fillElementFill = "_1b6tkn8", fillMixStroke = "_1b6tkna", hasIcon = "_1b6tknb", title$1 = "_1b6tkne", description$1 = "_1b6tknf", technology = "_1b6tkng", elementDataContainer = "_1b6tknh", elementTextData = "_1b6tkni", elementIcon = "_1b6tknj", cssShapeSvg = "_1b6tknm", cssNavigateBtn = "_1b6tknn", elementLink = "_4ysv0o0", trigger = "_4ysv0o1";
1450
- const selector$2 = (s) => {
1462
+ var container$3 = "_1b6tkn1", handleCenter = "_1b6tkn2", containerAnimated = "_1b6tkn3", dimmed = "_1b6tkn4", indicator = "_1b6tkn7", fillElementFill = "_1b6tkn8", fillMixStroke = "_1b6tkna", hasIcon = "_1b6tknb", title$1 = "_1b6tkne", description$1 = "_1b6tknf", technology = "_1b6tkng", elementDataContainer = "_1b6tknh", elementTextData = "_1b6tkni", elementIcon = "_1b6tknj", cssShapeSvg = "_1b6tknm", cssNavigateBtn = "_1b6tknn", elementLink = "_4ysv0o0", trigger = "_4ysv0o1";
1463
+ const selector$3 = (s) => {
1451
1464
  const target = s.getContainer();
1452
1465
  return target ? { target } : null;
1453
1466
  };
@@ -1455,7 +1468,7 @@ function ElementLink({
1455
1468
  element
1456
1469
  }) {
1457
1470
  invariant(element.links, "ElementLink: links are required");
1458
- const id = useId(), portalProps = useDiagramState(selector$2);
1471
+ const id = useId(), portalProps = useDiagramState(selector$3);
1459
1472
  return /* @__PURE__ */ jsx("div", { className: elementLink, "data-likec4-linkid": id, children: /* @__PURE__ */ jsxs(
1460
1473
  HoverCard,
1461
1474
  {
@@ -1495,8 +1508,8 @@ function ElementLink({
1495
1508
  onClick: (e) => e.stopPropagation(),
1496
1509
  p: "xs",
1497
1510
  children: /* @__PURE__ */ jsx(Stack, { gap: "xs", children: element.links.map((link) => /* @__PURE__ */ jsxs(Group, { wrap: "nowrap", gap: "sm", children: [
1498
- /* @__PURE__ */ jsx(Box, { flex: "1", style: { overflow: "clip", maxWidth: clamp(element.width, { min: 200, max: 400 }) }, children: /* @__PURE__ */ jsx(Anchor, { href: link, target: "_blank", fz: "13", truncate: "end", children: link }) }),
1499
- /* @__PURE__ */ jsx(CopyButton, { value: link, children: ({ copied, copy }) => /* @__PURE__ */ jsx(
1511
+ /* @__PURE__ */ jsx(Box, { flex: "1", style: { overflow: "clip", maxWidth: clamp(element.width, { min: 200, max: 400 }) }, children: /* @__PURE__ */ jsx(Anchor, { href: link.url, target: "_blank", fz: "13", truncate: "end", children: link.title || link.url }) }),
1512
+ /* @__PURE__ */ jsx(CopyButton, { value: link.url, children: ({ copied, copy }) => /* @__PURE__ */ jsx(
1500
1513
  Button,
1501
1514
  {
1502
1515
  size: "compact-xs",
@@ -1507,7 +1520,7 @@ function ElementLink({
1507
1520
  children: copied ? "copied" : "copy"
1508
1521
  }
1509
1522
  ) })
1510
- ] }, link)) })
1523
+ ] }, link.url)) })
1511
1524
  }
1512
1525
  )
1513
1526
  ]
@@ -1758,7 +1771,7 @@ const Text = Text$1.withProps({
1758
1771
  {
1759
1772
  id,
1760
1773
  className: clsx([
1761
- container$2,
1774
+ container$3,
1762
1775
  isDimmed && dimmed,
1763
1776
  animate !== "idle" && containerAnimated,
1764
1777
  "likec4-element-node"
@@ -2016,10 +2029,13 @@ function useXYFlowEvents() {
2016
2029
  onDoubleClick: (event) => {
2017
2030
  const {
2018
2031
  onCanvasDblClick,
2032
+ zoomable,
2019
2033
  xystore,
2034
+ viewportChanged,
2035
+ fitDiagram,
2020
2036
  resetLastClicked
2021
2037
  } = diagramApi.getState();
2022
- resetLastClicked(), xystore.getState().resetSelectedElements(), onCanvasDblClick || event.stopPropagation(), onCanvasDblClick == null || onCanvasDblClick(event);
2038
+ resetLastClicked(), xystore.getState().resetSelectedElements(), onCanvasDblClick || event.stopPropagation(), zoomable && viewportChanged && fitDiagram(), onCanvasDblClick == null || onCanvasDblClick(event);
2023
2039
  },
2024
2040
  onPaneClick: (event) => {
2025
2041
  if (dbclickLock())
@@ -2185,7 +2201,7 @@ const nodeTypes = {
2185
2201
  compound: CompoundNodeMemo
2186
2202
  }, edgeTypes = {
2187
2203
  relationship: RelationshipEdge
2188
- }, selector$1 = (s) => ({
2204
+ }, selector$2 = (s) => ({
2189
2205
  nodes: s.xynodes,
2190
2206
  edges: s.xyedges,
2191
2207
  onInit: s.onInit,
@@ -2233,11 +2249,11 @@ function XYFlow({
2233
2249
  hasOnEdgeContextMenu,
2234
2250
  translateX,
2235
2251
  translateY
2236
- } = useDiagramState(selector$1, shallowEqual), layoutConstraints = useLayoutConstraints(), handlers = useXYFlowEvents();
2252
+ } = useDiagramState(selector$2, shallowEqual), [zoomOnDoubleClick, setZoomOnDoubleClick] = useState(zoomable), layoutConstraints = useLayoutConstraints(), handlers = useXYFlowEvents();
2237
2253
  return useOnViewportChange({
2238
2254
  onEnd: ({ x, y, zoom }) => {
2239
2255
  const roundedX = Math.round(x), roundedY = Math.round(y);
2240
- (x !== roundedX || y !== roundedY) && xyflowApi.setState({ transform: [roundedX, roundedY, zoom] });
2256
+ (x !== roundedX || y !== roundedY) && xyflowApi.setState({ transform: [roundedX, roundedY, zoom] }), setZoomOnDoubleClick(zoomable && zoom < 1.1);
2241
2257
  }
2242
2258
  }), /* @__PURE__ */ jsx(
2243
2259
  ReactFlow,
@@ -2254,7 +2270,7 @@ function XYFlow({
2254
2270
  ...!zoomable && {
2255
2271
  zoomActivationKeyCode: null
2256
2272
  },
2257
- maxZoom: zoomable ? 1.9 : 1,
2273
+ maxZoom: zoomable ? MaxZoom : 1,
2258
2274
  minZoom: zoomable ? MinZoom : 1,
2259
2275
  fitView,
2260
2276
  fitViewOptions: useMemo(() => ({
@@ -2289,7 +2305,7 @@ function XYFlow({
2289
2305
  onNodeDragStop: layoutConstraints.onNodeDragStop
2290
2306
  },
2291
2307
  nodeDragThreshold: 3,
2292
- zoomOnDoubleClick: zoomable,
2308
+ zoomOnDoubleClick,
2293
2309
  elevateNodesOnSelect: !1,
2294
2310
  selectNodesOnDrag: !1,
2295
2311
  onNodesChange,
@@ -2319,15 +2335,15 @@ function XYFlow({
2319
2335
  }
2320
2336
  );
2321
2337
  }
2322
- var container$1 = "hbkbtj0", card = "hbkbtj1", title = "hbkbtj2", description = "hbkbtj3";
2323
- const selector = (s) => ({
2338
+ var container$2 = "hbkbtj0", card$1 = "hbkbtj1", title = "hbkbtj2", description = "hbkbtj3";
2339
+ const selector$1 = (s) => ({
2324
2340
  id: s.view.id,
2325
2341
  title: s.view.title ?? "untitled",
2326
2342
  description: s.view.description,
2327
2343
  links: s.view.links
2328
2344
  });
2329
2345
  function DiagramTitlePanel() {
2330
- const { id, title: title$12, description: description$12, links } = useDiagramState(selector), [isCollapsed, setCollapsed] = useLocalStorage({
2346
+ const { id, title: title$12, description: description$12, links } = useDiagramState(selector$1), [isCollapsed, setCollapsed] = useLocalStorage({
2331
2347
  key: "diagram-title-panel-collapsed",
2332
2348
  defaultValue: !1
2333
2349
  }), toggle = () => setCollapsed((v) => !v);
@@ -2340,7 +2356,7 @@ function DiagramTitlePanel() {
2340
2356
  opacity: 0.05,
2341
2357
  scale: 0.6
2342
2358
  },
2343
- className: clsx("react-flow__panel", container$1),
2359
+ className: clsx("react-flow__panel", container$2),
2344
2360
  style: {
2345
2361
  transformOrigin: "left center"
2346
2362
  },
@@ -2348,64 +2364,78 @@ function DiagramTitlePanel() {
2348
2364
  Card,
2349
2365
  {
2350
2366
  radius: "sm",
2351
- className: card,
2352
- withBorder: !0,
2353
- p: "md",
2354
- pb: isCollapsed ? "sm" : "md",
2367
+ className: card$1,
2368
+ withBorder: !isCollapsed,
2369
+ p: isCollapsed ? "sm" : "md",
2355
2370
  onDoubleClick: (e) => e.stopPropagation(),
2356
2371
  children: [
2357
- /* @__PURE__ */ jsx(CardSection, { mb: isCollapsed ? 10 : "sm", children: /* @__PURE__ */ jsx(
2372
+ isCollapsed && /* @__PURE__ */ jsx(CardSection, { children: /* @__PURE__ */ jsx(
2358
2373
  Button,
2359
2374
  {
2360
2375
  fullWidth: !0,
2361
- size: "xs",
2362
- h: "sm",
2363
- py: 2,
2376
+ justify: "stretch",
2377
+ size: "md",
2364
2378
  radius: "0",
2365
- variant: isCollapsed ? "light" : "subtle",
2379
+ variant: "subtle",
2366
2380
  color: "gray",
2367
2381
  onClick: toggle,
2368
- children: /* @__PURE__ */ jsx(IconMenu, { size: 11, opacity: 0.7 })
2382
+ fw: 500,
2383
+ fz: "sm",
2384
+ children: title$12
2369
2385
  }
2370
2386
  ) }),
2371
- /* @__PURE__ */ jsxs(Group, { justify: "stretch", wrap: "nowrap", mb: isCollapsed ? 0 : "sm", children: [
2372
- /* @__PURE__ */ jsx(
2373
- Text$1,
2374
- {
2375
- component: "div",
2376
- flex: "1",
2377
- size: isCollapsed ? "sm" : "lg",
2378
- fw: 500,
2379
- lh: 1.1,
2380
- className: title,
2381
- children: title$12
2382
- }
2383
- ),
2384
- /* @__PURE__ */ jsxs(
2385
- Text$1,
2387
+ !isCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
2388
+ /* @__PURE__ */ jsx(CardSection, { mb: "xs", children: /* @__PURE__ */ jsx(
2389
+ Button,
2386
2390
  {
2387
- hidden: isCollapsed,
2388
- component: "div",
2389
- flex: "0 0 auto",
2390
- inline: !0,
2391
+ fullWidth: !0,
2391
2392
  size: "xs",
2392
- fz: 9,
2393
- fw: 500,
2394
- c: "dimmed",
2395
- style: {
2396
- userSelect: "all"
2397
- },
2398
- children: [
2399
- /* @__PURE__ */ jsxs("span", { style: { userSelect: "none" }, children: [
2400
- "id:",
2401
- " "
2402
- ] }),
2403
- id
2404
- ]
2393
+ h: "sm",
2394
+ py: 2,
2395
+ radius: "0",
2396
+ variant: "subtle",
2397
+ color: "gray",
2398
+ onClick: toggle,
2399
+ children: /* @__PURE__ */ jsx(IconMenu, { size: 11, opacity: 0.7 })
2405
2400
  }
2406
- )
2407
- ] }),
2408
- !isCollapsed && /* @__PURE__ */ jsxs(Fragment, { children: [
2401
+ ) }),
2402
+ /* @__PURE__ */ jsxs(Group, { justify: "stretch", wrap: "nowrap", mb: "sm", children: [
2403
+ /* @__PURE__ */ jsx(
2404
+ Text$1,
2405
+ {
2406
+ component: "div",
2407
+ flex: "1",
2408
+ size: "md",
2409
+ fw: 500,
2410
+ lh: 1.1,
2411
+ className: title,
2412
+ children: title$12
2413
+ }
2414
+ ),
2415
+ /* @__PURE__ */ jsxs(
2416
+ Text$1,
2417
+ {
2418
+ hidden: isCollapsed,
2419
+ component: "div",
2420
+ flex: "0 0 auto",
2421
+ inline: !0,
2422
+ size: "xs",
2423
+ fz: 9,
2424
+ fw: 500,
2425
+ c: "dimmed",
2426
+ style: {
2427
+ userSelect: "all"
2428
+ },
2429
+ children: [
2430
+ /* @__PURE__ */ jsxs("span", { style: { userSelect: "none" }, children: [
2431
+ "id:",
2432
+ " "
2433
+ ] }),
2434
+ id
2435
+ ]
2436
+ }
2437
+ )
2438
+ ] }),
2409
2439
  description$12 && /* @__PURE__ */ jsx(
2410
2440
  Spoiler,
2411
2441
  {
@@ -2442,16 +2472,16 @@ function DiagramTitlePanel() {
2442
2472
  /* @__PURE__ */ jsx(Box, { flex: "1", style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx(
2443
2473
  Anchor,
2444
2474
  {
2445
- href: link,
2475
+ href: link.url,
2446
2476
  target: "_blank",
2447
2477
  fz: "xs",
2448
2478
  truncate: "end",
2449
2479
  display: "inline-block",
2450
2480
  w: "100%",
2451
- children: link
2481
+ children: link.title || link.url
2452
2482
  }
2453
2483
  ) }),
2454
- /* @__PURE__ */ jsx(CopyButton, { value: link, children: ({ copied, copy }) => /* @__PURE__ */ jsx(
2484
+ /* @__PURE__ */ jsx(CopyButton, { value: link.url, children: ({ copied, copy }) => /* @__PURE__ */ jsx(
2455
2485
  Button,
2456
2486
  {
2457
2487
  size: "compact-xs",
@@ -2462,7 +2492,7 @@ function DiagramTitlePanel() {
2462
2492
  children: copied ? "copied" : "copy"
2463
2493
  }
2464
2494
  ) })
2465
- ] }, link))
2495
+ ] }, link.url))
2466
2496
  }
2467
2497
  )
2468
2498
  ] })
@@ -2473,7 +2503,7 @@ function DiagramTitlePanel() {
2473
2503
  id
2474
2504
  ) });
2475
2505
  }
2476
- var container = "_2jkb2y0", buttons = "_2jkb2y1", btn = "_2jkb2y3";
2506
+ var container$1 = "_2jkb2y0", buttons = "_2jkb2y1", btn = "_2jkb2y3";
2477
2507
  function DynamicViewWalkthrough() {
2478
2508
  const isMobile = useXYStore((s) => s.width <= 750), {
2479
2509
  nextDynamicStep,
@@ -2508,7 +2538,7 @@ function DynamicViewWalkthrough() {
2508
2538
  return /* @__PURE__ */ jsxs(
2509
2539
  Box,
2510
2540
  {
2511
- className: clsx("react-flow__panel", container),
2541
+ className: clsx("react-flow__panel", container$1),
2512
2542
  onClick: (e) => e.stopPropagation(),
2513
2543
  onDoubleClick: (e) => e.stopPropagation(),
2514
2544
  children: [
@@ -2562,6 +2592,230 @@ function DynamicViewWalkthrough() {
2562
2592
  }
2563
2593
  );
2564
2594
  }
2595
+ var vars = { safariAnimationHook: "var(--likec4-safari-animation-hook)", default: { font: "var(--likec4-default-font-family)" }, likec4: { font: "var(--likec4-font-family)", background: { color: "var(--likec4-background-color)", pattern: { color: "var(--likec4-background-pattern-color)" } } }, compound: { font: "var(--likec4-compound-font-family)", titleColor: "var(--likec4-compound-title-color)" }, element: { font: "var(--likec4-element-font-family)", fill: "var(--likec4-element-fill)", stroke: "var(--likec4-element-stroke)", hiContrast: "var(--likec4-element-hiContrast)", loContrast: "var(--likec4-element-loContrast)" }, relation: { lineColor: "var(--likec4-relation-lineColor)", labelColor: "var(--likec4-relation-labelColor)", labelBgColor: "var(--likec4-relation-labelBg)" }, optionsPanel: { top: "var(--likec4-options-panel-top)", right: "var(--likec4-options-panel-right)" }, navigationPanel: { top: "var(--likec4-navigation-panel-top)", left: "var(--likec4-navigation-panel-left)" } }, container = "_1ncdyfa0", card = "_1ncdyfa1", tabPanel = "_1ncdyfa2", elementNotation = "_1ncdyfa3", shapeSvg = "_1ncdyfa4", shapeBadge = "_1ncdyfa5";
2596
+ const ElementNotation = ({ value }) => {
2597
+ const {
2598
+ title: title2,
2599
+ color = "primary",
2600
+ shape = "rectangle"
2601
+ } = value, [onlyKind, setOnlyKind] = useState(null), diagramStore = useDiagramStoreApi(), w = 300, h = 200;
2602
+ return /* @__PURE__ */ jsx(
2603
+ Card,
2604
+ {
2605
+ shadow: "none",
2606
+ px: "xs",
2607
+ py: "sm",
2608
+ className: elementNotation,
2609
+ mod: {
2610
+ "data-likec4-color": color
2611
+ },
2612
+ onMouseEnter: () => {
2613
+ setOnlyKind(null), diagramStore.getState().highlightByElementNotation(value);
2614
+ },
2615
+ onMouseLeave: () => {
2616
+ setOnlyKind(null), diagramStore.setState({ dimmed: /* @__PURE__ */ new Set() });
2617
+ },
2618
+ children: /* @__PURE__ */ jsxs(
2619
+ Group,
2620
+ {
2621
+ gap: "sm",
2622
+ align: "stretch",
2623
+ wrap: "nowrap",
2624
+ children: [
2625
+ /* @__PURE__ */ jsx(
2626
+ Box,
2627
+ {
2628
+ flex: "0 0 70px",
2629
+ style: {
2630
+ width: 70,
2631
+ height: ceil(70 * (h / w), 0)
2632
+ },
2633
+ children: /* @__PURE__ */ jsx(
2634
+ "svg",
2635
+ {
2636
+ className: clsx(
2637
+ shapeSvg
2638
+ ),
2639
+ viewBox: `0 0 ${w} ${h}`,
2640
+ children: /* @__PURE__ */ jsx(
2641
+ ElementShapeSvg,
2642
+ {
2643
+ shape,
2644
+ w,
2645
+ h
2646
+ }
2647
+ )
2648
+ }
2649
+ )
2650
+ }
2651
+ ),
2652
+ /* @__PURE__ */ jsxs(Stack, { gap: 4, flex: 1, children: [
2653
+ /* @__PURE__ */ jsx(Group, { gap: 4, flex: "0 0 auto", children: value.kinds.map((kind) => /* @__PURE__ */ jsx(
2654
+ Badge,
2655
+ {
2656
+ className: shapeBadge,
2657
+ onMouseEnter: () => {
2658
+ setOnlyKind(kind), diagramStore.getState().highlightByElementNotation(value, kind);
2659
+ },
2660
+ onMouseLeave: () => {
2661
+ setOnlyKind(null), diagramStore.getState().highlightByElementNotation(value);
2662
+ },
2663
+ opacity: isNonNullish(onlyKind) && onlyKind !== kind ? 0.25 : 1,
2664
+ color: isNonNullish(onlyKind) && onlyKind !== kind ? "gray" : vars.element.fill,
2665
+ children: kind
2666
+ },
2667
+ kind
2668
+ )) }),
2669
+ /* @__PURE__ */ jsx(
2670
+ Text$1,
2671
+ {
2672
+ component: "div",
2673
+ fz: "sm",
2674
+ fw: 500,
2675
+ lh: "1.25",
2676
+ style: {
2677
+ textWrap: "pretty"
2678
+ },
2679
+ children: title2
2680
+ }
2681
+ )
2682
+ ] })
2683
+ ]
2684
+ }
2685
+ )
2686
+ }
2687
+ );
2688
+ }, selector = (s) => {
2689
+ var _a;
2690
+ return {
2691
+ id: s.view.id,
2692
+ notations: ((_a = s.view.notation) == null ? void 0 : _a.elements) ?? [],
2693
+ height: s.xystore.getState().height,
2694
+ isVisible: isNullish(s.focusedNodeId ?? s.activeDynamicViewStep),
2695
+ target: s.getContainer()
2696
+ };
2697
+ };
2698
+ function NotationPanel() {
2699
+ const {
2700
+ id,
2701
+ notations,
2702
+ isVisible,
2703
+ height,
2704
+ target
2705
+ } = useDiagramState(selector), [isCollapsed, setCollapsed] = useLocalStorage({
2706
+ key: "notation-panel-collapsed",
2707
+ defaultValue: !0
2708
+ }), hasNotations = notations.length > 0, portalProps = target ? { portalProps: { target } } : { withinPortal: !1 };
2709
+ return /* @__PURE__ */ jsxs(AnimatePresence, { children: [
2710
+ !hasNotations && /* @__PURE__ */ jsx(
2711
+ m.div,
2712
+ {
2713
+ initial: { opacity: 0.75, translateX: "50%" },
2714
+ animate: { opacity: 1, translateX: 0 },
2715
+ exit: {
2716
+ translateX: "100%",
2717
+ opacity: 0.6
2718
+ },
2719
+ className: clsx("react-flow__panel", container),
2720
+ children: /* @__PURE__ */ jsx(Tooltip$2, { label: "Diagram has no notations", color: "orange", ...portalProps, children: /* @__PURE__ */ jsx(
2721
+ ThemeIcon,
2722
+ {
2723
+ size: "xl",
2724
+ variant: "light",
2725
+ color: "orange",
2726
+ radius: "md",
2727
+ children: /* @__PURE__ */ jsx(IconAlertTriangle, {})
2728
+ }
2729
+ ) })
2730
+ },
2731
+ "empty"
2732
+ ),
2733
+ hasNotations && isVisible && isCollapsed && /* @__PURE__ */ jsx(
2734
+ m.div,
2735
+ {
2736
+ initial: { opacity: 0.75, translateX: "50%" },
2737
+ animate: { opacity: 1, translateX: 0 },
2738
+ exit: {
2739
+ translateX: "100%",
2740
+ opacity: 0.6
2741
+ },
2742
+ className: clsx("react-flow__panel", container),
2743
+ children: /* @__PURE__ */ jsx(Tooltip$2, { label: "Show notation", color: "dark", fz: "xs", ...portalProps, children: /* @__PURE__ */ jsx(
2744
+ ActionIcon$2,
2745
+ {
2746
+ size: "xl",
2747
+ variant: "light",
2748
+ color: "gray",
2749
+ radius: "md",
2750
+ onClick: () => setCollapsed(!1),
2751
+ children: /* @__PURE__ */ jsx(IconHelpCircle, { stroke: 1.5 })
2752
+ }
2753
+ ) })
2754
+ },
2755
+ "collapsed"
2756
+ ),
2757
+ hasNotations && isVisible && !isCollapsed && /* @__PURE__ */ jsx(
2758
+ m.div,
2759
+ {
2760
+ initial: {
2761
+ opacity: 0.75,
2762
+ // translateX: '50%',
2763
+ scale: 0.2
2764
+ },
2765
+ animate: { opacity: 1, scale: 1 },
2766
+ exit: {
2767
+ opacity: 0,
2768
+ scale: 0.25
2769
+ },
2770
+ className: clsx("react-flow__panel", container),
2771
+ style: {
2772
+ transformOrigin: "bottom right"
2773
+ },
2774
+ children: /* @__PURE__ */ jsx(
2775
+ Paper,
2776
+ {
2777
+ radius: "sm",
2778
+ withBorder: !0,
2779
+ shadow: "lg",
2780
+ className: card,
2781
+ children: /* @__PURE__ */ jsxs(Tabs, { defaultValue: "first", radius: "xs", children: [
2782
+ /* @__PURE__ */ jsxs(TabsList, { children: [
2783
+ /* @__PURE__ */ jsx(
2784
+ ActionIcon$2,
2785
+ {
2786
+ size: "md",
2787
+ variant: "subtle",
2788
+ color: "gray",
2789
+ ml: 2,
2790
+ style: {
2791
+ alignSelf: "center"
2792
+ },
2793
+ onClick: () => setCollapsed(!0),
2794
+ children: /* @__PURE__ */ jsx(IconArrowDownRight, { stroke: 2 })
2795
+ }
2796
+ ),
2797
+ /* @__PURE__ */ jsx(TabsTab, { value: "first", fz: "xs", children: "Elements" }),
2798
+ /* @__PURE__ */ jsx(TabsTab, { value: "second", fz: "xs", disabled: !0, children: "Relationships" })
2799
+ ] }),
2800
+ /* @__PURE__ */ jsx(TabsPanel, { value: "first", className: tabPanel, hidden: isCollapsed, children: /* @__PURE__ */ jsx(
2801
+ ScrollAreaAutosize,
2802
+ {
2803
+ viewportProps: {
2804
+ style: {
2805
+ maxHeight: height > 50 ? `min(40vh, ${height - 20}px)` : "40vh"
2806
+ }
2807
+ },
2808
+ children: /* @__PURE__ */ jsx(Stack, { gap: 0, children: notations.map((n, i) => /* @__PURE__ */ jsx(ElementNotation, { value: n }, i)) })
2809
+ }
2810
+ ) })
2811
+ ] })
2812
+ }
2813
+ )
2814
+ },
2815
+ id
2816
+ )
2817
+ ] });
2818
+ }
2565
2819
  const {
2566
2820
  primary,
2567
2821
  secondary,
@@ -3092,19 +3346,19 @@ const ActionIcon = ActionIcon$2.withProps({
3092
3346
  showFitDiagram,
3093
3347
  showLayoutDriftWarning,
3094
3348
  showChangeAutoLayout,
3095
- portalProps: _portalProps,
3349
+ target,
3096
3350
  showGoToSource
3097
3351
  } = useDiagramState((s) => {
3098
- const isNotActive = s.activeDynamicViewStep === null && s.focusedNodeId === null, target = s.getContainer();
3352
+ const isNotActive = s.activeDynamicViewStep === null && s.focusedNodeId === null;
3099
3353
  return {
3100
3354
  showNavigationButtons: !!s.onBurgerMenuClick || s.showNavigationButtons && !!s.onNavigateTo,
3101
3355
  showFitDiagram: s.fitViewEnabled && s.zoomable && s.viewportChanged,
3102
3356
  showLayoutDriftWarning: s.readonly !== !0 && s.view.hasLayoutDrift === !0 && isNotActive,
3103
3357
  showChangeAutoLayout: s.readonly !== !0 && !!s.onChange && isNotActive,
3104
3358
  showGoToSource: !!s.onOpenSourceView,
3105
- portalProps: target ? { target } : void 0
3359
+ target: s.getContainer()
3106
3360
  };
3107
- }), portalProps = _portalProps ? { portalProps: _portalProps } : void 0;
3361
+ }), portalProps = target ? { portalProps: { target } } : void 0;
3108
3362
  return /* @__PURE__ */ jsxs(
3109
3363
  Stack,
3110
3364
  {
@@ -3185,7 +3439,7 @@ function literalToEnum(value) {
3185
3439
  }
3186
3440
  function XYFlowBackground({ background }) {
3187
3441
  if (typeof background == "string")
3188
- return /* @__PURE__ */ jsx(Background, { variant: literalToEnum(background) });
3442
+ return /* @__PURE__ */ jsx(Background, { variant: literalToEnum(background), size: 2, gap: 20 });
3189
3443
  const { variant, ...rest } = background;
3190
3444
  return /* @__PURE__ */ jsx(Background, { variant: literalToEnum(variant), ...rest });
3191
3445
  }
@@ -3196,11 +3450,13 @@ const XYFlowInner = memo(function({
3196
3450
  showDiagramTitle
3197
3451
  }) {
3198
3452
  const {
3453
+ showNotations,
3199
3454
  isDynamicView,
3200
3455
  isDynamicViewActive,
3201
3456
  readonly,
3202
3457
  enableDynamicViewWalkthrough
3203
3458
  } = useDiagramState((s) => ({
3459
+ showNotations: s.showNotations,
3204
3460
  isDynamicView: s.isDynamicView,
3205
3461
  readonly: s.readonly,
3206
3462
  enableDynamicViewWalkthrough: s.enableDynamicViewWalkthrough,
@@ -3213,6 +3469,7 @@ const XYFlowInner = memo(function({
3213
3469
  readonly !== !0 && /* @__PURE__ */ jsx(OptionsPanel, {}),
3214
3470
  showDiagramTitle === !0 && !isDynamicViewActive && /* @__PURE__ */ jsx(DiagramTitlePanel, {}),
3215
3471
  isDynamicView && enableDynamicViewWalkthrough && /* @__PURE__ */ jsx(DynamicViewWalkthrough, {}),
3472
+ showNotations && /* @__PURE__ */ jsx(NotationPanel, {}),
3216
3473
  children
3217
3474
  ] });
3218
3475
  }, shallowEqual);
@@ -3231,6 +3488,7 @@ function LikeC4Diagram({
3231
3488
  controls = !1,
3232
3489
  showElementLinks = !0,
3233
3490
  showDiagramTitle = !0,
3491
+ showNotations = !0,
3234
3492
  enableDynamicViewWalkthrough = !1,
3235
3493
  enableFocusMode = !1,
3236
3494
  initialWidth,
@@ -3278,6 +3536,7 @@ function LikeC4Diagram({
3278
3536
  fitViewPadding,
3279
3537
  showElementLinks,
3280
3538
  showNavigationButtons,
3539
+ showNotations,
3281
3540
  nodesDraggable,
3282
3541
  nodesSelectable,
3283
3542
  experimentalEdgeEditing,
@@ -3375,6 +3634,7 @@ function StaticLikeC4Diagram({
3375
3634
  background,
3376
3635
  showElementLinks: !0,
3377
3636
  showDiagramTitle: !1,
3637
+ showNotations: !1,
3378
3638
  enableDynamicViewWalkthrough: !1,
3379
3639
  enableFocusMode: !1,
3380
3640
  nodesSelectable: !1,