@likec4/diagram 1.44.0 → 1.45.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 (134) hide show
  1. package/bundle/index.js +18160 -17671
  2. package/dist/LikeC4Diagram.js +7 -5
  3. package/dist/LikeC4Diagram.props.d.ts +9 -8
  4. package/dist/LikeC4Styles.js +0 -3
  5. package/dist/LikeC4View.d.ts +3 -3
  6. package/dist/LikeC4View.js +7 -3
  7. package/dist/ReactLikeC4.d.ts +2 -2
  8. package/dist/base/Base.d.ts +1 -1
  9. package/dist/base/updateEdges.js +25 -15
  10. package/dist/base/updateNodes.d.ts +14 -0
  11. package/dist/base/updateNodes.js +51 -18
  12. package/dist/base-primitives/Markdown.d.ts +30 -4
  13. package/dist/base-primitives/Markdown.js +3 -2
  14. package/dist/base-primitives/compound/CompoundActionButton.d.ts +3 -2
  15. package/dist/base-primitives/compound/CompoundDetailsButton.d.ts +3 -2
  16. package/dist/base-primitives/edge/EdgeActionButton.d.ts +3 -2
  17. package/dist/base-primitives/edge/EdgeContainer.d.ts +1 -1
  18. package/dist/base-primitives/edge/EdgeContainer.js +4 -1
  19. package/dist/base-primitives/edge/EdgeLabel.d.ts +2 -2
  20. package/dist/base-primitives/edge/EdgePath.js +24 -4
  21. package/dist/base-primitives/element/ElementActionButtons.d.ts +3 -2
  22. package/dist/base-primitives/element/ElementActionButtons.js +2 -2
  23. package/dist/base-primitives/element/ElementData.d.ts +3 -3
  24. package/dist/base-primitives/element/ElementData.js +13 -7
  25. package/dist/base-primitives/element/ElementDetailsButton.d.ts +3 -2
  26. package/dist/base-primitives/element/ElementDetailsButton.js +3 -0
  27. package/dist/base-primitives/element/ElementShape.js +5 -5
  28. package/dist/base-primitives/element/ElementTags.d.ts +6 -4
  29. package/dist/context/DiagramFeatures.d.ts +1 -0
  30. package/dist/context/TagStylesContext.js +1 -1
  31. package/dist/custom/hooks.d.ts +2 -2
  32. package/dist/custom/index.js +1 -1
  33. package/dist/hooks/index.d.ts +2 -1
  34. package/dist/hooks/useDiagramCompareLayout.js +7 -2
  35. package/dist/hooks/useLikeC4Project.d.ts +6 -1
  36. package/dist/hooks/useLikeC4Project.js +14 -2
  37. package/dist/hooks/useMediaPrint.d.ts +6 -0
  38. package/dist/likec4diagram/DiagramXYFlow.js +11 -6
  39. package/dist/likec4diagram/custom/edges/EdgeDrifts.d.ts +6 -0
  40. package/dist/likec4diagram/custom/edges/EdgeDrifts.js +38 -0
  41. package/dist/likec4diagram/custom/edges/RelationshipEdge.js +24 -14
  42. package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +3 -1
  43. package/dist/likec4diagram/custom/edges/useControlPoints.js +3 -3
  44. package/dist/likec4diagram/custom/edges/useRelationshipEdgePath.js +5 -5
  45. package/dist/likec4diagram/custom/nodes/NodeDrifts.d.ts +6 -0
  46. package/dist/likec4diagram/custom/nodes/{ElementNodeDrifts.js → NodeDrifts.js} +8 -4
  47. package/dist/likec4diagram/custom/nodes/SequenceActorNode.js +4 -2
  48. package/dist/likec4diagram/custom/nodes/index.d.ts +1 -0
  49. package/dist/likec4diagram/custom/nodes/nodes.js +64 -23
  50. package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +3 -3
  51. package/dist/likec4diagram/relationship-popover/actor.d.ts +1 -1
  52. package/dist/likec4diagram/state/DiagramActorProvider.d.ts +2 -2
  53. package/dist/likec4diagram/state/DiagramActorProvider.js +0 -1
  54. package/dist/likec4diagram/state/assign.d.ts +10 -6
  55. package/dist/likec4diagram/state/assign.js +17 -192
  56. package/dist/likec4diagram/state/createViewChange.js +15 -26
  57. package/dist/likec4diagram/state/diagram-api.d.ts +2 -6
  58. package/dist/likec4diagram/state/diagram-api.js +2 -10
  59. package/dist/likec4diagram/state/hotkeyActor.d.ts +0 -7
  60. package/dist/likec4diagram/state/hotkeyActor.js +1 -17
  61. package/dist/likec4diagram/state/machine.actions.d.ts +430 -746
  62. package/dist/likec4diagram/state/machine.actions.js +193 -284
  63. package/dist/likec4diagram/state/machine.actions.layout.d.ts +579 -0
  64. package/dist/likec4diagram/state/machine.actions.layout.js +129 -0
  65. package/dist/likec4diagram/state/machine.d.ts +17 -3712
  66. package/dist/likec4diagram/state/machine.js +59 -73
  67. package/dist/likec4diagram/state/machine.setup.d.ts +61 -18
  68. package/dist/likec4diagram/state/machine.setup.js +27 -5
  69. package/dist/likec4diagram/state/machine.state.initializing.d.ts +25 -0
  70. package/dist/likec4diagram/state/machine.state.initializing.js +5 -5
  71. package/dist/likec4diagram/state/machine.state.navigating.d.ts +5 -108
  72. package/dist/likec4diagram/state/machine.state.navigating.js +167 -34
  73. package/dist/likec4diagram/state/machine.state.ready.d.ts +2316 -2851
  74. package/dist/likec4diagram/state/machine.state.ready.focused.d.ts +386 -0
  75. package/dist/likec4diagram/state/machine.state.ready.focused.js +85 -0
  76. package/dist/likec4diagram/state/machine.state.ready.idle.d.ts +474 -0
  77. package/dist/likec4diagram/state/machine.state.ready.idle.js +96 -0
  78. package/dist/likec4diagram/state/machine.state.ready.js +144 -319
  79. package/dist/likec4diagram/state/machine.state.ready.printing.d.ts +128 -0
  80. package/dist/likec4diagram/state/machine.state.ready.printing.js +40 -0
  81. package/dist/likec4diagram/state/machine.state.ready.walkthrough.d.ts +511 -0
  82. package/dist/likec4diagram/state/machine.state.ready.walkthrough.js +177 -0
  83. package/dist/likec4diagram/state/mediaPrintActor.d.ts +12 -0
  84. package/dist/likec4diagram/state/mediaPrintActor.js +18 -0
  85. package/dist/likec4diagram/state/syncManualLayoutActor.d.ts +6 -240
  86. package/dist/likec4diagram/state/syncManualLayoutActor.js +150 -89
  87. package/dist/likec4diagram/state/types.d.ts +5 -7
  88. package/dist/likec4diagram/state/utils.d.ts +15 -2
  89. package/dist/likec4diagram/state/utils.js +43 -3
  90. package/dist/likec4diagram/types.d.ts +8 -13
  91. package/dist/likec4diagram/ui/notation/NotationPanel.css.js +5 -2
  92. package/dist/likec4diagram/useLayoutConstraints.js +107 -102
  93. package/dist/likec4diagram/xyflow-diagram/diagram-view.js +12 -15
  94. package/dist/likec4diagram/xyflow-sequence/sequence-layout.js +11 -8
  95. package/dist/navigationpanel/NavigationPanel.js +5 -1
  96. package/dist/navigationpanel/actor.d.ts +4 -204
  97. package/dist/navigationpanel/actor.js +1 -1
  98. package/dist/navigationpanel/controls/ToggleReadonly.js +8 -3
  99. package/dist/navigationpanel/walkthrough/DynamicViewControls.d.ts +66 -66
  100. package/dist/navigationpanel/walkthrough/WalkthroughPanel.js +4 -3
  101. package/dist/overlays/element-details/ElementDetailsCard.css.js +3 -3
  102. package/dist/overlays/element-details/MetadataValue.d.ts +2 -1
  103. package/dist/overlays/element-details/actor.d.ts +8 -83
  104. package/dist/overlays/element-details/actor.js +0 -8
  105. package/dist/overlays/overlay/Overlay.js +10 -10
  106. package/dist/overlays/overlaysActor.d.ts +7 -392
  107. package/dist/overlays/overlaysActor.js +67 -46
  108. package/dist/overlays/relationship-details/_types.d.ts +3 -4
  109. package/dist/overlays/relationship-details/actor.d.ts +6 -562
  110. package/dist/overlays/relationship-details/actor.js +1 -0
  111. package/dist/overlays/relationship-details/layout-to-xyflow.js +3 -5
  112. package/dist/overlays/relationship-details/layout.d.ts +3 -3
  113. package/dist/overlays/relationship-details/layout.js +4 -4
  114. package/dist/overlays/relationships-browser/_types.d.ts +2 -3
  115. package/dist/overlays/relationships-browser/actor.d.ts +7 -739
  116. package/dist/overlays/relationships-browser/layout.d.ts +2 -2
  117. package/dist/overlays/relationships-browser/layout.js +3 -3
  118. package/dist/overlays/relationships-browser/useViewToNodesEdges.js +0 -3
  119. package/dist/overlays/types.d.ts +12 -13
  120. package/dist/search/components/_shared.css.d.ts +1 -1
  121. package/dist/search/searchActor.d.ts +3 -66
  122. package/dist/shadowroot/ShadowRoot.d.ts +5 -7
  123. package/dist/shadowroot/ShadowRoot.js +7 -5
  124. package/dist/styles.css2.js +1 -1
  125. package/dist/utils/vector.d.ts +5 -0
  126. package/dist/utils/vector.js +7 -0
  127. package/dist/utils/view-bounds.js +7 -6
  128. package/dist/utils/xyflow.d.ts +4 -2
  129. package/dist/utils/xyflow.js +18 -50
  130. package/package.json +19 -18
  131. package/panda.buildinfo.json +17 -5
  132. package/styles-min.css +1 -1
  133. package/styles.css +1 -1
  134. package/dist/likec4diagram/custom/nodes/ElementNodeDrifts.d.ts +0 -4
@@ -1,6 +1,7 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { useCustomCompareMemo } from '@react-hookz/web';
3
3
  import { ReactFlowProvider } from '@xyflow/react';
4
+ import { deepEqual } from 'fast-equals';
4
5
  import { useRef } from 'react';
5
6
  import { isPlainObject, mapValues } from 'remeda';
6
7
  import { RootContainer } from './components/RootContainer.js';
@@ -179,14 +180,15 @@ function useNormalizedViewPadding(raw) {
179
180
  if (isPlainObject(raw)) {
180
181
  return mapValues(raw, toLiteralPaddingWithUnit);
181
182
  }
182
- return toLiteralPaddingWithUnit(raw);
183
+ const v = toLiteralPaddingWithUnit(raw);
184
+ return {
185
+ x: v,
186
+ y: v
187
+ };
183
188
  },
184
189
  [raw],
185
- compareViewPaddingObject
190
+ deepEqual
186
191
  );
187
192
  }
188
- const compareViewPaddingObject = ([a], [b]) => {
189
- return a === b || isPlainObject(a) && isPlainObject(b) && a.bottom == b.bottom && a.left == b.left && a.right == b.right && a.top == b.top;
190
- };
191
193
 
192
194
  export { LikeC4Diagram };
@@ -29,6 +29,14 @@ export type LikeC4ColorScheme = 'light' | 'dark';
29
29
  export type OverrideReactFlowProps = Pick<ReactFlowProps<Types.AnyNode, Types.AnyEdge>, 'paneClickDistance' | 'nodeClickDistance' | 'selectionKeyCode' | 'panActivationKeyCode' | 'multiSelectionKeyCode' | 'zoomActivationKeyCode' | 'snapToGrid' | 'snapGrid' | 'onlyRenderVisibleElements' | 'nodesDraggable' | 'nodesFocusable' | 'elementsSelectable' | 'selectNodesOnDrag' | 'panOnDrag' | 'preventScrolling' | 'zoomOnScroll' | 'zoomOnPinch' | 'panOnScroll' | 'panOnScrollSpeed' | 'panOnScrollMode' | 'zoomOnDoubleClick' | 'nodeDragThreshold'>;
30
30
  export type PaddingUnit = 'px' | '%';
31
31
  export type PaddingWithUnit = `${number}${PaddingUnit}` | number;
32
+ export type ViewPaddings = {
33
+ top?: PaddingWithUnit;
34
+ right?: PaddingWithUnit;
35
+ bottom?: PaddingWithUnit;
36
+ left?: PaddingWithUnit;
37
+ x?: PaddingWithUnit;
38
+ y?: PaddingWithUnit;
39
+ };
32
40
  /**
33
41
  * Padding around the diagram
34
42
  *
@@ -45,14 +53,7 @@ export type PaddingWithUnit = `${number}${PaddingUnit}` | number;
45
53
  * y: '16px',
46
54
  * }
47
55
  */
48
- export type ViewPadding = PaddingWithUnit | {
49
- top?: PaddingWithUnit;
50
- right?: PaddingWithUnit;
51
- bottom?: PaddingWithUnit;
52
- left?: PaddingWithUnit;
53
- x?: PaddingWithUnit;
54
- y?: PaddingWithUnit;
55
- };
56
+ export type ViewPadding = PaddingWithUnit | ViewPaddings;
56
57
  export interface LikeC4DiagramProperties<A extends Any = Unknown> {
57
58
  view: LayoutedView<A>;
58
59
  className?: string | undefined;
@@ -7,7 +7,6 @@ import { useLikeC4Styles } from './hooks/useLikeC4Styles.js';
7
7
 
8
8
  const scheme = (scheme2) => `[data-mantine-color-scheme="${scheme2}"]`;
9
9
  const whenDark = scheme("dark");
10
- const whenLight = scheme("light");
11
10
  const MAX_DEPTH = 5;
12
11
  const generateCompoundColors = (rootSelector, name, colors, depth) => {
13
12
  const compoundDarkColor = (color) => toHex(
@@ -47,8 +46,6 @@ ${selector} {
47
46
  --likec4-palette-relation-stroke: ${relationships.line};
48
47
  --likec4-palette-relation-label: ${relationships.label};
49
48
  --likec4-palette-relation-label-bg: ${relationships.labelBg};
50
- }
51
- ${whenLight} ${selector} {
52
49
  --likec4-palette-relation-stroke-selected: ${toRgba(mix(relationships.line, "black", 85))};
53
50
  }
54
51
  ${whenDark} ${selector} {
@@ -1,4 +1,4 @@
1
- import { CSSProperties } from 'react';
1
+ import { CSSProperties, ReactNode } from 'react';
2
2
  import { ElementIconRenderer, NodeRenderers, OverrideReactFlowProps, PaddingWithUnit, ViewPadding } from './LikeC4Diagram.props';
3
3
  import type * as t from '@likec4/core/types';
4
4
  export interface LikeC4ViewProps<A extends t.aux.Any = t.aux.UnknownLayouted> {
@@ -159,7 +159,7 @@ export interface LikeC4ViewProps<A extends t.aux.Any = t.aux.UnknownLayouted> {
159
159
  /**
160
160
  * Children to render inside the diagram (not inside the browser overlay)
161
161
  */
162
- children?: React.ReactNode | undefined;
162
+ children?: ReactNode | undefined;
163
163
  }
164
164
  export interface LikeC4BrowserProps {
165
165
  /**
@@ -250,7 +250,7 @@ export interface LikeC4BrowserProps {
250
250
  /**
251
251
  * Children to render inside the browser overlay
252
252
  */
253
- children?: React.ReactNode | undefined;
253
+ children?: ReactNode | undefined;
254
254
  }
255
255
  /**
256
256
  * Ready-to-use component to display embedded LikeC4 view,
@@ -2,7 +2,8 @@ import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { cx, css } from '@likec4/styles/css';
3
3
  import { Box } from '@likec4/styles/jsx';
4
4
  import { ActionIcon } from '@mantine/core';
5
- import { useState } from 'react';
5
+ import { AnimatePresence } from 'motion/react';
6
+ import { useRef, useState } from 'react';
6
7
  import { isBoolean } from 'remeda';
7
8
  import { FitViewPaddings } from './base/const.js';
8
9
  import { ErrorMessage, ViewNotFound } from './components/ViewNotFound.js';
@@ -49,6 +50,7 @@ function LikeC4View({
49
50
  children,
50
51
  ...props
51
52
  }) {
53
+ const rootRef = useRef(null);
52
54
  const likec4model = useOptionalLikeC4Model();
53
55
  const [layoutType, setLayoutType] = useState("manual");
54
56
  const [browserViewId, _onNavigateTo] = useState(null);
@@ -82,9 +84,11 @@ function LikeC4View({
82
84
  const isBrowserEnabled = browser !== false;
83
85
  const browserProps = isBoolean(browser) ? {} : browser;
84
86
  const bounds = pickViewBounds(view, props.dynamicViewVariant);
87
+ const root = rootRef.current ? { root: rootRef.current } : void 0;
85
88
  return /* @__PURE__ */ jsx(
86
89
  ShadowRoot,
87
90
  {
91
+ ref: rootRef,
88
92
  injectFontCss,
89
93
  theme: mantineTheme,
90
94
  colorScheme,
@@ -133,7 +137,7 @@ function LikeC4View({
133
137
  ...props
134
138
  }
135
139
  ),
136
- browserView && /* @__PURE__ */ jsxs(Overlay, { openDelay: 0, onClose: () => onNavigateTo(null), children: [
140
+ /* @__PURE__ */ jsx(AnimatePresence, { ...root, children: browserView && /* @__PURE__ */ jsxs(Overlay, { openDelay: 0, onClose: () => onNavigateTo(null), children: [
137
141
  /* @__PURE__ */ jsx(
138
142
  LikeC4Diagram,
139
143
  {
@@ -175,7 +179,7 @@ function LikeC4View({
175
179
  children: /* @__PURE__ */ jsx(IconX, {})
176
180
  }
177
181
  ) })
178
- ] })
182
+ ] }) })
179
183
  ] })
180
184
  }
181
185
  );
@@ -1,4 +1,4 @@
1
- import { CSSProperties } from 'react';
1
+ import { CSSProperties, ReactNode } from 'react';
2
2
  import { Except } from 'type-fest';
3
3
  import { LikeC4DiagramEventHandlers, LikeC4DiagramProperties } from './LikeC4Diagram.props';
4
4
  import type * as t from '@likec4/core/types';
@@ -26,7 +26,7 @@ export type ReactLikeC4Props<A extends t.aux.Any = t.aux.UnknownLayouted> = {
26
26
  mantineTheme?: any;
27
27
  /** Function to generate nonce attribute added to all generated `<style />` tags */
28
28
  styleNonce?: string | (() => string) | undefined;
29
- children?: React.ReactNode | undefined;
29
+ children?: ReactNode | undefined;
30
30
  } & Except<LikeC4DiagramProperties<A>, 'view', {
31
31
  requireExactProps: true;
32
32
  }> & LikeC4DiagramEventHandlers<A>;
@@ -16,7 +16,7 @@ type WithData<D> = {
16
16
  data: D;
17
17
  };
18
18
  declare function setData<E extends WithData<any>>(value: E, state: Partial<E['data']>): E;
19
- declare function setData<E extends WithData<any>>(state: Partial<E['data']>): (value: E) => E;
19
+ declare function setData<E extends WithData<any>>(state: Partial<NoInfer<E['data']>>): (value: E) => E;
20
20
  export declare const Base: {
21
21
  setDimmed: typeof setDimmed;
22
22
  setHovered: typeof setHovered;
@@ -1,22 +1,32 @@
1
1
  import { deepEqual } from 'fast-equals';
2
- import { isDefined, omit } from 'remeda';
2
+ import { isDefined, pickBy, isShallowEqual } from 'remeda';
3
3
 
4
- function _update(current, update) {
5
- return update.map((next) => {
6
- const existing = current.find((n) => n.id === next.id);
7
- if (existing && existing.type === next.type && existing.source === next.source && existing.target === next.target) {
8
- const isSameData = deepEqual(existing.data, next.data);
9
- if (isSameData && deepEqual(existing.hidden ?? false, next.hidden ?? false) && deepEqual(existing.selected ?? false, next.selected ?? false) && deepEqual(existing.animated ?? false, next.animated ?? false) && deepEqual(existing.sourceHandle ?? null, next.sourceHandle ?? null) && deepEqual(existing.targetHandle ?? null, next.targetHandle ?? null) && deepEqual(existing.zIndex ?? 0, next.zIndex ?? 0) && deepEqual(existing.label ?? null, next.label ?? null)) {
10
- return existing;
11
- }
12
- return {
13
- ...omit(existing, ["hidden", "zIndex"]),
14
- ...next,
15
- data: isSameData ? existing.data : next.data
16
- };
4
+ const EMPTY_OBJ = {};
5
+ function _update(current, updated) {
6
+ if (current === updated) {
7
+ return current;
8
+ }
9
+ updated = updated.map((update) => {
10
+ const existing = current.find(
11
+ (n) => n.id === update.id && n.type === update.type && n.source === update.source && n.target === update.target
12
+ );
13
+ if (!existing) {
14
+ return update;
15
+ }
16
+ const isSameData = deepEqual(existing.data, update.data);
17
+ const data = isSameData ? existing.data : update.data;
18
+ if (isSameData && deepEqual(existing.hidden, update.hidden ?? existing.hidden) && deepEqual(existing.selected, update.selected ?? existing.selected) && deepEqual(existing.selectable, update.selectable ?? existing.selectable) && deepEqual(existing.focusable, update.focusable ?? existing.focusable) && deepEqual(existing.animated, update.animated ?? existing.animated) && deepEqual(existing.className, update.className) && deepEqual(existing.zIndex, update.zIndex ?? existing.zIndex) && deepEqual(existing.label, update.label) && deepEqual(existing.sourceHandle, update.sourceHandle) && deepEqual(existing.targetHandle, update.targetHandle) && deepEqual(existing.style ?? EMPTY_OBJ, update.style ?? EMPTY_OBJ)) {
19
+ return existing;
17
20
  }
18
- return next;
21
+ return {
22
+ // Retain existing properties that are defined, except parentId
23
+ ...pickBy(existing, isDefined),
24
+ // Apply updates, omitting undefined values
25
+ ...pickBy(update, isDefined),
26
+ data
27
+ };
19
28
  });
29
+ return isShallowEqual(current, updated) ? current : updated;
20
30
  }
21
31
  function updateEdges(current, update) {
22
32
  if (isDefined(update)) {
@@ -1,3 +1,17 @@
1
1
  import { BaseNode } from './types';
2
+ /**
3
+ * Updates nodes by merging existing nodes with updated nodes based on their IDs.
4
+ *
5
+ * Can be used in two ways:
6
+ * 1. By providing both current and updated node arrays:
7
+ * `const newNodes = updateNodes(currentNodes, updatedNodes);`
8
+ * 2. By providing only the updated node array, returning a function that takes the current nodes:
9
+ * `const updater = updateNodes(updatedNodes);
10
+ * const newNodes = updater(currentNodes);`
11
+ *
12
+ * @param current - The current array of nodes.
13
+ * @param update - The array of nodes with updates.
14
+ * @returns The updated array of nodes.
15
+ */
2
16
  export declare function updateNodes<N extends BaseNode>(current: N[], update: N[]): N[];
3
17
  export declare function updateNodes<N extends BaseNode>(update: N[]): (current: N[]) => N[];
@@ -1,34 +1,67 @@
1
1
  import { getNodeDimensions } from '@xyflow/system';
2
2
  import { deepEqual } from 'fast-equals';
3
- import { isDefined, omit } from 'remeda';
3
+ import { isDefined, hasSubObject, pickBy, isShallowEqual } from 'remeda';
4
4
 
5
+ const EMPTY_OBJ = {};
5
6
  function _update(current, updated) {
6
- return updated.map((update) => {
7
- const existing = current.find((n) => n.id === update.id);
8
- if (existing && deepEqual(existing.type, update.type)) {
9
- const isSameData = deepEqual(existing.data, update.data);
10
- const { width: existingWidth, height: existingHeight } = getNodeDimensions(existing);
11
- if (isSameData && deepEqual(existingWidth, update.initialWidth) && deepEqual(existingHeight, update.initialHeight) && deepEqual(existing.parentId ?? null, update.parentId ?? null) && deepEqual(existing.hidden ?? false, update.hidden ?? false) && deepEqual(existing.zIndex ?? 0, update.zIndex ?? 0) && deepEqual(existing.position, update.position)) {
12
- return existing;
7
+ if (current === updated) {
8
+ return current;
9
+ }
10
+ updated = updated.map((update) => {
11
+ const existing = current.find((n) => n.id === update.id && n.type === update.type);
12
+ if (!existing) {
13
+ return update;
14
+ }
15
+ const isSameData = hasSubObject(existing.data, update.data);
16
+ let data = isSameData ? existing.data : update.data;
17
+ if (!isSameData) {
18
+ if (isDefined(existing.data.hovered) && !isDefined(update.data.hovered)) {
19
+ data = {
20
+ ...data,
21
+ hovered: existing.data.hovered
22
+ };
13
23
  }
14
- return {
15
- ...omit(existing, ["measured", "parentId", "hidden", "zIndex"]),
16
- ...update,
17
- // Force dimensions from update
18
- width: update.initialWidth,
19
- height: update.initialHeight,
20
- data: isSameData ? existing.data : update.data
21
- };
24
+ if (isDefined(existing.data.dimmed) && !isDefined(update.data.dimmed)) {
25
+ data = {
26
+ ...data,
27
+ dimmed: existing.data.dimmed
28
+ };
29
+ }
30
+ }
31
+ const { width: existingWidth, height: existingHeight } = getNodeDimensions(existing);
32
+ const haveHandles = Object.hasOwn(existing, "handles") || Object.hasOwn(update, "handles");
33
+ const isSameHandles = !haveHandles || deepEqual(existing.handles ?? [], update.handles ?? []);
34
+ if (isSameData && isSameHandles && deepEqual(existingWidth, update.width ?? update.initialWidth) && deepEqual(existingHeight, update.height ?? update.initialHeight) && deepEqual(existing.parentId ?? null, update.parentId ?? null) && deepEqual(existing.hidden, update.hidden ?? existing.hidden) && deepEqual(existing.selected, update.selected ?? existing.selected) && deepEqual(existing.selectable, update.selectable ?? existing.selectable) && deepEqual(existing.focusable, update.focusable ?? existing.focusable) && deepEqual(existing.draggable, update.draggable ?? existing.draggable) && deepEqual(existing.dragHandle, update.dragHandle) && deepEqual(existing.className, update.className) && deepEqual(existing.zIndex, update.zIndex ?? existing.zIndex) && deepEqual(existing.position, update.position) && deepEqual(existing.domAttributes ?? EMPTY_OBJ, update.domAttributes ?? EMPTY_OBJ) && deepEqual(existing.style ?? EMPTY_OBJ, update.style ?? EMPTY_OBJ)) {
35
+ return existing;
22
36
  }
23
- return update;
37
+ const handles = haveHandles && isSameHandles ? existing.handles : update.handles;
38
+ return {
39
+ // Retain existing properties that are defined, except parentId
40
+ ...pickBy(existing, (v, k) => isDefined(v) && k !== "parentId"),
41
+ // Retain measured dimensions from existing if present
42
+ ..."measured" in existing && {
43
+ measured: {
44
+ width: update.width ?? update.initialWidth,
45
+ height: update.height ?? update.initialHeight
46
+ }
47
+ },
48
+ // Apply updates, omitting undefined values
49
+ ...pickBy(update, isDefined),
50
+ // Force dimensions
51
+ width: update.width ?? update.initialWidth,
52
+ height: update.height ?? update.initialHeight,
53
+ ...handles && { handles },
54
+ data
55
+ };
24
56
  });
57
+ return isShallowEqual(current, updated) ? current : updated;
25
58
  }
26
59
  function updateNodes(current, update) {
27
60
  if (isDefined(update)) {
28
61
  return _update(current, update);
29
62
  }
30
63
  update = current;
31
- return (current2) => _update(current2, update);
64
+ return (existing) => _update(existing, update);
32
65
  }
33
66
 
34
67
  export { updateNodes };
@@ -1,8 +1,7 @@
1
1
  import { RichTextOrEmpty as RichTextType } from '@likec4/core/types';
2
- import { BoxProps } from '@likec4/styles/jsx';
3
2
  import { JsxStyleProps } from '@likec4/styles/types';
4
- import { ForwardRefExoticComponent, RefAttributes } from 'react';
5
- export type MarkdownProps = Omit<BoxProps, 'dangerouslySetInnerHTML' | 'children'> & {
3
+ import { ComponentPropsWithoutRef, ForwardRefExoticComponent, DetailedHTMLProps, HTMLAttributes, RefAttributes } from 'react';
4
+ export type MarkdownProps = Omit<ComponentPropsWithoutRef<'div'>, 'dangerouslySetInnerHTML' | 'children' | 'color'> & {
6
5
  value: RichTextType;
7
6
  /**
8
7
  * When markdown block is used inside a diagram node, this variant should be used to apply the likec4 palette.
@@ -30,4 +29,31 @@ export type MarkdownProps = Omit<BoxProps, 'dangerouslySetInnerHTML' | 'children
30
29
  */
31
30
  emptyText?: string;
32
31
  };
33
- export declare const Markdown: ForwardRefExoticComponent<Omit<MarkdownProps, "ref"> & RefAttributes<HTMLDivElement>>;
32
+ export declare const Markdown: ForwardRefExoticComponent<Omit<Omit< DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "children" | "color" | "dangerouslySetInnerHTML"> & {
33
+ value: RichTextType;
34
+ /**
35
+ * When markdown block is used inside a diagram node, this variant should be used to apply the likec4 palette.
36
+ * @default false
37
+ */
38
+ uselikec4palette?: boolean;
39
+ /**
40
+ * Scale factor for the block
41
+ * @default 1
42
+ */
43
+ textScale?: number;
44
+ /**
45
+ * Font size for the block
46
+ * @default 'md'
47
+ */
48
+ fontSize?: (JsxStyleProps["fontSize"] & string) | undefined;
49
+ /**
50
+ * If true, the component will not render anything if the value is empty.
51
+ * @default false
52
+ */
53
+ hideIfEmpty?: boolean | undefined;
54
+ /**
55
+ * Text to show if the value is empty.
56
+ * @default "no content"
57
+ */
58
+ emptyText?: string;
59
+ } & RefAttributes<HTMLDivElement>>;
@@ -37,8 +37,9 @@ const Markdown = forwardRef(({
37
37
  ...fontSize && {
38
38
  "--text-fz": `var(--font-sizes-${fontSize}, var(--font-sizes-md))`
39
39
  },
40
- // @ts-expect-error
41
- ["--mantine-scale"]: textScale
40
+ ...textScale !== 1 && {
41
+ ["--mantine-scale"]: textScale
42
+ }
42
43
  },
43
44
  ...content
44
45
  }
@@ -1,8 +1,9 @@
1
+ import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
1
2
  import { Simplify } from 'type-fest';
2
3
  import { BaseNodeProps } from '../../base/types';
3
4
  type CompoundActionButtonProps = Simplify<BaseNodeProps & {
4
- icon?: React.ReactNode;
5
- onClick: (e: React.MouseEvent) => void;
5
+ icon?: ReactNode;
6
+ onClick: (e: ReactMouseEvent) => void;
6
7
  }>;
7
8
  export declare function CompoundActionButton({ data: { hovered: isHovered, }, icon, onClick, }: CompoundActionButtonProps): import("react/jsx-runtime").JSX.Element;
8
9
  export {};
@@ -1,8 +1,9 @@
1
+ import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
1
2
  import { Simplify } from 'type-fest';
2
3
  import { BaseNodeProps } from '../../base/types';
3
4
  type CompoundDetailsButtonProps = Simplify<BaseNodeProps & {
4
- icon?: React.ReactNode;
5
- onClick: (e: React.MouseEvent) => void;
5
+ icon?: ReactNode;
6
+ onClick: (e: ReactMouseEvent) => void;
6
7
  }>;
7
8
  export declare function CompoundDetailsButton({ data: { hovered: isHovered, }, icon, onClick, }: CompoundDetailsButtonProps): import("react/jsx-runtime").JSX.Element;
8
9
  export {};
@@ -1,6 +1,7 @@
1
+ import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
1
2
  type EdgeActionBtnProps = {
2
- icon?: React.ReactNode;
3
- onClick: (e: React.MouseEvent) => void;
3
+ icon?: ReactNode;
4
+ onClick: (e: ReactMouseEvent) => void;
4
5
  };
5
6
  export declare function EdgeActionButton({ icon, onClick }: EdgeActionBtnProps): import("react/jsx-runtime").JSX.Element;
6
7
  export {};
@@ -7,5 +7,5 @@ type EdgeContainerProps = PropsWithChildren<BaseEdgePropsWithData<Data> & {
7
7
  component?: 'g' | 'svg' | undefined;
8
8
  className?: string | undefined;
9
9
  }>;
10
- export declare function EdgeContainer({ className, component, selected, data: { color, hovered: isHovered, active: isActive, dimmed: isDimmed, ...data }, children, style, }: EdgeContainerProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function EdgeContainer({ className, component, selectable, selected, data: { color, hovered: isHovered, active: isActive, dimmed: isDimmed, ...data }, children, style, }: EdgeContainerProps): import("react/jsx-runtime").JSX.Element;
11
11
  export {};
@@ -6,6 +6,7 @@ import { edgeContainer } from './edge.css.js';
6
6
  function EdgeContainer({
7
7
  className,
8
8
  component = "g",
9
+ selectable = false,
9
10
  selected = false,
10
11
  data: {
11
12
  color = "gray",
@@ -21,7 +22,9 @@ function EdgeContainer({
21
22
  className: cx(
22
23
  className,
23
24
  edgeContainer,
24
- "likec4-edge-container"
25
+ "likec4-edge-container",
26
+ selected && "selected",
27
+ selectable && "selectable"
25
28
  ),
26
29
  "data-likec4-color": color,
27
30
  "data-edge-dir": data.dir ?? "forward",
@@ -1,11 +1,11 @@
1
1
  import { DiagramEdge } from '@likec4/core/types';
2
2
  import { HTMLMotionProps } from 'motion/react';
3
+ import { ReactNode, ForwardRefExoticComponent, RefAttributes } from 'react';
3
4
  import { UndefinedOnPartialDeep } from 'type-fest';
4
5
  import { BaseEdgePropsWithData } from '../../base/types';
5
- import { ForwardRefExoticComponent, RefAttributes } from 'react';
6
6
  type Data = UndefinedOnPartialDeep<Pick<DiagramEdge, 'label' | 'technology'>>;
7
7
  type EdgeLabelProps = HTMLMotionProps<'div'> & {
8
- children?: React.ReactNode;
8
+ children?: ReactNode;
9
9
  edgeProps: BaseEdgePropsWithData<Data>;
10
10
  pointerEvents?: 'all' | 'none';
11
11
  };
@@ -39,7 +39,7 @@ const EdgePath = forwardRef(({
39
39
  strokeDasharray = "8,10";
40
40
  }
41
41
  return /* @__PURE__ */ jsxs(Fragment, { children: [
42
- selectable && !isDragging && /* @__PURE__ */ jsx(
42
+ selectable && /* @__PURE__ */ jsx(
43
43
  "path",
44
44
  {
45
45
  className: cx(
@@ -53,22 +53,42 @@ const EdgePath = forwardRef(({
53
53
  style: {
54
54
  strokeWidth: interactionWidth ?? 10,
55
55
  stroke: "currentcolor",
56
- strokeOpacity: 0
56
+ strokeOpacity: 0,
57
+ ...isDragging ? { display: "none" } : {}
57
58
  }
58
59
  }
59
60
  ),
61
+ /* @__PURE__ */ jsx(
62
+ "circle",
63
+ {
64
+ className: cx(
65
+ "edge-center-point",
66
+ css({
67
+ visibility: "hidden",
68
+ offsetDistance: "50%"
69
+ })
70
+ ),
71
+ style: {
72
+ offsetPath: `path("${svgPath}")`
73
+ },
74
+ cx: 0,
75
+ cy: 0,
76
+ r: 2
77
+ }
78
+ ),
60
79
  /* @__PURE__ */ jsxs("g", { className: markerContext, onPointerDown: onEdgePointerDown, children: [
61
80
  /* @__PURE__ */ jsxs("defs", { children: [
62
81
  MarkerStart && /* @__PURE__ */ jsx(MarkerStart, { id: "start" + id }),
63
82
  MarkerEnd && /* @__PURE__ */ jsx(MarkerEnd, { id: "end" + id })
64
83
  ] }),
65
- !isDragging && /* @__PURE__ */ jsx(
84
+ /* @__PURE__ */ jsx(
66
85
  "path",
67
86
  {
68
87
  className: cx(
69
88
  "react-flow__edge-path",
70
89
  "hide-on-reduced-graphics",
71
- edgePathBg
90
+ edgePathBg,
91
+ isDragging && css({ display: "none" })
72
92
  ),
73
93
  d: svgPath,
74
94
  style,
@@ -1,3 +1,4 @@
1
+ import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
1
2
  type ElementActionButtonsProps = {
2
3
  selected?: boolean;
3
4
  data: {
@@ -34,8 +35,8 @@ export declare function ElementActionButtons({ selected, data: { hovered: isHove
34
35
  export declare namespace ElementActionButtons {
35
36
  type Item = {
36
37
  key?: string;
37
- icon?: React.ReactNode;
38
- onClick: (e: React.MouseEvent) => void;
38
+ icon?: ReactNode;
39
+ onClick: (e: ReactMouseEvent) => void;
39
40
  };
40
41
  }
41
42
  export {};
@@ -58,6 +58,7 @@ function ElementActionButtons({
58
58
  scale: isHovered ? 1.1 : selected ? 0.9 : 0.8,
59
59
  y: isHovered || selected ? 6 : 0
60
60
  },
61
+ layoutDependency: `${isHovered}-${selected}`,
61
62
  "data-likec4-hovered": isHovered,
62
63
  className: cx("nodrag nopan", actionButtons),
63
64
  children: buttons.map((button, index) => /* @__PURE__ */ jsx(
@@ -76,8 +77,7 @@ function ElementActionButtons({
76
77
  },
77
78
  `${id}-${button.key ?? index}`
78
79
  ))
79
- },
80
- `${id}-action-buttons`
80
+ }
81
81
  )
82
82
  }
83
83
  );
@@ -1,5 +1,5 @@
1
- import { ComputedNodeStyle, NodeId } from '@likec4/core';
2
- import { Color, RichTextOrEmpty } from '@likec4/core/types';
1
+ import { ComputedNodeStyle, MarkdownOrString, NodeId } from '@likec4/core';
2
+ import { Color } from '@likec4/core/types';
3
3
  import { CSSProperties, DetailedHTMLProps, HTMLAttributes } from 'react';
4
4
  type RequiredData = {
5
5
  id: NodeId;
@@ -7,7 +7,7 @@ type RequiredData = {
7
7
  technology?: string | null | undefined;
8
8
  color: Color;
9
9
  style: ComputedNodeStyle;
10
- description?: RichTextOrEmpty;
10
+ description?: MarkdownOrString | null | undefined;
11
11
  icon?: string | null;
12
12
  };
13
13
  type ElementDataProps = {
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { ensureSizes } from '@likec4/core/types';
3
- import { cx } from '@likec4/styles/css';
2
+ import { ensureSizes, RichText } from '@likec4/core/types';
3
+ import { cx, css } from '@likec4/styles/css';
4
4
  import { elementNodeData } from '@likec4/styles/recipes';
5
5
  import { Text } from '@mantine/core';
6
6
  import { forwardRef } from 'react';
@@ -69,9 +69,10 @@ const Technology = forwardRef(({ data, children, className, ...props }, ref) =>
69
69
  ) : null;
70
70
  });
71
71
  const Description = forwardRef(({ data: { description, style }, className, ...props }, ref) => {
72
- if (!description?.nonEmpty) {
72
+ if (!description) {
73
73
  return null;
74
74
  }
75
+ const desc = RichText.from(description);
75
76
  const { size } = ensureSizes(style);
76
77
  const isSm = size === "sm" || size === "xs";
77
78
  return /* @__PURE__ */ jsx(
@@ -80,14 +81,19 @@ const Description = forwardRef(({ data: { description, style }, className, ...pr
80
81
  ...props,
81
82
  className: cx(
82
83
  className,
83
- "likec4-element-description"
84
+ "likec4-element-description",
85
+ css({
86
+ lineClamp: isSm ? 3 : 5
87
+ })
84
88
  ),
85
89
  "data-likec4-node-description": "",
86
- value: description,
90
+ value: desc,
87
91
  uselikec4palette: true,
88
92
  hideIfEmpty: true,
89
- maxHeight: description.isMarkdown ? "8rem " : void 0,
90
- lineClamp: isSm ? 3 : 5,
93
+ style: {
94
+ // Workaround for lineClamp not working with nested TABLE elements (if markdown has tables)
95
+ maxHeight: desc.isMarkdown ? "8rem" : void 0
96
+ },
91
97
  ref
92
98
  }
93
99
  );
@@ -1,9 +1,10 @@
1
+ import { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
1
2
  import { BaseNodeData } from '../../base/types';
2
3
  type ElementDetailsButtonProps = {
3
4
  selected?: boolean;
4
5
  data: BaseNodeData;
5
- icon?: React.ReactNode;
6
- onClick: (e: React.MouseEvent) => void;
6
+ icon?: ReactNode;
7
+ onClick: (e: ReactMouseEvent) => void;
7
8
  };
8
9
  export declare function ElementDetailsButton({ selected, data: { hovered: isHovered, }, icon, onClick, }: ElementDetailsButtonProps): import("react/jsx-runtime").JSX.Element;
9
10
  export {};
@@ -27,6 +27,9 @@ const container = css({
27
27
  },
28
28
  _smallZoom: {
29
29
  display: "none"
30
+ },
31
+ _print: {
32
+ display: "none"
30
33
  }
31
34
  });
32
35
  function ElementDetailsButton({