@matfsw/docusaurus-plantuml-plugin 1.3.1 → 1.4.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/CHANGELOG.md +71 -1
- package/README.md +106 -13
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +10 -0
- package/dist/runtime/sanitize.js +5 -1
- package/dist/theme/PlantUmlDiagram/Minimap.d.ts +9 -0
- package/dist/theme/PlantUmlDiagram/Minimap.js +137 -0
- package/dist/theme/PlantUmlDiagram/deeplink.d.ts +55 -0
- package/dist/theme/PlantUmlDiagram/deeplink.js +149 -0
- package/dist/theme/PlantUmlDiagram/icons.d.ts +13 -0
- package/dist/theme/PlantUmlDiagram/icons.js +23 -0
- package/dist/theme/PlantUmlDiagram/index.js +71 -6
- package/dist/theme/PlantUmlDiagram/styles.module.css +180 -2
- package/dist/theme/PlantUmlDiagram/useDiagramDeeplink.d.ts +32 -0
- package/dist/theme/PlantUmlDiagram/useDiagramDeeplink.js +54 -0
- package/dist/theme/PlantUmlDiagram/useDiagramSearch.d.ts +37 -0
- package/dist/theme/PlantUmlDiagram/useDiagramSearch.js +107 -0
- package/dist/theme/PlantUmlDiagram/useZoomPan.d.ts +17 -1
- package/dist/theme/PlantUmlDiagram/useZoomPan.js +66 -6
- package/dist/theme/PlantUmlDiagram/zoomMath.d.ts +28 -0
- package/dist/theme/PlantUmlDiagram/zoomMath.js +42 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type KeyboardEvent as ReactKeyboardEvent, type MutableRefObject } from 'react';
|
|
2
|
-
import { MAX_SCALE, MIN_SCALE } from './zoomMath.js';
|
|
2
|
+
import { MAX_SCALE, MIN_SCALE, type Bounds, type Transform } from './zoomMath.js';
|
|
3
3
|
export interface ZoomPanHandle {
|
|
4
4
|
viewportRef: MutableRefObject<HTMLDivElement | null>;
|
|
5
5
|
layerRef: MutableRefObject<HTMLDivElement | null>;
|
|
@@ -8,10 +8,26 @@ export interface ZoomPanHandle {
|
|
|
8
8
|
zoomIn: () => void;
|
|
9
9
|
zoomOut: () => void;
|
|
10
10
|
reset: () => void;
|
|
11
|
+
/** Fits the whole diagram inside the viewport, at whatever scale that takes to fill it. */
|
|
12
|
+
fit: () => void;
|
|
11
13
|
toggleMaximize: () => void;
|
|
12
14
|
onKeyDown: (event: ReactKeyboardEvent<HTMLDivElement>) => void;
|
|
13
15
|
/** Whether the diagram currently fills the browser viewport. */
|
|
14
16
|
maximized: boolean;
|
|
17
|
+
/** Reads the live transform without subscribing to it. */
|
|
18
|
+
getTransform: () => Transform;
|
|
19
|
+
/** Applies a transform through the same clamp as every gesture. */
|
|
20
|
+
applyTransform: (next: Transform) => void;
|
|
21
|
+
/** Layout sizes of the viewport and content — the bounds the clamp runs against. */
|
|
22
|
+
measure: () => Bounds;
|
|
23
|
+
/**
|
|
24
|
+
* Calls the listener after every committed transform write; returns the unsubscribe.
|
|
25
|
+
*
|
|
26
|
+
* This is how the minimap tracks the view: the transform is written straight to the DOM on
|
|
27
|
+
* every pan frame, so anything that mirrored it through React state would re-render the
|
|
28
|
+
* component once per frame.
|
|
29
|
+
*/
|
|
30
|
+
subscribe: (listener: (transform: Transform) => void) => () => void;
|
|
15
31
|
}
|
|
16
32
|
export interface UseZoomPanParams {
|
|
17
33
|
enabled: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, useState, } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState, } from 'react';
|
|
2
2
|
import { clampTransform, fitScale, formatPercent, formatZoom, IDENTITY, MAX_SCALE, MIN_SCALE, normalizeWheelDelta, panBy, PAN_STEP, SCALE_STEP, toCssTransform, wheelZoomFactor, zoomAbout, } from './zoomMath.js';
|
|
3
3
|
/**
|
|
4
4
|
* Zoom, pan and maximize for one diagram.
|
|
@@ -19,12 +19,17 @@ export function useZoomPan({ enabled, resetKey }) {
|
|
|
19
19
|
const readoutRef = useRef(null);
|
|
20
20
|
const [maximized, setMaximized] = useState(false);
|
|
21
21
|
const transformRef = useRef(IDENTITY);
|
|
22
|
+
const listenersRef = useRef(new Set());
|
|
22
23
|
/** The view to restore when the diagram is un-maximized. */
|
|
23
24
|
const beforeMaximizeRef = useRef(null);
|
|
24
25
|
const frameRef = useRef(null);
|
|
25
26
|
/**
|
|
26
27
|
* Layout sizes, never `getBoundingClientRect()`: the layer's rect includes the transform we
|
|
27
|
-
* are about to change, which would feed back into the
|
|
28
|
+
* are about to change — and, mid-easing, the transition — which would feed back into the
|
|
29
|
+
* next measurement. The layer is `width: fit-content`, so its layout size *is* the rendered
|
|
30
|
+
* picture's, not the full-width frame's; that is what lets fitting fill a maximized screen
|
|
31
|
+
* and keeps the minimap honest about where the diagram ends. The clamp deliberately runs
|
|
32
|
+
* against wider bounds — see {@link clampBounds}.
|
|
28
33
|
*/
|
|
29
34
|
const measure = useCallback(() => {
|
|
30
35
|
const viewport = viewportRef.current;
|
|
@@ -36,6 +41,20 @@ export function useZoomPan({ enabled, resetKey }) {
|
|
|
36
41
|
contentHeight: layer?.offsetHeight ?? 0,
|
|
37
42
|
};
|
|
38
43
|
}, []);
|
|
44
|
+
/**
|
|
45
|
+
* The bounds the clamp runs against: the picture or the viewport, whichever is *wider*.
|
|
46
|
+
*
|
|
47
|
+
* Fitting wants the true picture size; the clamp deliberately does not. Focal zoom must
|
|
48
|
+
* hold the point under the pointer even when that slides the picture's right edge inside
|
|
49
|
+
* the viewport — clamping to the picture itself yanks the diagram sideways mid-gesture the
|
|
50
|
+
* moment that edge is reached. Flooring the width at the viewport's restores exactly the
|
|
51
|
+
* geometry the full-width layer always gave. Height is not floored: the layer never
|
|
52
|
+
* stretched vertically, so the height clamp has always run against the real picture.
|
|
53
|
+
*/
|
|
54
|
+
const clampBounds = useCallback(() => {
|
|
55
|
+
const bounds = measure();
|
|
56
|
+
return { ...bounds, contentWidth: Math.max(bounds.contentWidth, bounds.viewportWidth) };
|
|
57
|
+
}, [measure]);
|
|
39
58
|
const write = useCallback((next) => {
|
|
40
59
|
transformRef.current = next;
|
|
41
60
|
const layer = layerRef.current;
|
|
@@ -47,10 +66,19 @@ export function useZoomPan({ enabled, resetKey }) {
|
|
|
47
66
|
const readout = readoutRef.current;
|
|
48
67
|
if (readout)
|
|
49
68
|
readout.textContent = formatPercent(next.k);
|
|
69
|
+
for (const listener of listenersRef.current)
|
|
70
|
+
listener(next);
|
|
71
|
+
}, []);
|
|
72
|
+
const subscribe = useCallback((listener) => {
|
|
73
|
+
listenersRef.current.add(listener);
|
|
74
|
+
return () => {
|
|
75
|
+
listenersRef.current.delete(listener);
|
|
76
|
+
};
|
|
50
77
|
}, []);
|
|
78
|
+
const getTransform = useCallback(() => transformRef.current, []);
|
|
51
79
|
const apply = useCallback((next) => {
|
|
52
|
-
write(clampTransform(next,
|
|
53
|
-
}, [
|
|
80
|
+
write(clampTransform(next, clampBounds()));
|
|
81
|
+
}, [clampBounds, write]);
|
|
54
82
|
const reset = useCallback(() => {
|
|
55
83
|
apply(IDENTITY);
|
|
56
84
|
}, [apply]);
|
|
@@ -70,6 +98,18 @@ export function useZoomPan({ enabled, resetKey }) {
|
|
|
70
98
|
}, [apply]);
|
|
71
99
|
const zoomIn = useCallback(() => zoomByStep(SCALE_STEP), [zoomByStep]);
|
|
72
100
|
const zoomOut = useCallback(() => zoomByStep(1 / SCALE_STEP), [zoomByStep]);
|
|
101
|
+
/**
|
|
102
|
+
* The fitted view maximizing opens with: the diagram scaled to fill the screen, magnified
|
|
103
|
+
* or shrunk as the picture demands, capped at {@link MAX_SCALE}.
|
|
104
|
+
*
|
|
105
|
+
* Only offered while maximized. Inline it would duplicate Reset, because the inline frame
|
|
106
|
+
* grows with the diagram — at 100% everything is already visible, so there is nothing for
|
|
107
|
+
* a fit to add. Translation returns to the origin because a diagram that fits is
|
|
108
|
+
* left-aligned, exactly as `clampTransform` would force anyway.
|
|
109
|
+
*/
|
|
110
|
+
const fit = useCallback(() => {
|
|
111
|
+
apply({ ...IDENTITY, k: fitScale(measure()) });
|
|
112
|
+
}, [apply, measure]);
|
|
73
113
|
const toggleMaximize = useCallback(() => {
|
|
74
114
|
setMaximized((previous) => !previous);
|
|
75
115
|
}, []);
|
|
@@ -309,7 +349,10 @@ export function useZoomPan({ enabled, resetKey }) {
|
|
|
309
349
|
if (!enabled)
|
|
310
350
|
setMaximized(false);
|
|
311
351
|
}, [enabled]);
|
|
312
|
-
|
|
352
|
+
// One stable object per set of values: consumers hang effects off the handle (the search
|
|
353
|
+
// hook, the minimap), and an object recreated every render would re-run all of them — with
|
|
354
|
+
// visible consequences, such as a search that resets to its first match on any re-render.
|
|
355
|
+
return useMemo(() => ({
|
|
313
356
|
stageRef,
|
|
314
357
|
viewportRef,
|
|
315
358
|
layerRef,
|
|
@@ -317,9 +360,26 @@ export function useZoomPan({ enabled, resetKey }) {
|
|
|
317
360
|
zoomIn,
|
|
318
361
|
zoomOut,
|
|
319
362
|
reset,
|
|
363
|
+
fit,
|
|
364
|
+
toggleMaximize,
|
|
365
|
+
onKeyDown,
|
|
366
|
+
maximized,
|
|
367
|
+
getTransform,
|
|
368
|
+
applyTransform: apply,
|
|
369
|
+
measure,
|
|
370
|
+
subscribe,
|
|
371
|
+
}), [
|
|
372
|
+
zoomIn,
|
|
373
|
+
zoomOut,
|
|
374
|
+
reset,
|
|
375
|
+
fit,
|
|
320
376
|
toggleMaximize,
|
|
321
377
|
onKeyDown,
|
|
322
378
|
maximized,
|
|
323
|
-
|
|
379
|
+
getTransform,
|
|
380
|
+
apply,
|
|
381
|
+
measure,
|
|
382
|
+
subscribe,
|
|
383
|
+
]);
|
|
324
384
|
}
|
|
325
385
|
export { MAX_SCALE, MIN_SCALE };
|
|
@@ -67,6 +67,34 @@ export declare function wheelZoomFactor(normalizedDelta: number): number;
|
|
|
67
67
|
* `max`, and never below {@link MIN_SCALE}.
|
|
68
68
|
*/
|
|
69
69
|
export declare function fitScale(bounds: Bounds, max?: number): number;
|
|
70
|
+
export interface Rect {
|
|
71
|
+
x: number;
|
|
72
|
+
y: number;
|
|
73
|
+
width: number;
|
|
74
|
+
height: number;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Scale of the minimap image: the largest scale that fits the content into the given box,
|
|
78
|
+
* but never past 1 — a small diagram is shown at its own size, not blown up into a blurrier
|
|
79
|
+
* copy of itself sitting next to the original.
|
|
80
|
+
*
|
|
81
|
+
* Returns 0 when there is nothing to draw, which callers treat as "no minimap".
|
|
82
|
+
*/
|
|
83
|
+
export declare function minimapScale(bounds: Bounds, maxWidth: number, maxHeight: number): number;
|
|
84
|
+
/**
|
|
85
|
+
* The part of the content the viewport currently shows, in content coordinates.
|
|
86
|
+
*
|
|
87
|
+
* Clamped to the content box, so the minimap's viewport rectangle can never poke outside the
|
|
88
|
+
* picture — not even mid-gesture, when the transform itself may be momentarily out of range.
|
|
89
|
+
*/
|
|
90
|
+
export declare function visibleContentRect(transform: Transform, bounds: Bounds): Rect;
|
|
91
|
+
/**
|
|
92
|
+
* The transform that centres the viewport on a content point, at the current scale.
|
|
93
|
+
*
|
|
94
|
+
* Deliberately unclamped: callers apply it through the same clamp as every gesture, which
|
|
95
|
+
* pins edge cases exactly the way the end of a drag is pinned.
|
|
96
|
+
*/
|
|
97
|
+
export declare function centerViewportOn(transform: Transform, contentX: number, contentY: number, bounds: Bounds): Transform;
|
|
70
98
|
export declare function toCssTransform(transform: Transform): string;
|
|
71
99
|
/** Machine-readable scale for `data-plantuml-zoom`, used by tests and author CSS. */
|
|
72
100
|
export declare function formatZoom(scale: number): string;
|
|
@@ -94,6 +94,48 @@ export function fitScale(bounds, max = MAX_SCALE) {
|
|
|
94
94
|
return 1;
|
|
95
95
|
return clamp(scale, MIN_SCALE, max);
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Scale of the minimap image: the largest scale that fits the content into the given box,
|
|
99
|
+
* but never past 1 — a small diagram is shown at its own size, not blown up into a blurrier
|
|
100
|
+
* copy of itself sitting next to the original.
|
|
101
|
+
*
|
|
102
|
+
* Returns 0 when there is nothing to draw, which callers treat as "no minimap".
|
|
103
|
+
*/
|
|
104
|
+
export function minimapScale(bounds, maxWidth, maxHeight) {
|
|
105
|
+
if (bounds.contentWidth <= 0 || bounds.contentHeight <= 0)
|
|
106
|
+
return 0;
|
|
107
|
+
const scale = Math.min(maxWidth / bounds.contentWidth, maxHeight / bounds.contentHeight, 1);
|
|
108
|
+
return Number.isFinite(scale) && scale > 0 ? scale : 0;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The part of the content the viewport currently shows, in content coordinates.
|
|
112
|
+
*
|
|
113
|
+
* Clamped to the content box, so the minimap's viewport rectangle can never poke outside the
|
|
114
|
+
* picture — not even mid-gesture, when the transform itself may be momentarily out of range.
|
|
115
|
+
*/
|
|
116
|
+
export function visibleContentRect(transform, bounds) {
|
|
117
|
+
const width = Math.min(bounds.viewportWidth / transform.k, bounds.contentWidth);
|
|
118
|
+
const height = Math.min(bounds.viewportHeight / transform.k, bounds.contentHeight);
|
|
119
|
+
return {
|
|
120
|
+
x: clamp(-transform.x / transform.k, 0, Math.max(0, bounds.contentWidth - width)),
|
|
121
|
+
y: clamp(-transform.y / transform.k, 0, Math.max(0, bounds.contentHeight - height)),
|
|
122
|
+
width,
|
|
123
|
+
height,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The transform that centres the viewport on a content point, at the current scale.
|
|
128
|
+
*
|
|
129
|
+
* Deliberately unclamped: callers apply it through the same clamp as every gesture, which
|
|
130
|
+
* pins edge cases exactly the way the end of a drag is pinned.
|
|
131
|
+
*/
|
|
132
|
+
export function centerViewportOn(transform, contentX, contentY, bounds) {
|
|
133
|
+
return {
|
|
134
|
+
k: transform.k,
|
|
135
|
+
x: bounds.viewportWidth / 2 - contentX * transform.k,
|
|
136
|
+
y: bounds.viewportHeight / 2 - contentY * transform.k,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
97
139
|
/** Rounds away floating-point noise so repeated zoom in/out returns to exactly 1. */
|
|
98
140
|
function tidy(value) {
|
|
99
141
|
return Math.abs(value) < 1e-6 ? 0 : Math.round(value * 1e6) / 1e6;
|
package/package.json
CHANGED