@likec4/diagram 1.59.1 → 1.59.3
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/README.md +53 -0
- package/dist/LikeC4Styles.js +2 -1
- package/dist/base/BaseXYFlow.js +8 -1
- package/dist/base-primitives/compound/CompoundNodeContainer.js +2 -3
- package/dist/base-primitives/compound/CompoundTitle.js +5 -4
- package/dist/base-primitives/element/ElementData.js +6 -1
- package/dist/base-primitives/element/ElementShape.js +9 -10
- package/dist/context/CurrentViewModelProvider.js +2 -1
- package/dist/context/DiagramEventHandlers.js +2 -1
- package/dist/context/IconRenderer.js +42 -26
- package/dist/context/TagStylesContext.d.ts +2 -1
- package/dist/context/TagStylesContext.js +32 -20
- package/dist/custom/index.js +1 -1
- package/dist/editor/LikeC4EditorCallbacks.d.ts +20 -1
- package/dist/editor/LikeC4EditorCallbacks.js +4 -1
- package/dist/editor/actor/setup.d.ts +1 -0
- package/dist/editor/actor/setup.js +17 -12
- package/dist/editor/actor/state.sync-queue.js +1 -0
- package/dist/editor/applyChangesToManualLayout.js +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/useEditorActorLogic.js +10 -3
- package/dist/hooks/useDiagram.js +4 -2
- package/dist/hooks/useDiagramCompareLayout.d.ts +13 -3
- package/dist/hooks/useDiagramCompareLayout.js +6 -1
- package/dist/hooks/useUpdateEffect.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/likec4diagram/DiagramUI.js +4 -3
- package/dist/likec4diagram/DiagramXYFlow.d.ts +1 -0
- package/dist/likec4diagram/DiagramXYFlow.js +14 -7
- package/dist/likec4diagram/custom/edges/RelationshipEdge.js +1 -3
- package/dist/likec4diagram/custom/edges/SequenceStepEdge.js +1 -3
- package/dist/likec4diagram/custom/edges/useRelationshipEdgePath.js +3 -2
- package/dist/likec4diagram/custom/nodes/nodes.js +6 -3
- package/dist/likec4diagram/custom/nodes/toolbar/Toolbar.js +4 -3
- package/dist/likec4diagram/relationship-popover/RelationshipPopover.js +1 -2
- package/dist/likec4diagram/state/assign.js +1 -1
- package/dist/likec4diagram/state/createViewChange.js +1 -6
- package/dist/likec4diagram/state/machine.actions.js +6 -2
- package/dist/likec4diagram/state/machine.setup.js +9 -8
- package/dist/likec4diagram/state/machine.state.ready.walkthrough.js +1 -1
- package/dist/likec4diagram/state/utils.js +7 -3
- package/dist/likec4diagram/ui/FloatingSequenceActors.js +2 -1
- package/dist/likec4diagram/ui/sequence-outline/SequenceOutlinePanel.js +4 -2
- package/dist/likec4diagram/useLayoutConstraints.js +19 -4
- package/dist/likec4diagram/xyflow-sequence/layouter.js +8 -10
- package/dist/navigationpanel/NavigationPanelDropdown.js +2 -1
- package/dist/navigationpanel/controls/DetailsControls.js +2 -1
- package/dist/navigationpanel/editorpanel/ApplySemanticLayout.js +2 -1
- package/dist/navigationpanel/editorpanel/ChangeAutoLayoutButton.js +2 -1
- package/dist/navigationpanel/editorpanel/ManualLayoutToolsButton.d.ts +2 -0
- package/dist/navigationpanel/editorpanel/ManualLayoutToolsButton.js +11 -3
- package/dist/navigationpanel/walkthrough/DynamicViewControls.js +1 -3
- package/dist/overlays/element-details/ElementDetailsCard.js +2 -2
- package/dist/overlays/overlaysActor.js +16 -17
- package/dist/overlays/relationship-details/RelationshipDetails.js +2 -1
- package/dist/overlays/relationship-details/hooks.js +2 -1
- package/dist/overlays/relationships-browser/RelationshipsBrowser.js +4 -2
- package/dist/overlays/relationships-browser/actor.js +2 -1
- package/dist/overlays/relationships-browser/custom/ElementActions.js +39 -38
- package/dist/overlays/relationships-browser/hooks.js +2 -1
- package/dist/projects-overview/ProjectsOverview.js +8 -7
- package/dist/projects-overview/hooks.js +2 -1
- package/dist/search/components/ViewsColum.js +10 -8
- package/dist/search/hooks.js +2 -1
- package/dist/shadowroot/ShadowRoot.js +4 -2
- package/dist/styles2.js +1 -1
- package/dist/utils/index.js +2 -2
- package/package.json +27 -27
- package/panda.buildinfo.json +2 -1
- package/styles-min.css +1 -1
- package/styles-xyflow.css +1 -1
- package/styles.css +1 -1
package/README.md
CHANGED
|
@@ -272,6 +272,59 @@ function App() {
|
|
|
272
272
|
}
|
|
273
273
|
```
|
|
274
274
|
|
|
275
|
+
### Manual layout controls
|
|
276
|
+
|
|
277
|
+
Use `useDiagramCompareLayout` in a component rendered inside `LikeC4Diagram`
|
|
278
|
+
to place the built-in manual-layout behavior in custom application chrome. The
|
|
279
|
+
hook provides comparison availability and activity, drift reasons, the current
|
|
280
|
+
layout type, whether the latest model can be applied, and the corresponding
|
|
281
|
+
actions.
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { LikeC4Diagram, useDiagramCompareLayout } from '@likec4/diagram'
|
|
285
|
+
|
|
286
|
+
function LayoutControls() {
|
|
287
|
+
const [state, actions] = useDiagramCompareLayout()
|
|
288
|
+
|
|
289
|
+
if (!state.isEnabled) {
|
|
290
|
+
return null
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div>
|
|
295
|
+
<button onClick={() => actions.toggleCompare()}>
|
|
296
|
+
{state.isActive ? 'Stop comparing' : 'Compare with latest'}
|
|
297
|
+
</button>
|
|
298
|
+
<button
|
|
299
|
+
disabled={state.layout === 'auto' ||
|
|
300
|
+
!state.canApplyLatest ||
|
|
301
|
+
!state.hasEditor ||
|
|
302
|
+
!state.isEditable}
|
|
303
|
+
onClick={actions.applyLatestToManual}
|
|
304
|
+
>
|
|
305
|
+
Apply latest
|
|
306
|
+
</button>
|
|
307
|
+
<button onClick={actions.resetManualLayout}>Reset manual layout</button>
|
|
308
|
+
</div>
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function App() {
|
|
313
|
+
return (
|
|
314
|
+
<LikeC4Diagram
|
|
315
|
+
view={view}
|
|
316
|
+
onLayoutTypeChange={setLayoutType}
|
|
317
|
+
>
|
|
318
|
+
<LayoutControls />
|
|
319
|
+
</LikeC4Diagram>
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
The hook reuses the same controller as LikeC4's built-in compare panel. It
|
|
325
|
+
requires comparison to be enabled for the diagram and an editor provider for
|
|
326
|
+
actions that modify a saved manual layout.
|
|
327
|
+
|
|
275
328
|
## Customization
|
|
276
329
|
|
|
277
330
|
### Custom node renderer
|
package/dist/LikeC4Styles.js
CHANGED
|
@@ -47,10 +47,11 @@ function LikeC4Styles({ id }) {
|
|
|
47
47
|
const rootSelector = `#${id}`;
|
|
48
48
|
const nonce = useMantineStyleNonce()?.();
|
|
49
49
|
const $styles = useLikeC4Styles();
|
|
50
|
+
const colorsStyles = useMemo(() => generateBuiltInColorStyles($styles, rootSelector), [rootSelector, $styles.fingerprint]);
|
|
50
51
|
return /* @__PURE__ */ jsx(MemoizedStyle, {
|
|
51
52
|
id,
|
|
52
53
|
nonce,
|
|
53
|
-
colorsStyles
|
|
54
|
+
colorsStyles
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
/**
|
package/dist/base/BaseXYFlow.js
CHANGED
|
@@ -114,6 +114,12 @@ function BaseXYFlow({ nodes, edges, onEdgesChange, onNodesChange, className, pan
|
|
|
114
114
|
onNodesChange,
|
|
115
115
|
onEdgesChange,
|
|
116
116
|
onMoveEnd: useCallbackRef((event, { x, y, zoom }) => {
|
|
117
|
+
/**
|
|
118
|
+
* WORKAROUND
|
|
119
|
+
* Viewport transform is not rounded to integers which results in blurry nodes on some resolution
|
|
120
|
+
* https://github.com/xyflow/xyflow/issues/3282
|
|
121
|
+
* https://github.com/likec4/likec4/issues/734
|
|
122
|
+
*/
|
|
117
123
|
const roundedX = roundDpr(x);
|
|
118
124
|
const roundedY = roundDpr(y);
|
|
119
125
|
if (x !== roundedX || y !== roundedY) xystore.setState({ transform: [
|
|
@@ -190,7 +196,8 @@ function BaseXYFlow({ nodes, edges, onEdgesChange, onNodesChange, className, pan
|
|
|
190
196
|
}
|
|
191
197
|
var selectDimensions = ({ width, height }) => (width || 1) * (height || 1);
|
|
192
198
|
var ViewportResizeHanlder = ({ onViewportResize }) => {
|
|
193
|
-
|
|
199
|
+
const square = useStore(selectDimensions);
|
|
200
|
+
useUpdateEffect(onViewportResize, [square]);
|
|
194
201
|
return null;
|
|
195
202
|
};
|
|
196
203
|
//#endregion
|
|
@@ -13,10 +13,9 @@ function CompoundNodeContainer({ nodeProps: { data: { hovered: isHovered = false
|
|
|
13
13
|
max: 100
|
|
14
14
|
});
|
|
15
15
|
const isTransparent = opacity < 98;
|
|
16
|
-
const
|
|
17
|
-
const borderOpacity = MIN_OPACITY + clamp((100 - MIN_OPACITY) * (opacity / 100), {
|
|
16
|
+
const borderOpacity = 50 + clamp(50 * (opacity / 100), {
|
|
18
17
|
min: 0,
|
|
19
|
-
max:
|
|
18
|
+
max: 50
|
|
20
19
|
});
|
|
21
20
|
const compoundClass = compoundNode({
|
|
22
21
|
isTransparent,
|
|
@@ -3,13 +3,14 @@ import { IconRenderer } from "../../context/IconRenderer.js";
|
|
|
3
3
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/base-primitives/compound/CompoundTitle.tsx
|
|
5
5
|
function CompoundTitle({ data, "aria-hidden": ariaHidden }) {
|
|
6
|
+
const elementIcon = IconRenderer({
|
|
7
|
+
element: data,
|
|
8
|
+
className: "likec4-compound-icon"
|
|
9
|
+
});
|
|
6
10
|
return /* @__PURE__ */ jsxs("div", {
|
|
7
11
|
className: "likec4-compound-title-container",
|
|
8
12
|
"aria-hidden": ariaHidden,
|
|
9
|
-
children: [
|
|
10
|
-
element: data,
|
|
11
|
-
className: "likec4-compound-icon"
|
|
12
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
13
|
+
children: [elementIcon, /* @__PURE__ */ jsx("div", {
|
|
13
14
|
className: "likec4-compound-title",
|
|
14
15
|
children: data.title
|
|
15
16
|
})]
|
|
@@ -22,16 +22,21 @@ var resolveIconColor = (styles, data) => {
|
|
|
22
22
|
const colors = styles.colors(iconColor).elements;
|
|
23
23
|
return iconColor === data.color ? colors.stroke : colors.fill;
|
|
24
24
|
};
|
|
25
|
+
function shouldBlendIconWithPalette(icon, hasIconColor) {
|
|
26
|
+
return !hasIconColor && icon?.startsWith("bootstrap:") === true;
|
|
27
|
+
}
|
|
25
28
|
var Root = forwardRef(({ className, style, data, ...props }, ref) => {
|
|
26
29
|
const styles = useLikeC4Styles();
|
|
27
30
|
const iconSize = data.style.iconSize ? styles.nodeSizes(data.style).values.iconSize : void 0;
|
|
28
31
|
const resolvedIconColor = resolveIconColor(styles, data);
|
|
32
|
+
const hasIconColor = !!resolvedIconColor;
|
|
29
33
|
return /* @__PURE__ */ jsx("div", {
|
|
30
34
|
...props,
|
|
31
35
|
ref,
|
|
32
36
|
className: cx(className, elementNodeData({
|
|
33
37
|
iconPosition: data.style.iconPosition,
|
|
34
|
-
withIconColor:
|
|
38
|
+
withIconColor: hasIconColor,
|
|
39
|
+
withIconBlend: shouldBlendIconWithPalette(data.icon, hasIconColor)
|
|
35
40
|
}), "likec4-element"),
|
|
36
41
|
style: {
|
|
37
42
|
...style,
|
|
@@ -22,7 +22,8 @@ function cylinderSVGPath(diameter, height, tilt = .07) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
function docSVGPath(width, height) {
|
|
25
|
-
const
|
|
25
|
+
const waveHeight = height / 8;
|
|
26
|
+
const baseY = roundDpr(height - waveHeight / 2);
|
|
26
27
|
const amplitude = roundDpr(height / 6);
|
|
27
28
|
const radius = 6;
|
|
28
29
|
return { path: `
|
|
@@ -302,15 +303,13 @@ function ShapeSvgOutline({ shape, w, h }) {
|
|
|
302
303
|
children: /* @__PURE__ */ jsx("path", { d: cylinderSVGPath(w + 6, h + 6).path })
|
|
303
304
|
});
|
|
304
305
|
break;
|
|
305
|
-
default:
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
});
|
|
313
|
-
break;
|
|
306
|
+
default: svg = /* @__PURE__ */ jsx("rect", {
|
|
307
|
+
x: -3,
|
|
308
|
+
y: -3,
|
|
309
|
+
width: w + 6,
|
|
310
|
+
height: h + 6,
|
|
311
|
+
rx: 8
|
|
312
|
+
});
|
|
314
313
|
}
|
|
315
314
|
return /* @__PURE__ */ jsx("g", {
|
|
316
315
|
className: "likec4-shape-outline",
|
|
@@ -5,8 +5,9 @@ import { jsx } from "react/jsx-runtime";
|
|
|
5
5
|
//#region src/context/CurrentViewModelProvider.tsx
|
|
6
6
|
function CurrentViewModelProvider({ children }) {
|
|
7
7
|
const viewId = useCurrentViewId();
|
|
8
|
+
const viewmodel = useOptionalLikeC4Model()?.findView(viewId) ?? null;
|
|
8
9
|
return /* @__PURE__ */ jsx(CurrentViewModelContext, {
|
|
9
|
-
value:
|
|
10
|
+
value: viewmodel,
|
|
10
11
|
children
|
|
11
12
|
});
|
|
12
13
|
}
|
|
@@ -26,13 +26,14 @@ var DiagramEventHandlersReactContext = createContext({
|
|
|
26
26
|
});
|
|
27
27
|
function DiagramEventHandlers({ handlers, children }) {
|
|
28
28
|
const handlersRef = useSyncedRef(handlers);
|
|
29
|
+
const deps = HandlerNames.map((name) => isFunction(handlers[name]));
|
|
29
30
|
const value = useMemo(() => ({
|
|
30
31
|
...mapToObj(HandlerNames, (name) => {
|
|
31
32
|
if (handlersRef.current[name]) return [name, (...args) => handlersRef.current[name]?.(...args)];
|
|
32
33
|
return [name, null];
|
|
33
34
|
}),
|
|
34
35
|
handlersRef
|
|
35
|
-
}), [handlersRef, ...
|
|
36
|
+
}), [handlersRef, ...deps]);
|
|
36
37
|
return /* @__PURE__ */ jsx(DiagramEventHandlersRefContext.Provider, {
|
|
37
38
|
value: handlersRef,
|
|
38
39
|
children: /* @__PURE__ */ jsx(DiagramEventHandlersReactContext.Provider, {
|
|
@@ -26,23 +26,6 @@ function IconRendererProvider({ value, children }) {
|
|
|
26
26
|
children
|
|
27
27
|
});
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Attempts to extract and decode SVG content from a data URL.
|
|
31
|
-
* Returns the decoded SVG string if successful, or null if not an SVG data URL.
|
|
32
|
-
*/
|
|
33
|
-
function decodeSvgDataUrl(dataUrl) {
|
|
34
|
-
if (!dataUrl.startsWith("data:image/svg+xml")) return null;
|
|
35
|
-
try {
|
|
36
|
-
if (dataUrl.includes(";base64,")) {
|
|
37
|
-
const base64Content = dataUrl.split(";base64,")[1];
|
|
38
|
-
if (base64Content) return atob(base64Content);
|
|
39
|
-
} else {
|
|
40
|
-
const encodedContent = dataUrl.split(",")[1];
|
|
41
|
-
if (encodedContent) return decodeURIComponent(encodedContent);
|
|
42
|
-
}
|
|
43
|
-
} catch {}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
29
|
function IconRenderer({ element, className, style }) {
|
|
47
30
|
const RenderIcon = useContext(IconRendererContext);
|
|
48
31
|
if (!element || !element.icon || element.icon === "none") return null;
|
|
@@ -65,9 +48,7 @@ function IconRenderer({ element, className, style }) {
|
|
|
65
48
|
case !!RenderIcon:
|
|
66
49
|
icon = /* @__PURE__ */ jsx(RenderIcon, { node: element });
|
|
67
50
|
break;
|
|
68
|
-
default:
|
|
69
|
-
icon = null;
|
|
70
|
-
break;
|
|
51
|
+
default: icon = null;
|
|
71
52
|
}
|
|
72
53
|
if (!icon) return null;
|
|
73
54
|
return /* @__PURE__ */ jsx("div", {
|
|
@@ -104,14 +85,49 @@ function IconOrShapeRenderer({ element, className, style }) {
|
|
|
104
85
|
style
|
|
105
86
|
});
|
|
106
87
|
}
|
|
107
|
-
function SvgDataUrlIcon({ dataUrl,
|
|
108
|
-
|
|
109
|
-
|
|
88
|
+
function SvgDataUrlIcon({ dataUrl, alt }) {
|
|
89
|
+
if (!useMemo(() => hasCurrentColorReference(dataUrl), [dataUrl])) return /* @__PURE__ */ jsx("img", {
|
|
90
|
+
src: dataUrl,
|
|
91
|
+
alt
|
|
92
|
+
});
|
|
93
|
+
const maskUrl = `url(${JSON.stringify(dataUrl)})`;
|
|
110
94
|
return /* @__PURE__ */ jsx("span", {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
95
|
+
role: alt ? "img" : void 0,
|
|
96
|
+
"aria-label": alt,
|
|
97
|
+
style: {
|
|
98
|
+
display: "inline-block",
|
|
99
|
+
width: "100%",
|
|
100
|
+
height: "100%",
|
|
101
|
+
backgroundColor: "currentColor",
|
|
102
|
+
maskImage: maskUrl,
|
|
103
|
+
maskRepeat: "no-repeat",
|
|
104
|
+
maskPosition: "center",
|
|
105
|
+
maskSize: "contain",
|
|
106
|
+
WebkitMaskImage: maskUrl,
|
|
107
|
+
WebkitMaskRepeat: "no-repeat",
|
|
108
|
+
WebkitMaskPosition: "center",
|
|
109
|
+
WebkitMaskSize: "contain"
|
|
110
|
+
}
|
|
114
111
|
});
|
|
115
112
|
}
|
|
113
|
+
function hasCurrentColorReference(dataUrl) {
|
|
114
|
+
return /currentcolor/i.test(decodeSvgDataUrl(dataUrl) ?? "");
|
|
115
|
+
}
|
|
116
|
+
function decodeSvgDataUrl(dataUrl) {
|
|
117
|
+
if (!dataUrl.startsWith("data:image/svg+xml")) return null;
|
|
118
|
+
try {
|
|
119
|
+
const comma = dataUrl.indexOf(",");
|
|
120
|
+
if (comma === -1) return null;
|
|
121
|
+
const payload = dataUrl.slice(comma + 1);
|
|
122
|
+
if (dataUrl.slice(0, comma).endsWith(";base64")) return new TextDecoder().decode(Uint8Array.from(atob(payload), (c) => c.charCodeAt(0)));
|
|
123
|
+
try {
|
|
124
|
+
return decodeURIComponent(payload);
|
|
125
|
+
} catch {
|
|
126
|
+
return payload;
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
116
132
|
//#endregion
|
|
117
133
|
export { IconOrShapeRenderer, IconRenderer, IconRendererProvider };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TagSpecification } from '@likec4/core';
|
|
2
|
+
import { LikeC4Styles } from '@likec4/core/styles';
|
|
2
3
|
import { PropsWithChildren } from 'react';
|
|
3
|
-
export declare function generateColorVars(spec: TagSpecification): string;
|
|
4
|
+
export declare function generateColorVars(spec: TagSpecification, styles?: LikeC4Styles): string;
|
|
4
5
|
export declare function TagStylesProvider({ children, id }: PropsWithChildren<{
|
|
5
6
|
id: string;
|
|
6
7
|
}>): import("react").JSX.Element;
|
|
@@ -1,49 +1,61 @@
|
|
|
1
1
|
import "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { useLikeC4Specification } from "../hooks/useLikeC4Model.js";
|
|
3
|
+
import { useLikeC4Styles } from "../hooks/useLikeC4Styles.js";
|
|
3
4
|
import { createContext, memo } from "react";
|
|
4
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { entries, flatMap, isEmpty, join, pipe } from "remeda";
|
|
6
7
|
import { isTagColorSpecified } from "@likec4/core";
|
|
7
8
|
import { useMantineStyleNonce } from "@mantine/core";
|
|
8
|
-
import { DefaultTagColors, getContrastedColorsAPCA, isValidColor } from "@likec4/core/styles";
|
|
9
|
+
import { DefaultTagColors, LikeC4Styles, getContrastedColorsAPCA, isValidColor } from "@likec4/core/styles";
|
|
9
10
|
//#region src/context/TagStylesContext.tsx
|
|
10
11
|
var radixColors = DefaultTagColors;
|
|
11
|
-
function generateColorVars(spec) {
|
|
12
|
+
function generateColorVars(spec, styles = LikeC4Styles.DEFAULT) {
|
|
12
13
|
const color = spec.color;
|
|
13
14
|
if (isTagColorSpecified(spec) && isValidColor(color)) return `
|
|
14
15
|
--colors-likec4-tag-bg: ${color};
|
|
15
16
|
--colors-likec4-tag-bg-hover: color-mix(in oklab, ${color}, var(--colors-likec4-mix-color) 20%);
|
|
16
17
|
--colors-likec4-tag-text: ${getContrastedColorsAPCA(color).hiContrast};
|
|
17
18
|
`;
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
19
|
+
if (radixColors.includes(color)) {
|
|
20
|
+
let textcolor = `var(--colors-${color}-12)`;
|
|
21
|
+
if ([
|
|
22
|
+
"mint",
|
|
23
|
+
"grass",
|
|
24
|
+
"lime",
|
|
25
|
+
"yellow",
|
|
26
|
+
"amber"
|
|
27
|
+
].includes(color)) textcolor = "rgba(0 0 0 / 0.85)";
|
|
28
|
+
return `
|
|
29
|
+
--colors-likec4-tag-border: var(--colors-${color}-8);
|
|
30
|
+
--colors-likec4-tag-bg: var(--colors-${color}-9);
|
|
31
|
+
--colors-likec4-tag-bg-hover: var(--colors-${color}-10);
|
|
32
|
+
--colors-likec4-tag-text: ${textcolor};
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
if (styles.isThemeColor(color)) {
|
|
36
|
+
const { fill, hiContrast } = styles.tagColor(color);
|
|
37
|
+
return `
|
|
38
|
+
--colors-likec4-tag-bg: ${fill};
|
|
39
|
+
--colors-likec4-tag-bg-hover: color-mix(in oklab, ${fill}, var(--colors-likec4-mix-color) 20%);
|
|
40
|
+
--colors-likec4-tag-text: ${hiContrast};
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
return "";
|
|
33
44
|
}
|
|
34
45
|
var TagStylesContext = createContext({});
|
|
35
|
-
function generateStylesheet(tags, rootSelector) {
|
|
46
|
+
function generateStylesheet(tags, rootSelector, styles) {
|
|
36
47
|
if (!tags || isEmpty(tags)) return "";
|
|
37
48
|
return pipe(entries(tags), flatMap(([tag, spec]) => [
|
|
38
49
|
`:is(${rootSelector} [data-likec4-tag="${tag}"]) {`,
|
|
39
|
-
generateColorVars(spec),
|
|
50
|
+
generateColorVars(spec, styles),
|
|
40
51
|
"}"
|
|
41
52
|
]), join("\n"));
|
|
42
53
|
}
|
|
43
54
|
function TagStylesProvider({ children, id }) {
|
|
44
55
|
const tags = useLikeC4Specification().tags;
|
|
56
|
+
const styles = useLikeC4Styles();
|
|
45
57
|
const nonce = useMantineStyleNonce()?.();
|
|
46
|
-
const stylesheet = generateStylesheet(tags, `#${id}
|
|
58
|
+
const stylesheet = generateStylesheet(tags, `#${id}`, styles);
|
|
47
59
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [stylesheet !== "" && /* @__PURE__ */ jsx(TagStylesheet, {
|
|
48
60
|
nonce,
|
|
49
61
|
stylesheet
|
package/dist/custom/index.js
CHANGED
|
@@ -9,10 +9,10 @@ import { useDiagramContext, useOnDiagramEvent } from "../hooks/useDiagram.js";
|
|
|
9
9
|
import { useCurrentView, useCurrentViewId } from "../hooks/useCurrentView.js";
|
|
10
10
|
import { useOptionalCurrentViewModel, useOptionalLikeC4Model } from "../context/LikeC4ModelContext.js";
|
|
11
11
|
import { useLikeC4Model, useLikeC4Specification, useLikeC4ViewModel } from "../hooks/useLikeC4Model.js";
|
|
12
|
+
import { useLikeC4Styles } from "../hooks/useLikeC4Styles.js";
|
|
12
13
|
import { ErrorBoundary, ErrorFallback } from "../components/ErrorFallback.js";
|
|
13
14
|
import { useCurrentViewModel } from "../hooks/useCurrentViewModel.js";
|
|
14
15
|
import { useHasProjects, useLikeC4Project, useLikeC4ProjectId, useLikeC4Projects, useLikeC4ProjectsContext } from "../hooks/useLikeC4Project.js";
|
|
15
|
-
import { useLikeC4Styles } from "../hooks/useLikeC4Styles.js";
|
|
16
16
|
import { CompoundActionButton } from "../base-primitives/compound/CompoundActionButton.js";
|
|
17
17
|
import { CompoundDetailsButton } from "../base-primitives/compound/CompoundDetailsButton.js";
|
|
18
18
|
import { CompoundNodeContainer } from "../base-primitives/compound/CompoundNodeContainer.js";
|
|
@@ -1,4 +1,19 @@
|
|
|
1
1
|
import type * as t from '@likec4/core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Result of applying a change from the LikeC4 Editor.
|
|
4
|
+
*/
|
|
5
|
+
export interface LikeC4EditorChangeResult {
|
|
6
|
+
/**
|
|
7
|
+
* Whether applying the change will publish a refreshed view to the diagram
|
|
8
|
+
* that the editor should wait for.
|
|
9
|
+
*
|
|
10
|
+
* Returning no result from `handleChange` defaults this to `true`,
|
|
11
|
+
* preserving the existing behavior for integrations that persist changes
|
|
12
|
+
* through an external model service.
|
|
13
|
+
*/
|
|
14
|
+
waitForViewSync: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function shouldWaitForViewSync(result?: void | LikeC4EditorChangeResult): boolean;
|
|
2
17
|
/**
|
|
3
18
|
* Callbacks from LikeC4 Editor.
|
|
4
19
|
*/
|
|
@@ -17,7 +32,11 @@ export interface LikeC4EditorCallbacks {
|
|
|
17
32
|
fetchView(viewId: t.ViewId, layout?: t.LayoutType): t.LayoutedView | Promise<t.LayoutedView>;
|
|
18
33
|
/**
|
|
19
34
|
* Callback invoked when the view changes.
|
|
35
|
+
*
|
|
36
|
+
* Return `{ waitForViewSync: false }` when the integration applies the
|
|
37
|
+
* change locally and will not publish a refreshed view. When changes are
|
|
38
|
+
* batched, the editor waits if any callback invocation expects a refresh.
|
|
20
39
|
*/
|
|
21
|
-
handleChange(viewId: t.ViewId, change: t.ViewChange): void | Promise<void>;
|
|
40
|
+
handleChange(viewId: t.ViewId, change: t.ViewChange): void | LikeC4EditorChangeResult | Promise<void | LikeC4EditorChangeResult>;
|
|
22
41
|
}
|
|
23
42
|
export declare function createLikeC4Editor(callbacks: LikeC4EditorCallbacks): LikeC4EditorCallbacks;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
//#region src/editor/LikeC4EditorCallbacks.tsx
|
|
3
|
+
function shouldWaitForViewSync(result) {
|
|
4
|
+
return result?.waitForViewSync ?? true;
|
|
5
|
+
}
|
|
3
6
|
function createLikeC4Editor(callbacks) {
|
|
4
7
|
return callbacks;
|
|
5
8
|
}
|
|
6
9
|
//#endregion
|
|
7
|
-
export { createLikeC4Editor };
|
|
10
|
+
export { createLikeC4Editor, shouldWaitForViewSync };
|
|
@@ -2,6 +2,22 @@ import "../../_virtual/_rolldown/runtime.js";
|
|
|
2
2
|
import { defineActors } from "../../utils/defineActors.js";
|
|
3
3
|
import { hotkey } from "./hotkey.js";
|
|
4
4
|
import { fromPromise, setup } from "xstate";
|
|
5
|
+
//#region src/editor/actor/setup.ts
|
|
6
|
+
var applyLatest = fromPromise(() => {
|
|
7
|
+
throw new Error("Not implemented");
|
|
8
|
+
});
|
|
9
|
+
var executeChange = fromPromise(() => {
|
|
10
|
+
throw new Error("Not implemented");
|
|
11
|
+
});
|
|
12
|
+
var applySemanticLayout = fromPromise(() => {
|
|
13
|
+
throw new Error("Not implemented");
|
|
14
|
+
});
|
|
15
|
+
var actors = defineActors({
|
|
16
|
+
hotkey,
|
|
17
|
+
applyLatest,
|
|
18
|
+
executeChange,
|
|
19
|
+
applySemanticLayout
|
|
20
|
+
});
|
|
5
21
|
var machine = setup({
|
|
6
22
|
types: {
|
|
7
23
|
context: {},
|
|
@@ -11,18 +27,7 @@ var machine = setup({
|
|
|
11
27
|
children: {},
|
|
12
28
|
tags: ""
|
|
13
29
|
},
|
|
14
|
-
actors
|
|
15
|
-
hotkey,
|
|
16
|
-
applyLatest: fromPromise(() => {
|
|
17
|
-
throw new Error("Not implemented");
|
|
18
|
-
}),
|
|
19
|
-
executeChange: fromPromise(() => {
|
|
20
|
-
throw new Error("Not implemented");
|
|
21
|
-
}),
|
|
22
|
-
applySemanticLayout: fromPromise(() => {
|
|
23
|
-
throw new Error("Not implemented");
|
|
24
|
-
})
|
|
25
|
-
}),
|
|
30
|
+
actors,
|
|
26
31
|
delays: {
|
|
27
32
|
"500ms": 500,
|
|
28
33
|
"wait-after-edit": 1e3
|
|
@@ -153,7 +153,7 @@ function edgeControlPoints(source, target) {
|
|
|
153
153
|
if (source === target) {
|
|
154
154
|
const loopSize = 80;
|
|
155
155
|
const centerOfTopBoundary = vector(0, source.height || 0).multiply(-.5).add(sourceCenter);
|
|
156
|
-
return [centerOfTopBoundary.add(vector(-
|
|
156
|
+
return [centerOfTopBoundary.add(vector(-32, -80)).trunc().toObject(), centerOfTopBoundary.add(vector(loopSize / 2.5, -80)).trunc().toObject()];
|
|
157
157
|
}
|
|
158
158
|
const sourceToTargetVector = targetCenter.subtract(sourceCenter);
|
|
159
159
|
const sourceBorderPoint = getBorderPointOnVector(source, sourceCenter, sourceToTargetVector);
|
package/dist/editor/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type * from './actor/machine';
|
|
2
2
|
export type * from './actor/types';
|
|
3
3
|
export { createLikeC4Editor } from './LikeC4EditorCallbacks';
|
|
4
|
-
export type { LikeC4EditorCallbacks } from './LikeC4EditorCallbacks';
|
|
4
|
+
export type { LikeC4EditorCallbacks, LikeC4EditorChangeResult, } from './LikeC4EditorCallbacks';
|
|
5
5
|
export { EnsureEditorContext, LikeC4EditorProvider, useOptionalLikeC4Editor, } from './LikeC4EditorProvider';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "../_virtual/_rolldown/runtime.js";
|
|
2
2
|
import { useCallbackRef } from "../hooks/useCallbackRef.js";
|
|
3
|
+
import { shouldWaitForViewSync } from "./LikeC4EditorCallbacks.js";
|
|
3
4
|
import { useOptionalLikeC4Editor } from "./LikeC4EditorProvider.js";
|
|
4
5
|
import { editorActorLogic } from "./actor/machine.js";
|
|
5
6
|
import { applyChangesToManualLayout } from "./applyChangesToManualLayout.js";
|
|
@@ -18,7 +19,9 @@ function useEditorActorLogic() {
|
|
|
18
19
|
console.error("No editor port available for applying latest to manual layout");
|
|
19
20
|
throw new Error("No editor port");
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
+
const manual = await promisify(() => current ?? port.fetchView(viewId, "manual"));
|
|
23
|
+
const latest = await promisify(() => port.fetchView(viewId, "auto"));
|
|
24
|
+
return { updated: applyChangesToManualLayout(manual, latest) };
|
|
22
25
|
});
|
|
23
26
|
const executeChange = useCallbackRef(async ({ input }) => {
|
|
24
27
|
if (!port) {
|
|
@@ -26,18 +29,22 @@ function useEditorActorLogic() {
|
|
|
26
29
|
throw new Error("No editor port");
|
|
27
30
|
}
|
|
28
31
|
const applied = [];
|
|
32
|
+
let waitForViewSync = false;
|
|
29
33
|
for (const change of input.changes) try {
|
|
30
|
-
await promisify(() => port.handleChange(input.viewId, change));
|
|
34
|
+
const result = await promisify(() => port.handleChange(input.viewId, change));
|
|
31
35
|
applied.push(change);
|
|
36
|
+
waitForViewSync ||= shouldWaitForViewSync(result);
|
|
32
37
|
} catch (error) {
|
|
33
38
|
console.error("Failed to execute change", {
|
|
34
39
|
change,
|
|
35
40
|
error
|
|
36
41
|
});
|
|
42
|
+
waitForViewSync ||= shouldWaitForViewSync();
|
|
37
43
|
}
|
|
38
44
|
return {
|
|
39
45
|
requested: input.changes,
|
|
40
|
-
applied
|
|
46
|
+
applied,
|
|
47
|
+
waitForViewSync
|
|
41
48
|
};
|
|
42
49
|
});
|
|
43
50
|
const applySemanticLayout = useCallbackRef(async ({ input }) => {
|
package/dist/hooks/useDiagram.js
CHANGED
|
@@ -17,7 +17,8 @@ function selectDiagramActor(selector) {
|
|
|
17
17
|
* @deprecated use {@link useDiagramSelector}
|
|
18
18
|
*/
|
|
19
19
|
function useDiagramSnapshot(selector, compare = shallowEqual) {
|
|
20
|
-
|
|
20
|
+
const actorRef = useDiagramActorRef();
|
|
21
|
+
return useSelector(actorRef, selector, compare);
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* Helper to create a selector for diagram actor snapshot
|
|
@@ -33,7 +34,8 @@ function selectDiagramActorContext(selector) {
|
|
|
33
34
|
function useDiagramContext(selector, compare = shallowEqual, deps = []) {
|
|
34
35
|
const actorRef = useDiagramActorRef();
|
|
35
36
|
const selectorRef = useCallbackRef(selector);
|
|
36
|
-
|
|
37
|
+
const select = useCallback((s) => selectorRef(s.context), deps);
|
|
38
|
+
return useSelector(actorRef, select, compare);
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
41
|
* Subscribe to diagram emitted events
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { DiagramActorSnapshot } from '../likec4diagram/state/types';
|
|
2
2
|
import type * as t from '@likec4/core/types';
|
|
3
|
-
declare const selectCompareLayoutState: ({ context }: DiagramActorSnapshot) => {
|
|
3
|
+
export declare const selectCompareLayoutState: ({ context }: DiagramActorSnapshot) => {
|
|
4
4
|
isEnabled: false;
|
|
5
5
|
hasEditor: false;
|
|
6
6
|
isEditable: false;
|
|
7
7
|
isActive: false;
|
|
8
|
-
drifts:
|
|
8
|
+
drifts: null;
|
|
9
9
|
canApplyLatest: boolean;
|
|
10
10
|
layout: t.LayoutType;
|
|
11
11
|
} | {
|
|
@@ -17,7 +17,13 @@ declare const selectCompareLayoutState: ({ context }: DiagramActorSnapshot) => {
|
|
|
17
17
|
canApplyLatest: boolean;
|
|
18
18
|
layout: t.LayoutType;
|
|
19
19
|
};
|
|
20
|
+
/**
|
|
21
|
+
* Current manual-layout comparison state exposed by {@link useDiagramCompareLayout}.
|
|
22
|
+
*/
|
|
20
23
|
export type DiagramCompareLayoutState = ReturnType<typeof selectCompareLayoutState>;
|
|
24
|
+
/**
|
|
25
|
+
* Manual-layout actions exposed by {@link useDiagramCompareLayout}.
|
|
26
|
+
*/
|
|
21
27
|
export type DiagramCompareLayoutOps = {
|
|
22
28
|
/**
|
|
23
29
|
* Toggles the compare mode on or off.
|
|
@@ -36,8 +42,12 @@ export type DiagramCompareLayoutOps = {
|
|
|
36
42
|
*/
|
|
37
43
|
resetManualLayout: () => void;
|
|
38
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* Controls LikeC4's manual-layout comparison from custom diagram children.
|
|
47
|
+
*
|
|
48
|
+
* The hook must be called by a component rendered inside {@link LikeC4Diagram}.
|
|
49
|
+
*/
|
|
39
50
|
export declare function useDiagramCompareLayout(): [
|
|
40
51
|
DiagramCompareLayoutState,
|
|
41
52
|
DiagramCompareLayoutOps
|
|
42
53
|
];
|
|
43
|
-
export {};
|
|
@@ -28,6 +28,11 @@ var selectCompareLayoutState = ({ context }) => {
|
|
|
28
28
|
layout: context.view._layout ?? "auto"
|
|
29
29
|
};
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Controls LikeC4's manual-layout comparison from custom diagram children.
|
|
33
|
+
*
|
|
34
|
+
* The hook must be called by a component rendered inside {@link LikeC4Diagram}.
|
|
35
|
+
*/
|
|
31
36
|
function useDiagramCompareLayout() {
|
|
32
37
|
const actorRef = useDiagramActorRef();
|
|
33
38
|
const state = useSelector(actorRef, selectCompareLayoutState, shallowEqual);
|
|
@@ -78,4 +83,4 @@ function useDiagramCompareLayout() {
|
|
|
78
83
|
}];
|
|
79
84
|
}
|
|
80
85
|
//#endregion
|
|
81
|
-
export { useDiagramCompareLayout };
|
|
86
|
+
export { selectCompareLayoutState, useDiagramCompareLayout };
|