@carlonicora/nextjs-jsonapi 1.99.0 → 1.100.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BlockNoteEditor-IBV3KBQM.mjs → BlockNoteEditor-QH3NPFYN.mjs} +2 -2
- package/dist/{BlockNoteEditor-LYJUF5N4.js → BlockNoteEditor-ZD2AMN72.js} +9 -9
- package/dist/{BlockNoteEditor-LYJUF5N4.js.map → BlockNoteEditor-ZD2AMN72.js.map} +1 -1
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-TRTKIQUB.js → chunk-LIZ5C76W.js} +300 -224
- package/dist/chunk-LIZ5C76W.js.map +1 -0
- package/dist/{chunk-CDNVUON3.mjs → chunk-S34H33HJ.mjs} +82 -6
- package/dist/{chunk-CDNVUON3.mjs.map → chunk-S34H33HJ.mjs.map} +1 -1
- package/dist/client/index.d.mts +17 -1
- package/dist/client/index.d.ts +17 -1
- package/dist/client/index.js +4 -2
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -1
- package/dist/components/index.js +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/package.json +1 -1
- package/src/hooks/__tests__/computeLayeredLayout.spec.ts +135 -1
- package/src/hooks/computeLayeredLayout.ts +126 -13
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCustomD3Graph.tsx +20 -8
- package/dist/chunk-TRTKIQUB.js.map +0 -1
- /package/dist/{BlockNoteEditor-IBV3KBQM.mjs.map → BlockNoteEditor-QH3NPFYN.mjs.map} +0 -0
|
@@ -16,10 +16,25 @@ export interface LayeredLayoutPosition {
|
|
|
16
16
|
y: number;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
export interface FitLayeredLayoutOptions extends LayeredLayoutOptions {
|
|
20
|
+
targetAspectRatio: number;
|
|
21
|
+
maxIterations?: number;
|
|
22
|
+
tolerance?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
const DEFAULT_RANKDIR: LayeredRankDir = "LR";
|
|
20
26
|
const DEFAULT_NODESEP = 50;
|
|
21
27
|
const DEFAULT_RANKSEP = 120;
|
|
22
28
|
|
|
29
|
+
const MIN_NODESEP = 20;
|
|
30
|
+
const MAX_NODESEP = 400;
|
|
31
|
+
const MIN_RANKSEP = 40;
|
|
32
|
+
const MAX_RANKSEP = 600;
|
|
33
|
+
const MIN_FACTOR = 0.25;
|
|
34
|
+
const MAX_FACTOR = 4;
|
|
35
|
+
const DEFAULT_TOLERANCE = 0.05;
|
|
36
|
+
const DEFAULT_MAX_ITERATIONS = 4;
|
|
37
|
+
|
|
23
38
|
const TITLE_PX_PER_CHAR_16 = 8;
|
|
24
39
|
const NAME_PX_PER_CHAR_12 = 6.5;
|
|
25
40
|
const NAME_PX_PER_CHAR_16_BOLD = 8;
|
|
@@ -40,20 +55,22 @@ function linkEndpointId(end: D3Link["source"] | D3Link["target"]): string {
|
|
|
40
55
|
return typeof end === "string" ? end : end.id;
|
|
41
56
|
}
|
|
42
57
|
|
|
58
|
+
function clamp(value: number, min: number, max: number): number {
|
|
59
|
+
return Math.min(Math.max(value, min), max);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface DagreRunResult {
|
|
63
|
+
positions: Map<string, LayeredLayoutPosition>;
|
|
64
|
+
bbox: { width: number; height: number };
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* should fall back to their previous layout in that case.
|
|
68
|
+
* Run dagre once for the given nodes/links/opts and return both the
|
|
69
|
+
* computed positions and the bounding box of the laid-out graph.
|
|
70
|
+
* Internal helper — callers should use `computeLayeredLayout` or
|
|
71
|
+
* `fitLayeredLayoutToAspectRatio`.
|
|
49
72
|
*/
|
|
50
|
-
|
|
51
|
-
nodes: D3Node[],
|
|
52
|
-
links: D3Link[],
|
|
53
|
-
opts: LayeredLayoutOptions,
|
|
54
|
-
): Map<string, LayeredLayoutPosition> | null {
|
|
55
|
-
if (nodes.length === 0) return new Map();
|
|
56
|
-
|
|
73
|
+
function runDagreOnce(nodes: D3Node[], links: D3Link[], opts: LayeredLayoutOptions): DagreRunResult | null {
|
|
57
74
|
const rankdir = opts.rankdir ?? DEFAULT_RANKDIR;
|
|
58
75
|
const nodesep = opts.nodesep ?? DEFAULT_NODESEP;
|
|
59
76
|
const ranksep = opts.ranksep ?? DEFAULT_RANKSEP;
|
|
@@ -86,11 +103,107 @@ export function computeLayeredLayout(
|
|
|
86
103
|
}
|
|
87
104
|
|
|
88
105
|
const positions = new Map<string, LayeredLayoutPosition>();
|
|
106
|
+
let xMin = Infinity;
|
|
107
|
+
let xMax = -Infinity;
|
|
108
|
+
let yMin = Infinity;
|
|
109
|
+
let yMax = -Infinity;
|
|
110
|
+
|
|
89
111
|
for (const node of nodes) {
|
|
90
112
|
const laid = g.node(node.id);
|
|
91
113
|
if (laid && Number.isFinite(laid.x) && Number.isFinite(laid.y)) {
|
|
92
114
|
positions.set(node.id, { x: laid.x, y: laid.y });
|
|
115
|
+
const halfW = (laid.width ?? opts.minNodeWidth) / 2;
|
|
116
|
+
const halfH = (laid.height ?? opts.minNodeHeight) / 2;
|
|
117
|
+
xMin = Math.min(xMin, laid.x - halfW);
|
|
118
|
+
xMax = Math.max(xMax, laid.x + halfW);
|
|
119
|
+
yMin = Math.min(yMin, laid.y - halfH);
|
|
120
|
+
yMax = Math.max(yMax, laid.y + halfH);
|
|
93
121
|
}
|
|
94
122
|
}
|
|
95
|
-
|
|
123
|
+
|
|
124
|
+
const bbox =
|
|
125
|
+
positions.size === 0
|
|
126
|
+
? { width: 0, height: 0 }
|
|
127
|
+
: { width: Math.max(0, xMax - xMin), height: Math.max(0, yMax - yMin) };
|
|
128
|
+
|
|
129
|
+
return { positions, bbox };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Compute a layered DAG layout using dagre. Pure function: no DOM, no React,
|
|
134
|
+
* no d3 globals. Returns a Map of node.id -> { x, y } in graph coordinates.
|
|
135
|
+
*
|
|
136
|
+
* Returns null if dagre.layout throws (e.g. an unexpected cycle). Callers
|
|
137
|
+
* should fall back to their previous layout in that case.
|
|
138
|
+
*/
|
|
139
|
+
export function computeLayeredLayout(
|
|
140
|
+
nodes: D3Node[],
|
|
141
|
+
links: D3Link[],
|
|
142
|
+
opts: LayeredLayoutOptions,
|
|
143
|
+
): Map<string, LayeredLayoutPosition> | null {
|
|
144
|
+
if (nodes.length === 0) return new Map();
|
|
145
|
+
const result = runDagreOnce(nodes, links, opts);
|
|
146
|
+
return result ? result.positions : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Compute a layered layout, then iteratively re-run dagre with adjusted
|
|
151
|
+
* `nodesep`/`ranksep` until the bounding-box aspect ratio is within
|
|
152
|
+
* `tolerance` of `targetAspectRatio` (or `maxIterations` is reached).
|
|
153
|
+
*
|
|
154
|
+
* Degenerate cases (empty graph, single-rank graph where one axis has
|
|
155
|
+
* zero extent, missing target ratio) skip fitting and return the
|
|
156
|
+
* single-pass result.
|
|
157
|
+
*/
|
|
158
|
+
export function fitLayeredLayoutToAspectRatio(
|
|
159
|
+
nodes: D3Node[],
|
|
160
|
+
links: D3Link[],
|
|
161
|
+
opts: FitLayeredLayoutOptions,
|
|
162
|
+
): Map<string, LayeredLayoutPosition> | null {
|
|
163
|
+
if (nodes.length === 0) return new Map();
|
|
164
|
+
if (!Number.isFinite(opts.targetAspectRatio) || opts.targetAspectRatio <= 0) {
|
|
165
|
+
return computeLayeredLayout(nodes, links, opts);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const maxIterations = opts.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
169
|
+
const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE;
|
|
170
|
+
const rankdir = opts.rankdir ?? DEFAULT_RANKDIR;
|
|
171
|
+
const isHorizontalFlow = rankdir === "LR" || rankdir === "RL";
|
|
172
|
+
|
|
173
|
+
let nodesep = opts.nodesep ?? DEFAULT_NODESEP;
|
|
174
|
+
let ranksep = opts.ranksep ?? DEFAULT_RANKSEP;
|
|
175
|
+
|
|
176
|
+
let best: DagreRunResult | null = null;
|
|
177
|
+
|
|
178
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
179
|
+
const result = runDagreOnce(nodes, links, {
|
|
180
|
+
...opts,
|
|
181
|
+
nodesep,
|
|
182
|
+
ranksep,
|
|
183
|
+
});
|
|
184
|
+
if (!result) return best ? best.positions : null;
|
|
185
|
+
best = result;
|
|
186
|
+
|
|
187
|
+
if (result.positions.size <= 1) return result.positions;
|
|
188
|
+
|
|
189
|
+
const { width, height } = result.bbox;
|
|
190
|
+
if (width === 0 || height === 0) return result.positions;
|
|
191
|
+
|
|
192
|
+
const currentAspect = width / height;
|
|
193
|
+
const ratio = opts.targetAspectRatio / currentAspect;
|
|
194
|
+
|
|
195
|
+
if (Math.abs(ratio - 1) < tolerance) return result.positions;
|
|
196
|
+
|
|
197
|
+
const factor = clamp(Math.sqrt(ratio), MIN_FACTOR, MAX_FACTOR);
|
|
198
|
+
|
|
199
|
+
if (isHorizontalFlow) {
|
|
200
|
+
ranksep = clamp(ranksep * factor, MIN_RANKSEP, MAX_RANKSEP);
|
|
201
|
+
nodesep = clamp(nodesep / factor, MIN_NODESEP, MAX_NODESEP);
|
|
202
|
+
} else {
|
|
203
|
+
ranksep = clamp(ranksep / factor, MIN_RANKSEP, MAX_RANKSEP);
|
|
204
|
+
nodesep = clamp(nodesep * factor, MIN_NODESEP, MAX_NODESEP);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return best ? best.positions : null;
|
|
96
209
|
}
|
package/src/hooks/index.ts
CHANGED
|
@@ -37,6 +37,8 @@ export * from "./usePushNotifications";
|
|
|
37
37
|
export * from "./useSocket";
|
|
38
38
|
export {
|
|
39
39
|
computeLayeredLayout,
|
|
40
|
+
fitLayeredLayoutToAspectRatio,
|
|
41
|
+
type FitLayeredLayoutOptions,
|
|
40
42
|
type LayeredLayoutOptions,
|
|
41
43
|
type LayeredLayoutPosition,
|
|
42
44
|
type LayeredRankDir,
|
|
@@ -5,7 +5,7 @@ import { Loader2 } from "lucide-react";
|
|
|
5
5
|
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
6
6
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
7
7
|
import { D3Link, D3Node } from "../interfaces";
|
|
8
|
-
import { computeLayeredLayout, type LayeredRankDir } from "./computeLayeredLayout";
|
|
8
|
+
import { computeLayeredLayout, fitLayeredLayoutToAspectRatio, type LayeredRankDir } from "./computeLayeredLayout";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Custom hook for D3 graph visualization with larger circles and more interactive features
|
|
@@ -22,6 +22,7 @@ export function useCustomD3Graph(
|
|
|
22
22
|
rankdir?: LayeredRankDir;
|
|
23
23
|
nodesep?: number;
|
|
24
24
|
ranksep?: number;
|
|
25
|
+
fitContainer?: boolean;
|
|
25
26
|
};
|
|
26
27
|
},
|
|
27
28
|
loadingNodeIds?: Set<string>,
|
|
@@ -244,13 +245,23 @@ export function useCustomD3Graph(
|
|
|
244
245
|
|
|
245
246
|
if (layoutMode === "layered") {
|
|
246
247
|
const layeredOpts = options?.layered ?? {};
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
248
|
+
const useFit = layeredOpts.fitContainer === true && width > 0 && height > 0;
|
|
249
|
+
const positions = useFit
|
|
250
|
+
? fitLayeredLayoutToAspectRatio(visibleNodes, visibleLinks, {
|
|
251
|
+
rankdir: layeredOpts.rankdir ?? "LR",
|
|
252
|
+
nodesep: layeredOpts.nodesep,
|
|
253
|
+
ranksep: layeredOpts.ranksep,
|
|
254
|
+
minNodeWidth: nodeRadius * 2,
|
|
255
|
+
minNodeHeight: nodeRadius * 2,
|
|
256
|
+
targetAspectRatio: width / height,
|
|
257
|
+
})
|
|
258
|
+
: computeLayeredLayout(visibleNodes, visibleLinks, {
|
|
259
|
+
rankdir: layeredOpts.rankdir ?? "LR",
|
|
260
|
+
nodesep: layeredOpts.nodesep,
|
|
261
|
+
ranksep: layeredOpts.ranksep,
|
|
262
|
+
minNodeWidth: nodeRadius * 2,
|
|
263
|
+
minNodeHeight: nodeRadius * 2,
|
|
264
|
+
});
|
|
254
265
|
|
|
255
266
|
if (positions) {
|
|
256
267
|
visibleNodes.forEach((node) => {
|
|
@@ -785,6 +796,7 @@ export function useCustomD3Graph(
|
|
|
785
796
|
options?.layered?.rankdir,
|
|
786
797
|
options?.layered?.nodesep,
|
|
787
798
|
options?.layered?.ranksep,
|
|
799
|
+
options?.layered?.fitContainer,
|
|
788
800
|
loadingNodeIds,
|
|
789
801
|
onNodeClick,
|
|
790
802
|
]);
|