@leavepulse/ui 0.22.10 → 0.22.12

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.
@@ -6,6 +6,7 @@ import { MiniMap } from "@vue-flow/minimap";
6
6
  import { _ as _sfc_main$3 } from "./LpInfraNode-qIL3l7IO.js";
7
7
  import { _ as _sfc_main$1 } from "./LpLaneNode-HtnQf2FF.js";
8
8
  import { _ as _sfc_main$2 } from "./LpServiceNode-D8uJ8sGk.js";
9
+ import { _ as _sfc_main$4 } from "./LpWaypointEdge-D8yHlQiw.js";
9
10
  const _hoisted_1 = { class: "flex flex-col gap-1.5 rounded-card border border-line bg-surface-overlay/90 px-3 py-2 text-[11px] backdrop-blur" };
10
11
  const _hoisted_2 = {
11
12
  width: "22",
@@ -23,6 +24,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
23
24
  selectable: { type: Boolean, default: false },
24
25
  legend: { type: Boolean, default: true },
25
26
  legendPosition: { default: "bottom-left" },
27
+ edgeType: {},
26
28
  legendLabels: {},
27
29
  lanes: { default: () => [] },
28
30
  minimap: { type: Boolean, default: true },
@@ -37,6 +39,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
37
39
  service: markRaw(_sfc_main$2),
38
40
  lane: markRaw(_sfc_main$1)
39
41
  };
42
+ const edgeTypes = { waypoints: markRaw(_sfc_main$4) };
40
43
  const laneNodes = computed(
41
44
  () => props.lanes.map(
42
45
  (l) => ({
@@ -105,7 +108,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
105
108
  id: e.id,
106
109
  source: e.source,
107
110
  target: e.target,
108
- type: "default",
111
+ type: props.edgeType ?? "default",
112
+ data: { waypoints: e.waypoints },
109
113
  animated: obs !== "applied" && !e.dimmed,
110
114
  label: e.kind,
111
115
  labelBgStyle: { fill: "var(--color-surface)" },
@@ -188,6 +192,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
188
192
  nodes: flowNodes.value,
189
193
  edges: flowEdges.value,
190
194
  "node-types": nodeTypes,
195
+ "edge-types": edgeTypes,
191
196
  "nodes-connectable": __props.connectable,
192
197
  "selection-on-drag": __props.selectable,
193
198
  "pan-on-drag": __props.selectable ? [1, 2] : true,
@@ -0,0 +1,67 @@
1
+ import { defineComponent, computed, openBlock, createBlock, unref, normalizeStyle } from "vue";
2
+ import { getBezierPath, Position, BaseEdge } from "@vue-flow/core";
3
+ const _sfc_main = /* @__PURE__ */ defineComponent({
4
+ __name: "LpWaypointEdge",
5
+ props: {
6
+ id: {},
7
+ sourceX: {},
8
+ sourceY: {},
9
+ targetX: {},
10
+ targetY: {},
11
+ sourcePosition: {},
12
+ targetPosition: {},
13
+ data: {},
14
+ markerEnd: {},
15
+ style: {}
16
+ },
17
+ setup(__props) {
18
+ const props = __props;
19
+ function splineThrough(points) {
20
+ if (points.length < 2) return "";
21
+ const p = [points[0], ...points, points[points.length - 1]];
22
+ let d = `M${points[0].x},${points[0].y}`;
23
+ for (let i = 1; i < p.length - 2; i++) {
24
+ const p0 = p[i - 1];
25
+ const p1 = p[i];
26
+ const p2 = p[i + 1];
27
+ const p3 = p[i + 2];
28
+ const c1x = p1.x + (p2.x - p0.x) / 6;
29
+ const c1y = p1.y + (p2.y - p0.y) / 6;
30
+ const c2x = p2.x - (p3.x - p1.x) / 6;
31
+ const c2y = p2.y - (p3.y - p1.y) / 6;
32
+ d += ` C${c1x},${c1y} ${c2x},${c2y} ${p2.x},${p2.y}`;
33
+ }
34
+ return d;
35
+ }
36
+ const path = computed(() => {
37
+ const via = props.data?.waypoints ?? [];
38
+ if (!via.length) {
39
+ const [d] = getBezierPath({
40
+ sourceX: props.sourceX,
41
+ sourceY: props.sourceY,
42
+ targetX: props.targetX,
43
+ targetY: props.targetY,
44
+ sourcePosition: props.sourcePosition ?? Position.Right,
45
+ targetPosition: props.targetPosition ?? Position.Left
46
+ });
47
+ return d;
48
+ }
49
+ return splineThrough([
50
+ { x: props.sourceX, y: props.sourceY },
51
+ ...via,
52
+ { x: props.targetX, y: props.targetY }
53
+ ]);
54
+ });
55
+ return (_ctx, _cache) => {
56
+ return openBlock(), createBlock(unref(BaseEdge), {
57
+ id: __props.id,
58
+ path: path.value,
59
+ "marker-end": __props.markerEnd,
60
+ style: normalizeStyle(__props.style)
61
+ }, null, 8, ["id", "path", "marker-end", "style"]);
62
+ };
63
+ }
64
+ });
65
+ export {
66
+ _sfc_main as _
67
+ };
@@ -1 +1 @@
1
- export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpCheckbox", "LpCodeBlock", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpModal", "LpNotificationBell", "LpNumberField", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpScrollArea", "LpSegmented", "LpSelect", "LpServiceNode", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar"];
1
+ export declare const COMPONENT_NAMES: readonly ["LayoutCanvas", "LayoutNode", "LpAddressInput", "LpAlert", "LpAppShell", "LpAutocomplete", "LpAvatar", "LpBadge", "LpBreadcrumbs", "LpButton", "LpCalendar", "LpCard", "LpCheckbox", "LpCodeBlock", "LpCommandPalette", "LpConfirmDialog", "LpContextMenu", "LpDatePicker", "LpDisclosure", "LpDivider", "LpDrawer", "LpDropdownMenu", "LpEmptyState", "LpFileTree", "LpFileTreeNode", "LpFormField", "LpIcon", "LpInfraNode", "LpInput", "LpLaneNode", "LpLightbox", "LpLink", "LpLogViewer", "LpMapPicker", "LpModal", "LpNotificationBell", "LpNumberField", "LpOtpInput", "LpPagination", "LpPasswordInput", "LpPhoneInput", "LpPopover", "LpProgress", "LpRadio", "LpRadioGroup", "LpScrollArea", "LpSegmented", "LpSelect", "LpServiceNode", "LpShortcutsDialog", "LpSidebar", "LpSidebarNav", "LpSkeleton", "LpSlider", "LpStat", "LpStepper", "LpSwitch", "LpTable", "LpTableOfContents", "LpTabs", "LpTextarea", "LpThemeSwitcher", "LpTilt", "LpToaster", "LpTooltip", "LpTopologyCanvas", "LpUptimeBar", "LpWaypointEdge"];
@@ -46,6 +46,14 @@ export interface TopologyEdge {
46
46
  * every unrelated line at full strength.
47
47
  */
48
48
  dimmed?: boolean;
49
+ /**
50
+ * Points the edge should curve through, when the host's layout knows where
51
+ * the gaps are. Requires `edgeType: "waypoints"`; ignored otherwise.
52
+ */
53
+ waypoints?: {
54
+ x: number;
55
+ y: number;
56
+ }[];
49
57
  }
50
58
  /**
51
59
  * A background swimlane: a sized zone (one per project / overlay network) drawn
@@ -90,6 +98,13 @@ type __VLS_Props = {
90
98
  * minimap owns the bottom-right.
91
99
  */
92
100
  legendPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
101
+ /**
102
+ * Edge shape. The default bezier bulges away from the straight line between
103
+ * its ends, which suits a free-form graph and cuts through everything in a
104
+ * LAYERED one — a long edge crosses the cards standing between its columns.
105
+ * `smoothstep` routes orthogonally through the gaps instead.
106
+ */
107
+ edgeType?: "default" | "smoothstep" | "step" | "straight" | "waypoints";
93
108
  /** Rename legend rows to match what this graph's edges actually mean. */
94
109
  legendLabels?: Partial<Record<"network" | "structural" | "pending" | "drift", string>>;
95
110
  /**
@@ -1,4 +1,4 @@
1
- import { _ as _sfc_main } from "../chunks/LpTopologyCanvas-D_EsMgCg.js";
1
+ import { _ as _sfc_main } from "../chunks/LpTopologyCanvas-ebP67CLx.js";
2
2
  export {
3
3
  _sfc_main as default
4
4
  };
@@ -0,0 +1,21 @@
1
+ import { Position } from '@vue-flow/core';
2
+ type __VLS_Props = {
3
+ id: string;
4
+ sourceX: number;
5
+ sourceY: number;
6
+ targetX: number;
7
+ targetY: number;
8
+ sourcePosition?: Position;
9
+ targetPosition?: Position;
10
+ data?: {
11
+ waypoints?: {
12
+ x: number;
13
+ y: number;
14
+ }[];
15
+ };
16
+ markerEnd?: string;
17
+ style?: Record<string, unknown>;
18
+ };
19
+ declare const __VLS_export: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {}, any>;
20
+ declare const _default: typeof __VLS_export;
21
+ export default _default;
@@ -0,0 +1,4 @@
1
+ import { _ as _sfc_main } from "../chunks/LpWaypointEdge-D8yHlQiw.js";
2
+ export {
3
+ _sfc_main as default
4
+ };
package/dist/index.js CHANGED
@@ -64,7 +64,7 @@ import { D, _ as _56, N, T, b, c, d, e, f, g, i, l, h, j, n, r, t, k, m, u, v }
64
64
  import { _ as _57 } from "./chunks/LpTilt-DuyKbpkA.js";
65
65
  import { _ as _58 } from "./chunks/LpToaster-gDB1EaU3.js";
66
66
  import { _ as _59 } from "./chunks/LpTooltip-D1hbjQWG.js";
67
- import { _ as _60 } from "./chunks/LpTopologyCanvas-D_EsMgCg.js";
67
+ import { _ as _60 } from "./chunks/LpTopologyCanvas-ebP67CLx.js";
68
68
  import { _ as _61 } from "./chunks/LpUptimeBar-CB366k0O.js";
69
69
  import { deserializeLayout, serializeLayout } from "./layout/tree.js";
70
70
  import { addLeaf, addTab, countBlocks, countLeaves, makeLayout, moveLeaf, removeBlock, removeLeaf, reorderTab, resizeAt, setActiveTab } from "./layout/tree.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.22.10",
3
+ "version": "0.22.12",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -68,5 +68,6 @@ export const COMPONENT_NAMES = [
68
68
  "LpToaster",
69
69
  "LpTooltip",
70
70
  "LpTopologyCanvas",
71
- "LpUptimeBar"
71
+ "LpUptimeBar",
72
+ "LpWaypointEdge"
72
73
  ] as const
@@ -26,6 +26,7 @@ import { computed, markRaw, watch, type Component } from "vue"
26
26
  import LpInfraNode, { type InfraNodeData } from "./LpInfraNode.vue"
27
27
  import LpLaneNode from "./LpLaneNode.vue"
28
28
  import LpServiceNode, { type ServiceNodeData } from "./LpServiceNode.vue"
29
+ import LpWaypointEdge from "./LpWaypointEdge.vue"
29
30
 
30
31
  // Vue Flow base CSS + canvas chrome theming is shipped via the kit's
31
32
  // "@leavepulse/ui/canvas.css" export (a plain CSS file). Component-local CSS
@@ -77,6 +78,11 @@ export interface TopologyEdge {
77
78
  * every unrelated line at full strength.
78
79
  */
79
80
  dimmed?: boolean
81
+ /**
82
+ * Points the edge should curve through, when the host's layout knows where
83
+ * the gaps are. Requires `edgeType: "waypoints"`; ignored otherwise.
84
+ */
85
+ waypoints?: { x: number; y: number }[]
80
86
  }
81
87
 
82
88
  /**
@@ -125,6 +131,13 @@ const props = withDefaults(
125
131
  * minimap owns the bottom-right.
126
132
  */
127
133
  legendPosition?: "top-left" | "top-right" | "bottom-left" | "bottom-right"
134
+ /**
135
+ * Edge shape. The default bezier bulges away from the straight line between
136
+ * its ends, which suits a free-form graph and cuts through everything in a
137
+ * LAYERED one — a long edge crosses the cards standing between its columns.
138
+ * `smoothstep` routes orthogonally through the gaps instead.
139
+ */
140
+ edgeType?: "default" | "smoothstep" | "step" | "straight" | "waypoints"
128
141
  /** Rename legend rows to match what this graph's edges actually mean. */
129
142
  legendLabels?: Partial<
130
143
  Record<"network" | "structural" | "pending" | "drift", string>
@@ -185,6 +198,9 @@ const nodeTypes: NodeTypesObject = {
185
198
  service: markRaw(LpServiceNode) as Component,
186
199
  lane: markRaw(LpLaneNode) as Component,
187
200
  }
201
+ // `waypoints` curves through the points an edge carries, for layouts that know
202
+ // where the gaps are; with none it falls back to the plain bezier.
203
+ const edgeTypes = { waypoints: markRaw(LpWaypointEdge) as Component }
188
204
 
189
205
  // Lanes render as non-interactive Vue Flow nodes BENEATH the graph: prepended
190
206
  // so they paint first, with selecting/dragging/connecting off and a low z so
@@ -268,7 +284,8 @@ const flowEdges = computed(() =>
268
284
  id: e.id,
269
285
  source: e.source,
270
286
  target: e.target,
271
- type: "default",
287
+ type: props.edgeType ?? "default",
288
+ data: { waypoints: e.waypoints },
272
289
  animated: obs !== "applied" && !e.dimmed,
273
290
  label: e.kind,
274
291
  labelBgStyle: { fill: "var(--color-surface)" },
@@ -375,6 +392,7 @@ defineExpose({ fitView, setViewport: applyViewport })
375
392
  :nodes="flowNodes"
376
393
  :edges="flowEdges"
377
394
  :node-types="nodeTypes"
395
+ :edge-types="edgeTypes"
378
396
  :nodes-connectable="connectable"
379
397
  :selection-on-drag="selectable"
380
398
  :pan-on-drag="selectable ? [1, 2] : true"
@@ -0,0 +1,77 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * An edge that curves THROUGH a list of waypoints.
4
+ *
5
+ * The built-in shapes each fail one way in a layered graph: a bezier bulges off
6
+ * the line between its ends and cuts through whatever stands between the
7
+ * columns, and orthogonal routing clears the cards but draws the graph as a
8
+ * grid of right angles. Given the gaps a layout already knows about, a curve
9
+ * threaded through them is both — clear of the cards and still a curve.
10
+ *
11
+ * Waypoints ride on the edge's `data.waypoints`; with none, this is a plain
12
+ * bezier, so the type is safe to use for every edge in a graph.
13
+ */
14
+ import { BaseEdge, getBezierPath, Position } from "@vue-flow/core"
15
+ import { computed } from "vue"
16
+
17
+ const props = defineProps<{
18
+ id: string
19
+ sourceX: number
20
+ sourceY: number
21
+ targetX: number
22
+ targetY: number
23
+ sourcePosition?: Position
24
+ targetPosition?: Position
25
+ data?: { waypoints?: { x: number; y: number }[] }
26
+ markerEnd?: string
27
+ style?: Record<string, unknown>
28
+ }>()
29
+
30
+ /*
31
+ * Catmull-Rom through the points, converted to cubic beziers — the standard way
32
+ * to get a curve that actually passes through its control points rather than
33
+ * being pulled towards them. Tension 0.5 keeps it taut enough not to loop back
34
+ * on itself between closely spaced waypoints.
35
+ */
36
+ function splineThrough(points: { x: number; y: number }[]): string {
37
+ if (points.length < 2) return ""
38
+ const p = [points[0]!, ...points, points[points.length - 1]!]
39
+ let d = `M${points[0]!.x},${points[0]!.y}`
40
+ for (let i = 1; i < p.length - 2; i++) {
41
+ const p0 = p[i - 1]!
42
+ const p1 = p[i]!
43
+ const p2 = p[i + 1]!
44
+ const p3 = p[i + 2]!
45
+ const c1x = p1.x + (p2.x - p0.x) / 6
46
+ const c1y = p1.y + (p2.y - p0.y) / 6
47
+ const c2x = p2.x - (p3.x - p1.x) / 6
48
+ const c2y = p2.y - (p3.y - p1.y) / 6
49
+ d += ` C${c1x},${c1y} ${c2x},${c2y} ${p2.x},${p2.y}`
50
+ }
51
+ return d
52
+ }
53
+
54
+ const path = computed(() => {
55
+ const via = props.data?.waypoints ?? []
56
+ if (!via.length) {
57
+ const [d] = getBezierPath({
58
+ sourceX: props.sourceX,
59
+ sourceY: props.sourceY,
60
+ targetX: props.targetX,
61
+ targetY: props.targetY,
62
+ sourcePosition: props.sourcePosition ?? Position.Right,
63
+ targetPosition: props.targetPosition ?? Position.Left,
64
+ })
65
+ return d
66
+ }
67
+ return splineThrough([
68
+ { x: props.sourceX, y: props.sourceY },
69
+ ...via,
70
+ { x: props.targetX, y: props.targetY },
71
+ ])
72
+ })
73
+ </script>
74
+
75
+ <template>
76
+ <BaseEdge :id="id" :path="path" :marker-end="markerEnd" :style="style" />
77
+ </template>