@floegence/floe-webapp-core 0.86.15 → 0.86.18

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.
@@ -21,6 +21,10 @@ export interface GraphCanvasProps {
21
21
  onInteractionStart?: () => void;
22
22
  /** Overlay content should use shared SurfaceFloatingLayer for point placement. */
23
23
  overlay?: JSX.Element;
24
+ /** Optional interactive overview. Its geometry follows the same visible layout. */
25
+ minimap?: {
26
+ ariaLabel: string;
27
+ };
24
28
  class?: string;
25
29
  }
26
30
  /** Controlled graph surface. Hosts own documents and selection; Floe owns geometry and input. */
@@ -1,79 +1,111 @@
1
- import { insert as y, createComponent as w, setAttribute as l, effect as S, className as V, template as _, memo as p, setStyleProperty as T, use as ee, delegateEvents as te } from "solid-js/web";
2
- import { createUniqueId as ne, createSignal as B, createMemo as $, onMount as re, For as D, Show as F, onCleanup as ae } from "solid-js";
3
- import { InfiniteCanvas as ie } from "../ui/InfiniteCanvas.js";
4
- import { resolveSurfaceInteractionTargetRole as oe } from "../ui/localInteractionSurface.js";
5
- import { graphRelatedEdges as W, graphEdgeLabelPosition as le, graphSectionPath as ce } from "./geometry.js";
6
- var de = /* @__PURE__ */ _("<div data-floe-canvas-pan-surface=true role=group tabindex=0>"), se = /* @__PURE__ */ _("<div role=region tabindex=0>"), ue = /* @__PURE__ */ _('<svg class=floe-graph__edges width=1 height=1><defs><marker viewBox="0 0 8 8"refX=7 refY=4 markerWidth=7 markerHeight=7 orient=auto-start-reverse><path d="M1 1 L7 4 L1 7"fill=none stroke=context-stroke stroke-width=1.2>'), he = /* @__PURE__ */ _("<svg><g><path class=floe-graph__edge></path><path class=floe-graph__edge-target tabindex=0 role=button data-floe-canvas-pan-surface=true></svg>", !1, !0, !1), ge = /* @__PURE__ */ _("<svg><text class=floe-graph__edge-label aria-hidden=true></svg>", !1, !0, !1);
7
- function ke(i) {
8
- const H = `graph-arrow-${ne()}`;
1
+ import { insert as w, createComponent as x, setAttribute as l, effect as S, className as K, template as I, memo as ae, setStyleProperty as T, use as ie, delegateEvents as oe } from "solid-js/web";
2
+ import { createUniqueId as le, createSignal as O, createMemo as y, onMount as ce, For as j, Show as D, onCleanup as de } from "solid-js";
3
+ import { InfiniteCanvas as ue } from "../ui/InfiniteCanvas.js";
4
+ import { resolveSurfaceInteractionTargetRole as se } from "../ui/localInteractionSurface.js";
5
+ import { graphRelatedEdges as U, graphEdgeLabelPosition as he, graphSectionPath as ge } from "./geometry.js";
6
+ import { GraphMinimap as fe } from "./GraphMinimap.js";
7
+ var ve = /* @__PURE__ */ I("<div data-floe-canvas-pan-surface=true role=group tabindex=0>"), me = /* @__PURE__ */ I("<div role=region tabindex=0>"), be = /* @__PURE__ */ I('<svg class=floe-graph__edges width=1 height=1><defs><marker viewBox="0 0 8 8"refX=7 refY=4 markerWidth=7 markerHeight=7 orient=auto-start-reverse><path d="M1 1 L7 4 L1 7"fill=none stroke=context-stroke stroke-width=1.2>'), xe = /* @__PURE__ */ I("<svg><g><path class=floe-graph__edge></path><path class=floe-graph__edge-target tabindex=0 role=button data-floe-canvas-pan-surface=true></svg>", !1, !0, !1), ye = /* @__PURE__ */ I("<svg><text class=floe-graph__edge-label aria-hidden=true></svg>", !1, !0, !1);
8
+ function Le(i) {
9
+ const H = `graph-arrow-${le()}`;
9
10
  let g;
10
- const [q, K] = B(null), [O, U] = B({
11
+ const [J, Q] = O(null), [V, Z] = O({
11
12
  width: 0,
12
13
  height: 0
13
- }), X = () => i.highlight === void 0 ? q() : i.highlight, J = $(() => new Map(i.layout.nodes.map((e) => [e.id, e]))), Q = $(() => {
14
+ }), X = () => i.highlight === void 0 ? J() : i.highlight, p = y(() => new Map(i.layout.nodes.map((e) => [e.id, e]))), ee = y(() => {
14
15
  const e = X();
15
- return e?.kind === "node" ? W(i.layout, e.id) : new Set(e ? [e.id] : []);
16
- }), Y = $(() => {
16
+ return e?.kind === "node" ? U(i.layout, e.id) : new Set(e ? [e.id] : []);
17
+ }), Y = y(() => {
17
18
  const e = X();
18
19
  if (e?.kind !== "node") return null;
19
- const t = J().get(e.id), r = t?.kind === "group" ? t.id : t?.parentId;
20
- return r ? W(i.layout, r) : null;
21
- }), A = (e) => i.selected?.kind === e.kind && i.selected.id === e.id, Z = (e) => {
20
+ const t = p().get(e.id), n = t?.kind === "group" ? t.id : t?.parentId;
21
+ return n ? U(i.layout, n) : null;
22
+ }), A = (e) => i.selected?.kind === e.kind && i.selected.id === e.id, B = (e) => {
22
23
  const {
23
24
  width: t,
24
- height: r
25
- } = O();
26
- if (!t || !r) return i.layout.nodes;
27
- const a = -e.x / e.scale - 240, n = -e.y / e.scale - 240, b = a + t / e.scale + 480, c = n + r / e.scale + 480;
28
- return i.layout.nodes.filter((d) => d.id === i.selected?.id || d.x + d.width >= a && d.x <= b && d.y + d.height >= n && d.y <= c);
29
- }, v = (e) => {
30
- K(e), i.onHover?.(e);
31
- }, x = (e, t, r) => {
32
- const a = t.getBoundingClientRect();
25
+ height: n
26
+ } = V();
27
+ if (!t || !n) return;
28
+ const r = -e.x / e.scale - 240, a = -e.y / e.scale - 240, m = r + t / e.scale + 480, c = a + n / e.scale + 480;
29
+ return {
30
+ left: r,
31
+ top: a,
32
+ right: m,
33
+ bottom: c
34
+ };
35
+ }, te = (e) => {
36
+ const t = B(e);
37
+ if (!t) return i.layout.nodes;
38
+ const {
39
+ left: n,
40
+ top: r,
41
+ right: a,
42
+ bottom: m
43
+ } = t;
44
+ return i.layout.nodes.filter((c) => c.id === i.selected?.id || c.x + c.width >= n && c.x <= a && c.y + c.height >= r && c.y <= m);
45
+ }, N = y(() => i.layout.edges.map((e) => ({
46
+ edge: e,
47
+ path: e.sections.map(ge).join(" "),
48
+ label: he(e.sections),
49
+ segments: e.sections.flatMap((t) => t.slice(1).map((n, r) => ({
50
+ left: Math.min(n.x, t[r].x),
51
+ right: Math.max(n.x, t[r].x),
52
+ top: Math.min(n.y, t[r].y),
53
+ bottom: Math.max(n.y, t[r].y)
54
+ })))
55
+ }))), ne = (e) => {
56
+ const t = B(e);
57
+ return t ? N().filter(({
58
+ edge: n,
59
+ segments: r
60
+ }) => n.id === i.selected?.id || r.some((a) => a.left <= t.right && a.right >= t.left && a.top <= t.bottom && a.bottom >= t.top)) : N();
61
+ }, f = (e) => {
62
+ Q(e), i.onHover?.(e);
63
+ }, k = (e, t, n) => {
64
+ const r = t.getBoundingClientRect();
33
65
  return {
34
66
  object: e,
35
67
  owner: t,
36
- position: r && (r.clientX !== 0 || r.clientY !== 0) ? {
37
- x: r.clientX,
38
- y: r.clientY
68
+ position: n && (n.clientX !== 0 || n.clientY !== 0) ? {
69
+ x: n.clientX,
70
+ y: n.clientY
39
71
  } : {
40
- x: a.left + a.width / 2,
41
- y: a.top + Math.min(32, a.height / 2)
72
+ x: r.left + r.width / 2,
73
+ y: r.top + Math.min(32, r.height / 2)
42
74
  }
43
75
  };
44
- }, R = (e, t) => {
45
- i.onContextMenu && (t.preventDefault(), t.stopPropagation(), i.onContextMenu(x(e, t.currentTarget, t)));
46
- }, j = (e, t) => {
47
- t.target === t.currentTarget && (t.key === "ContextMenu" || t.shiftKey && t.key === "F10" ? (t.preventDefault(), t.stopPropagation(), i.onContextMenu?.(x(e, t.currentTarget))) : t.key === "Enter" ? (t.preventDefault(), t.stopPropagation(), i.onActivate?.(x(e, t.currentTarget))) : t.key === "Escape" && (v(null), i.onActivate?.(x(null, g))));
76
+ }, z = (e, t) => {
77
+ i.onContextMenu && (t.preventDefault(), t.stopPropagation(), i.onContextMenu(k(e, t.currentTarget, t)));
78
+ }, G = (e, t) => {
79
+ t.target === t.currentTarget && (t.key === "ContextMenu" || t.shiftKey && t.key === "F10" ? (t.preventDefault(), t.stopPropagation(), i.onContextMenu?.(k(e, t.currentTarget))) : t.key === "Enter" ? (t.preventDefault(), t.stopPropagation(), i.onActivate?.(k(e, t.currentTarget))) : t.key === "Escape" && (f(null), i.onActivate?.(k(null, g))));
48
80
  };
49
- re(() => {
81
+ ce(() => {
50
82
  if (!g) return;
51
- const e = () => U({
83
+ const e = () => Z({
52
84
  width: g.clientWidth,
53
85
  height: g.clientHeight
54
86
  });
55
87
  e();
56
88
  const t = new ResizeObserver(e);
57
- t.observe(g), ae(() => t.disconnect());
89
+ t.observe(g), de(() => t.disconnect());
58
90
  });
59
- const G = (e) => {
91
+ const F = (e) => {
60
92
  const t = {
61
93
  kind: "node",
62
94
  id: e.id
63
- }, r = {
95
+ }, n = {
64
96
  selected: () => A(t),
65
- openMenu: (a) => R(t, a)
97
+ openMenu: (r) => z(t, r)
66
98
  };
67
99
  return (() => {
68
- var a = de();
69
- return a.$$keydown = (n) => j(t, n), a.$$contextmenu = (n) => R(t, n), a.$$click = (n) => {
70
- n.target.closest("button, a, input, select, textarea, [data-floe-canvas-interactive]") || (n.stopPropagation(), i.onActivate?.(x(t, n.currentTarget, n)));
71
- }, a.$$focusout = () => v(null), a.$$focusin = () => v(t), a.addEventListener("pointerleave", () => v(null)), a.addEventListener("pointerenter", () => v(t)), y(a, (() => {
72
- var n = p(() => !!(e.kind === "group" && i.renderGroup));
73
- return () => n() ? i.renderGroup(e, r) : i.renderNode(e, r);
74
- })()), S((n) => {
75
- var b = e.kind === "group" ? "floe-graph__group" : "floe-graph__node", c = e.id, d = A(t), P = `${e.x}px`, s = `${e.y}px`, f = `${e.width}px`, C = `${e.height}px`, I = e.label;
76
- return b !== n.e && V(a, n.e = b), c !== n.t && l(a, "data-graph-object", n.t = c), d !== n.a && l(a, "data-selected", n.a = d), P !== n.o && T(a, "left", n.o = P), s !== n.i && T(a, "top", n.i = s), f !== n.n && T(a, "width", n.n = f), C !== n.s && T(a, "height", n.s = C), I !== n.h && l(a, "aria-label", n.h = I), n;
100
+ var r = ve();
101
+ return r.$$keydown = (a) => G(t, a), r.$$contextmenu = (a) => z(t, a), r.$$click = (a) => {
102
+ a.target.closest("button, a, input, select, textarea, [data-floe-canvas-interactive]") || (a.stopPropagation(), i.onActivate?.(k(t, a.currentTarget, a)));
103
+ }, r.$$focusout = () => f(null), r.$$focusin = () => f(t), r.addEventListener("pointerleave", () => f(null)), r.addEventListener("pointerenter", () => f(t)), w(r, (() => {
104
+ var a = ae(() => !!(e.kind === "group" && i.renderGroup));
105
+ return () => a() ? i.renderGroup(e, n) : i.renderNode(e, n);
106
+ })()), S((a) => {
107
+ var m = e.kind === "group" ? "floe-graph__group" : "floe-graph__node", c = e.id, d = A(t), _ = `${e.x}px`, P = `${e.y}px`, $ = `${e.width}px`, u = `${e.height}px`, v = e.label;
108
+ return m !== a.e && K(r, a.e = m), c !== a.t && l(r, "data-graph-object", a.t = c), d !== a.a && l(r, "data-selected", a.a = d), _ !== a.o && T(r, "left", a.o = _), P !== a.i && T(r, "top", a.i = P), $ !== a.n && T(r, "width", a.n = $), u !== a.s && T(r, "height", a.s = u), v !== a.h && l(r, "aria-label", a.h = v), a;
77
109
  }, {
78
110
  e: void 0,
79
111
  t: void 0,
@@ -83,14 +115,14 @@ function ke(i) {
83
115
  n: void 0,
84
116
  s: void 0,
85
117
  h: void 0
86
- }), a;
118
+ }), r;
87
119
  })();
88
120
  };
89
121
  return (() => {
90
- var e = se();
91
- e.$$keydown = (r) => j(null, r);
122
+ var e = me();
123
+ e.$$keydown = (n) => G(null, n);
92
124
  var t = g;
93
- return typeof t == "function" ? ee(t, e) : g = e, y(e, w(ie, {
125
+ return typeof t == "function" ? ie(t, e) : g = e, w(e, x(ue, {
94
126
  get viewport() {
95
127
  return i.viewport;
96
128
  },
@@ -103,62 +135,64 @@ function ke(i) {
103
135
  return i.onViewportChange;
104
136
  },
105
137
  onViewportInteractionStart: () => i.onInteractionStart?.(),
106
- resolveTargetRole: (r) => (r.target instanceof Element ? r.target : null)?.closest('button, a, input, select, textarea, [data-floe-canvas-interactive="true"]') ? "local_surface" : oe(r),
107
- onCanvasPointerDown: (r) => {
108
- !(r.target instanceof Element) || r.target.closest("[data-graph-object]") || i.onActivate?.(x(null, g, r));
138
+ resolveTargetRole: (n) => (n.target instanceof Element ? n.target : null)?.closest('button, a, input, select, textarea, [data-floe-canvas-interactive="true"]') ? "local_surface" : se(n),
139
+ onCanvasPointerDown: (n) => {
140
+ !(n.target instanceof Element) || n.target.closest("[data-graph-object]") || i.onActivate?.(k(null, g, n));
109
141
  },
110
- onCanvasContextMenu: (r) => i.onContextMenu?.({
142
+ onCanvasContextMenu: (n) => i.onContextMenu?.({
111
143
  object: null,
112
144
  owner: g,
113
145
  position: {
114
- x: r.clientX,
115
- y: r.clientY
146
+ x: n.clientX,
147
+ y: n.clientY
116
148
  }
117
149
  }),
118
- children: (r) => {
119
- const a = $(() => Z(r())), n = $(() => a().filter((c) => c.kind === "group")), b = $(() => a().filter((c) => c.kind !== "group"));
120
- return [w(D, {
150
+ children: (n) => {
151
+ const r = y(() => te(n())), a = y(() => r().filter((d) => d.kind === "group")), m = y(() => r().filter((d) => d.kind !== "group")), c = y(() => ne(n()));
152
+ return [x(j, {
121
153
  get each() {
122
- return n();
154
+ return a();
123
155
  },
124
- children: G
156
+ children: F
125
157
  }), (() => {
126
- var c = ue(), d = c.firstChild, P = d.firstChild;
127
- return l(P, "id", H), y(c, w(D, {
158
+ var d = be(), _ = d.firstChild, P = _.firstChild;
159
+ return l(P, "id", H), w(d, x(j, {
128
160
  get each() {
129
- return i.layout.edges;
161
+ return c();
130
162
  },
131
- children: (s) => {
132
- const f = {
163
+ children: ($) => {
164
+ const {
165
+ edge: u
166
+ } = $, v = {
133
167
  kind: "edge",
134
- id: s.id
135
- }, C = () => s.sections.map(ce).join(" "), I = () => le(s.sections);
136
- return w(F, {
168
+ id: u.id
169
+ }, W = () => $.path, re = () => $.label;
170
+ return x(D, {
137
171
  get when() {
138
- return !Y() || Y().has(s.id);
172
+ return !Y() || Y().has(u.id);
139
173
  },
140
174
  get children() {
141
- var k = he(), z = k.firstChild, u = z.nextSibling;
142
- return l(z, "marker-end", `url(#${H})`), y(k, w(F, {
175
+ var C = xe(), R = C.firstChild, s = R.nextSibling;
176
+ return l(R, "marker-end", `url(#${H})`), w(C, x(D, {
143
177
  get when() {
144
- return I();
178
+ return re();
145
179
  },
146
180
  children: (o) => (() => {
147
- var m = ge();
148
- return y(m, () => s.label), S((h) => {
149
- var E = /^\d+$/.test(s.label), L = o().x, M = o().y - 6;
150
- return E !== h.e && l(m, "data-count", h.e = E), L !== h.t && l(m, "x", h.t = L), M !== h.a && l(m, "y", h.a = M), h;
181
+ var b = ye();
182
+ return w(b, () => u.label), S((h) => {
183
+ var M = /^\d+$/.test(u.label), E = o().x, L = o().y - 6;
184
+ return M !== h.e && l(b, "data-count", h.e = M), E !== h.t && l(b, "x", h.t = E), L !== h.a && l(b, "y", h.a = L), h;
151
185
  }, {
152
186
  e: void 0,
153
187
  t: void 0,
154
188
  a: void 0
155
- }), m;
189
+ }), b;
156
190
  })()
157
- }), u), u.$$keydown = (o) => j(f, o), u.$$contextmenu = (o) => R(f, o), u.$$click = (o) => {
158
- o.stopPropagation(), i.onActivate?.(x(f, o.currentTarget, o));
159
- }, u.addEventListener("blur", () => v(null)), u.addEventListener("focus", () => v(f)), u.addEventListener("pointerleave", () => v(null)), u.addEventListener("pointerenter", () => v(f)), S((o) => {
160
- var m = s.id, h = Q().has(s.id), E = A(f), L = C(), M = C(), N = s.label;
161
- return m !== o.e && l(k, "data-graph-object", o.e = m), h !== o.t && l(k, "data-emphasized", o.t = h), E !== o.a && l(k, "data-selected", o.a = E), L !== o.o && l(z, "d", o.o = L), M !== o.i && l(u, "d", o.i = M), N !== o.n && l(u, "aria-label", o.n = N), o;
191
+ }), s), s.$$keydown = (o) => G(v, o), s.$$contextmenu = (o) => z(v, o), s.$$click = (o) => {
192
+ o.stopPropagation(), i.onActivate?.(k(v, o.currentTarget, o));
193
+ }, s.addEventListener("blur", () => f(null)), s.addEventListener("focus", () => f(v)), s.addEventListener("pointerleave", () => f(null)), s.addEventListener("pointerenter", () => f(v)), S((o) => {
194
+ var b = u.id, h = ee().has(u.id), M = A(v), E = W(), L = W(), q = u.label;
195
+ return b !== o.e && l(C, "data-graph-object", o.e = b), h !== o.t && l(C, "data-emphasized", o.t = h), M !== o.a && l(C, "data-selected", o.a = M), E !== o.o && l(R, "d", o.o = E), L !== o.i && l(s, "d", o.i = L), q !== o.n && l(s, "aria-label", o.n = q), o;
162
196
  }, {
163
197
  e: void 0,
164
198
  t: void 0,
@@ -166,28 +200,52 @@ function ke(i) {
166
200
  o: void 0,
167
201
  i: void 0,
168
202
  n: void 0
169
- }), k;
203
+ }), C;
170
204
  }
171
205
  });
172
206
  }
173
- }), null), S(() => l(c, "aria-label", i.ariaLabel)), c;
174
- })(), w(D, {
207
+ }), null), S(() => l(d, "aria-label", i.ariaLabel)), d;
208
+ })(), x(j, {
175
209
  get each() {
176
- return b();
210
+ return m();
177
211
  },
178
- children: G
212
+ children: F
179
213
  })];
180
214
  }
181
- }), null), y(e, () => i.overlay, null), S((r) => {
182
- var a = `floe-graph ${i.class ?? ""}`, n = i.ariaLabel;
183
- return a !== r.e && V(e, r.e = a), n !== r.t && l(e, "aria-label", r.t = n), r;
215
+ }), null), w(e, x(D, {
216
+ get when() {
217
+ return i.minimap;
218
+ },
219
+ children: (n) => x(fe, {
220
+ get layout() {
221
+ return i.layout;
222
+ },
223
+ get viewport() {
224
+ return i.viewport;
225
+ },
226
+ get size() {
227
+ return V();
228
+ },
229
+ get onViewportChange() {
230
+ return i.onViewportChange;
231
+ },
232
+ get onInteractionStart() {
233
+ return i.onInteractionStart;
234
+ },
235
+ get ariaLabel() {
236
+ return n().ariaLabel;
237
+ }
238
+ })
239
+ }), null), w(e, () => i.overlay, null), S((n) => {
240
+ var r = `floe-graph ${i.class ?? ""}`, a = i.ariaLabel;
241
+ return r !== n.e && K(e, n.e = r), a !== n.t && l(e, "aria-label", n.t = a), n;
184
242
  }, {
185
243
  e: void 0,
186
244
  t: void 0
187
245
  }), e;
188
246
  })();
189
247
  }
190
- te(["focusin", "focusout", "click", "contextmenu", "keydown"]);
248
+ oe(["focusin", "focusout", "click", "contextmenu", "keydown"]);
191
249
  export {
192
- ke as GraphCanvas
250
+ Le as GraphCanvas
193
251
  };
@@ -0,0 +1,14 @@
1
+ import type { GraphLayout, GraphViewport } from './types';
2
+ export interface GraphMinimapProps {
3
+ layout: GraphLayout;
4
+ viewport: GraphViewport;
5
+ size: {
6
+ width: number;
7
+ height: number;
8
+ };
9
+ onViewportChange: (viewport: GraphViewport) => void;
10
+ onInteractionStart?: () => void;
11
+ ariaLabel: string;
12
+ }
13
+ /** A lightweight navigator over the exact main-canvas geometry. No second layout. */
14
+ export declare function GraphMinimap(props: GraphMinimapProps): import("solid-js").JSX.Element;
@@ -0,0 +1,88 @@
1
+ import { effect as S, setAttribute as d, template as z, use as A, delegateEvents as k } from "solid-js/web";
2
+ import { createMemo as w } from "solid-js";
3
+ import { graphSectionPath as D, fitGraphViewport as L } from "./geometry.js";
4
+ var V = /* @__PURE__ */ z('<svg class=floe-graph__minimap role=region tabindex=0 preserveAspectRatio="xMidYMid meet"data-floe-canvas-interactive=true><path class=floe-graph__minimap-groups></path><path class=floe-graph__minimap-edges></path><path class=floe-graph__minimap-nodes></path><rect class=floe-graph__minimap-viewport>');
5
+ function R(e) {
6
+ let h, n;
7
+ const c = w(() => {
8
+ const {
9
+ x: i,
10
+ y: o,
11
+ width: s,
12
+ height: v
13
+ } = e.layout.bounds, a = Math.max(s, v, 1) * 0.04;
14
+ return {
15
+ x: i - a,
16
+ y: o - a,
17
+ width: s + a * 2,
18
+ height: v + a * 2
19
+ };
20
+ }), g = (i) => e.layout.nodes.filter((o) => (o.kind === "group" ? "group" : "node") === i).map((o) => `M${o.x},${o.y}h${o.width}v${o.height}h${-o.width}Z`).join(""), C = w(() => g("node")), I = w(() => g("group")), P = w(() => e.layout.edges.flatMap((i) => i.sections.map(D)).join(" ")), u = (i) => {
21
+ const o = h?.getScreenCTM();
22
+ return o ? new DOMPoint(i.clientX, i.clientY).matrixTransform(o.inverse()) : void 0;
23
+ }, f = (i) => {
24
+ n?.id === i.pointerId && (n = void 0, h?.hasPointerCapture(i.pointerId) && h.releasePointerCapture(i.pointerId));
25
+ };
26
+ return (() => {
27
+ var i = V(), o = i.firstChild, s = o.nextSibling, v = s.nextSibling, a = v.nextSibling;
28
+ i.$$keydown = (t) => {
29
+ const l = {
30
+ ArrowLeft: [1, 0],
31
+ ArrowRight: [-1, 0],
32
+ ArrowUp: [0, 1],
33
+ ArrowDown: [0, -1]
34
+ }[t.key];
35
+ !l && t.key !== "Home" || (t.preventDefault(), t.stopPropagation(), e.onInteractionStart?.(), e.onViewportChange(l ? {
36
+ ...e.viewport,
37
+ x: e.viewport.x + l[0] * e.size.width * 0.2,
38
+ y: e.viewport.y + l[1] * e.size.height * 0.2
39
+ } : L(e.layout.bounds, e.size, 24)));
40
+ }, i.addEventListener("lostpointercapture", () => {
41
+ n = void 0;
42
+ }), i.addEventListener("pointercancel", f), i.$$pointerup = f, i.$$pointermove = (t) => {
43
+ if (n?.id !== t.pointerId) return;
44
+ const r = u(t);
45
+ r && e.onViewportChange({
46
+ ...n.viewport,
47
+ x: n.viewport.x - (r.x - n.x) * n.viewport.scale,
48
+ y: n.viewport.y - (r.y - n.y) * n.viewport.scale
49
+ });
50
+ }, i.$$pointerdown = (t) => {
51
+ if (t.button !== 0) return;
52
+ const r = u(t);
53
+ r && (t.preventDefault(), t.stopPropagation(), e.onInteractionStart?.(), h.focus({
54
+ preventScroll: !0
55
+ }), t.target.matches(".floe-graph__minimap-viewport") || e.onViewportChange({
56
+ ...e.viewport,
57
+ x: e.size.width / 2 - r.x * e.viewport.scale,
58
+ y: e.size.height / 2 - r.y * e.viewport.scale
59
+ }), n = {
60
+ id: t.pointerId,
61
+ x: r.x,
62
+ y: r.y,
63
+ viewport: {
64
+ ...e.viewport
65
+ }
66
+ }, h.setPointerCapture(t.pointerId));
67
+ };
68
+ var m = h;
69
+ return typeof m == "function" ? A(m, i) : h = i, S((t) => {
70
+ var r = e.ariaLabel, l = `${c().x} ${c().y} ${c().width} ${c().height}`, x = I(), y = P(), p = C(), $ = -e.viewport.x / e.viewport.scale, b = -e.viewport.y / e.viewport.scale, _ = e.size.width / e.viewport.scale, M = e.size.height / e.viewport.scale;
71
+ return r !== t.e && d(i, "aria-label", t.e = r), l !== t.t && d(i, "viewBox", t.t = l), x !== t.a && d(o, "d", t.a = x), y !== t.o && d(s, "d", t.o = y), p !== t.i && d(v, "d", t.i = p), $ !== t.n && d(a, "x", t.n = $), b !== t.s && d(a, "y", t.s = b), _ !== t.h && d(a, "width", t.h = _), M !== t.r && d(a, "height", t.r = M), t;
72
+ }, {
73
+ e: void 0,
74
+ t: void 0,
75
+ a: void 0,
76
+ o: void 0,
77
+ i: void 0,
78
+ n: void 0,
79
+ s: void 0,
80
+ h: void 0,
81
+ r: void 0
82
+ }), i;
83
+ })();
84
+ }
85
+ k(["pointerdown", "pointermove", "pointerup", "keydown"]);
86
+ export {
87
+ R as GraphMinimap
88
+ };
@@ -1,28 +1,34 @@
1
- import m from "../../_virtual/elk-api.js";
2
- import { computeGraphLayout as u } from "./layout.js";
3
- function c() {
4
- const r = new Worker(new URL(
5
- /* @vite-ignore */
6
- "" + new URL("../../assets/elk-worker-CrDP6jPz.js", import.meta.url).href,
7
- import.meta.url
8
- ), { type: "module" }), p = new m({ workerFactory: () => r });
9
- let o;
10
- const t = /* @__PURE__ */ new Set(), n = (e) => {
11
- o = e, r.terminate();
12
- for (const a of t) a.reject(e);
13
- t.clear();
1
+ function w() {
2
+ const t = new Worker(new URL("../../assets/layout-worker-BK6m515B.js", import.meta.url), { type: "module" });
3
+ let s, u = 0;
4
+ const o = /* @__PURE__ */ new Map(), n = (e) => {
5
+ s = e, t.terminate();
6
+ for (const r of o.values()) r.reject(e);
7
+ o.clear();
14
8
  };
15
- return r.addEventListener(
9
+ return t.addEventListener(
16
10
  "error",
17
11
  (e) => n(new Error(e.message || "Graph layout worker failed"))
18
- ), r.addEventListener(
12
+ ), t.addEventListener(
19
13
  "messageerror",
20
14
  () => n(new Error("Invalid graph layout worker response"))
21
- ), {
22
- layout(e, a) {
23
- return o ? Promise.reject(o) : new Promise((l, s) => {
24
- const i = { reject: s };
25
- t.add(i), u(e, a ?? {}, p).then(l, s).finally(() => t.delete(i));
15
+ ), t.addEventListener("message", ({ data: e }) => {
16
+ if (!e || !Number.isInteger(e.id) || !e.error && !e.layout) {
17
+ n(new Error("Invalid graph layout worker response"));
18
+ return;
19
+ }
20
+ const r = o.get(e.id);
21
+ r && (o.delete(e.id), e.error ? r.reject(new Error(e.error)) : r.resolve(e.layout));
22
+ }), {
23
+ layout(e, r) {
24
+ return s ? Promise.reject(s) : new Promise((a, l) => {
25
+ const i = ++u;
26
+ o.set(i, { resolve: a, reject: l });
27
+ try {
28
+ t.postMessage({ id: i, input: e, options: r ?? {} });
29
+ } catch (p) {
30
+ o.delete(i), l(p);
31
+ }
26
32
  });
27
33
  },
28
34
  dispose() {
@@ -31,5 +37,5 @@ function c() {
31
37
  };
32
38
  }
33
39
  export {
34
- c as createGraphLayoutEngine
40
+ w as createGraphLayoutEngine
35
41
  };
@@ -0,0 +1 @@
1
+ export {};
package/dist/graph.css CHANGED
@@ -69,6 +69,57 @@
69
69
  stroke: var(--graph-edge-active, var(--foreground));
70
70
  stroke-width: 3px;
71
71
  }
72
+ .floe-graph__minimap {
73
+ position: absolute;
74
+ right: 16px;
75
+ bottom: 16px;
76
+ z-index: 4;
77
+ width: clamp(112px, 20vw, 196px);
78
+ height: clamp(72px, 12vw, 120px);
79
+ border: 1px solid var(--border);
80
+ border-radius: 9px;
81
+ background: color-mix(in srgb, var(--card) 94%, transparent);
82
+ box-shadow: 0 3px 14px rgb(0 0 0 / 8%);
83
+ cursor: crosshair;
84
+ touch-action: none;
85
+ user-select: none;
86
+ overflow: hidden;
87
+ }
88
+ .floe-graph__minimap:focus-visible {
89
+ outline: 2px solid var(--ring);
90
+ outline-offset: 2px;
91
+ }
92
+ .floe-graph__minimap-nodes {
93
+ fill: var(--muted-foreground);
94
+ opacity: 0.65;
95
+ pointer-events: none;
96
+ }
97
+ .floe-graph__minimap-groups {
98
+ fill: color-mix(in srgb, var(--primary) 10%, transparent);
99
+ stroke: var(--muted-foreground);
100
+ stroke-width: 0.7px;
101
+ vector-effect: non-scaling-stroke;
102
+ pointer-events: none;
103
+ }
104
+ .floe-graph__minimap-edges {
105
+ fill: none;
106
+ stroke: var(--muted-foreground);
107
+ stroke-width: 0.5px;
108
+ vector-effect: non-scaling-stroke;
109
+ opacity: 0.35;
110
+ pointer-events: none;
111
+ }
112
+ .floe-graph__minimap-viewport {
113
+ fill: color-mix(in srgb, var(--primary) 12%, transparent);
114
+ stroke: var(--primary);
115
+ stroke-width: 1.5px;
116
+ vector-effect: non-scaling-stroke;
117
+ cursor: grab;
118
+ }
119
+ .floe-graph__minimap:active,
120
+ .floe-graph__minimap:active .floe-graph__minimap-viewport {
121
+ cursor: grabbing;
122
+ }
72
123
  .floe-graph__node:focus-visible,
73
124
  .floe-graph__group:focus-visible {
74
125
  outline: 2px solid var(--ring);
@@ -90,6 +141,21 @@
90
141
  .floe-graph__group:focus-visible {
91
142
  outline-color: Highlight;
92
143
  }
144
+ .floe-graph__minimap {
145
+ background: Canvas;
146
+ border-color: CanvasText;
147
+ }
148
+ .floe-graph__minimap-nodes {
149
+ fill: CanvasText;
150
+ }
151
+ .floe-graph__minimap-groups,
152
+ .floe-graph__minimap-edges {
153
+ stroke: CanvasText;
154
+ }
155
+ .floe-graph__minimap-viewport {
156
+ stroke: Highlight;
157
+ fill: transparent;
158
+ }
93
159
  }
94
160
 
95
161
  .floe-graph__edge-label {
package/dist/graph.d.ts CHANGED
@@ -2,3 +2,4 @@ export * from './components/graph/types';
2
2
  export * from './components/graph/geometry';
3
3
  export * from './components/graph/engine';
4
4
  export * from './components/graph/GraphCanvas';
5
+ export * from './components/graph/GraphMinimap';
package/dist/graph.js CHANGED
@@ -1,11 +1,13 @@
1
- import { fitGraphViewport as e, graphEdgeLabelPosition as o, graphRelatedEdges as t, graphSectionPath as p } from "./components/graph/geometry.js";
2
- import { createGraphLayoutEngine as g } from "./components/graph/engine.js";
1
+ import { fitGraphViewport as e, graphEdgeLabelPosition as o, graphRelatedEdges as p, graphSectionPath as t } from "./components/graph/geometry.js";
2
+ import { createGraphLayoutEngine as i } from "./components/graph/engine.js";
3
3
  import { GraphCanvas as n } from "./components/graph/GraphCanvas.js";
4
+ import { GraphMinimap as m } from "./components/graph/GraphMinimap.js";
4
5
  export {
5
6
  n as GraphCanvas,
6
- g as createGraphLayoutEngine,
7
+ m as GraphMinimap,
8
+ i as createGraphLayoutEngine,
7
9
  e as fitGraphViewport,
8
10
  o as graphEdgeLabelPosition,
9
- t as graphRelatedEdges,
10
- p as graphSectionPath
11
+ p as graphRelatedEdges,
12
+ t as graphSectionPath
11
13
  };