@matfsw/docusaurus-plantuml-plugin 1.6.0 → 1.6.2

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 CHANGED
@@ -7,6 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.6.2] - 2026-08-24
11
+
12
+ ### Changed
13
+
14
+ - Bump the all group across 1 directory with 3 updates (#34)
15
+
16
+ ## [1.6.1] - 2026-08-21
17
+
18
+ ### Fixed
19
+
20
+ - **A diagram that fits can now be dragged around the space beside it.** The viewport has always
21
+ shown a grab cursor, but content that fitted was pinned at the origin, so on anything short of
22
+ a magnified diagram the cursor promised something that did not happen. It was most obvious
23
+ maximized: a modest diagram is fitted into the top-left corner of the screen with room to
24
+ spare, and none of that room could be used.
25
+
26
+ `clampTransform` now bounds each axis by the empty space beside the picture,
27
+ `viewport - content × scale`, instead of pinning content that fits at the origin. The diagram
28
+ moves through that space and no further, so it can never leave the viewport, and a magnified
29
+ one still cannot be dragged until an edge comes inside it. Axes stay independent, so a wide,
30
+ short diagram pans sideways while moving only through what little vertical room it has, and
31
+ **Reset** puts everything back.
32
+
33
+ Note that *fitting* makes one axis flush with the screen by construction, so a freshly
34
+ maximized diagram slides only along the other one. Zoom out a step and it moves both ways.
35
+
36
+ - **Dragging no longer marks the diagram's text.** `pointerdown` cannot `preventDefault()`
37
+ without breaking focus, links and double-click, so the browser anchored a text selection on
38
+ the first press — before the 3px drag threshold could tell a drag from a click. The labels
39
+ flashed blue for the whole gesture, a drag past the frame kept extending the selection across
40
+ the page, and it could not be cleared by clicking, because the click that ends a drag is the
41
+ one the component swallows so a drag over a link does not follow it.
42
+
43
+ The canvas inside a zoom viewport is now `user-select: none`, which removes the anchor rather
44
+ than trying to undo its effects. The text stays reachable where it is actually read: the `</>`
45
+ source view with Copy, the diagram search, and the browser's find-in-page. A diagram with
46
+ `zoom=false` is not a drag surface and is untouched; a site that wants the selection back can
47
+ say `[data-plantuml-diagram] svg {user-select: text}`.
48
+
10
49
  ## [1.6.0] - 2026-08-21
11
50
 
12
51
  ### Fixed
@@ -570,7 +609,9 @@ Initial release.
570
609
  opt-out.
571
610
  - Plugin option validation that rejects unknown keys and out-of-range values at build time.
572
611
 
573
- [Unreleased]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.6.0...HEAD
612
+ [Unreleased]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.6.2...HEAD
613
+ [1.6.2]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.6.1...v1.6.2
614
+ [1.6.1]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.6.0...v1.6.1
574
615
  [1.6.0]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.5.2...v1.6.0
575
616
  [1.5.2]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.5.1...v1.5.2
576
617
  [1.5.1]: https://github.com/matf/docusaurus-plantuml-plugin/compare/v1.5.0...v1.5.1
package/README.md CHANGED
@@ -550,7 +550,7 @@ bottom-left corner: the reader asked for it, and it closes with its own ✕.
550
550
  | ----------------------------------- | -------------------------------------------------------------- |
551
551
  | Plain scroll wheel | Scrolls the page. Never intercepted. |
552
552
  | **Ctrl** + wheel, or trackpad pinch | Zooms about the pointer |
553
- | Drag | Pans, once zoomed in |
553
+ | Drag | Moves the diagram, at any zoom level |
554
554
  | One finger on a touchscreen | Scrolls the page |
555
555
  | Two-finger pinch on a touchscreen | The browser's own page zoom |
556
556
  | Toolbar buttons | Zoom out, zoom in, reset, maximize — plus fit, while maximized |
@@ -581,11 +581,32 @@ working, and `Tab` always moves on — the diagram is never a keyboard trap.
581
581
 
582
582
  The toolbar buttons and the keyboard zoom about the **top-left corner** of the viewport, while
583
583
  the wheel zooms about the **pointer**. That difference is deliberate: a diagram rarely fills its
584
- viewport, and one that fits is left-aligned, so the empty space sits to its right and below.
584
+ viewport, and one that fits opens at the origin, so the empty space sits to its right and below.
585
585
  Zooming about the centre would scale that empty space and push the diagram off the top and left
586
586
  edges — most visibly when maximized. Anchoring at the top-left grows the diagram into the empty
587
587
  space instead and keeps it visible as long as possible.
588
588
 
589
+ ### Moving the diagram
590
+
591
+ Dragging moves the diagram **whatever the zoom level** — including a small one, and including a
592
+ diagram that fits its frame whole. Maximize a modest diagram and it is fitted into a corner of
593
+ the screen with room to spare; that space is now yours to move it into, rather than a grab
594
+ cursor that did nothing.
595
+
596
+ Text inside a zoomable diagram is **not selectable**, because the same box is the drag surface:
597
+ the browser anchors a selection on the first press, before a drag can be told apart from a
598
+ click, so dragging used to paint the labels blue and leave them that way. The text is still
599
+ reachable everywhere it is actually read — the `</>` source view with its Copy button, the
600
+ diagram search, and the browser's find-in-page. A diagram with `zoom=false` is not a drag
601
+ surface and stays selectable, and a site that wants the selection back everywhere can say
602
+ `[data-plantuml-diagram] svg {user-select: text}`.
603
+
604
+ The rule in both directions is that the diagram stays reachable: it can never be dragged so far
605
+ that an edge comes inside the viewport, and never so far that it leaves the viewport altogether.
606
+ Where there is no room — a picture exactly as wide as its frame — it does not budge on that axis,
607
+ so a wide, short diagram still pans sideways while staying put vertically. The arrow keys move it
608
+ the same way, and **Reset** puts it back where it opened.
609
+
589
610
  Scale is limited to 0.25×–8×, and the view resets to 100% whenever the diagram source or the
590
611
  site colour mode changes.
591
612
 
@@ -90,13 +90,30 @@
90
90
 
91
91
  .viewport[data-plantuml-dragging='true'] {
92
92
  cursor: grabbing;
93
- user-select: none;
94
93
  }
95
94
 
96
- /* Inside the viewport the SVG must not be clipped by the old scroll container. */
95
+ /*
96
+ * Inside the viewport the SVG must not be clipped by the old scroll container — and its text
97
+ * is not selectable, because this box is a drag surface.
98
+ *
99
+ * Pressing on SVG text used to anchor a native selection: `pointerdown` deliberately does not
100
+ * `preventDefault()`, so that focus, links and double-click keep working, which means the
101
+ * browser starts selecting before the 3px drag threshold has been crossed. Marking the box
102
+ * unselectable once dragging began was too late — the anchor already existed, the labels
103
+ * flashed blue for the whole gesture, a drag past the edge kept extending the selection into
104
+ * the page, and the click that would normally collapse it is the very click a drag swallows.
105
+ * Removing the anchor removes all of that at once.
106
+ *
107
+ * The text stays reachable everywhere it was actually read: the `</>` source view (with Copy),
108
+ * this component's own search, and the browser's find-in-page, none of which need a selection.
109
+ * A diagram with `zoom=false` is not a drag surface and is left alone. A site that wants the
110
+ * old behaviour can say `[data-plantuml-diagram] svg {user-select: text}` — an attribute
111
+ * selector, so no hashed class name is needed.
112
+ */
97
113
  .viewport .canvas {
98
114
  display: block;
99
115
  overflow: visible;
116
+ user-select: none;
100
117
  }
101
118
 
102
119
  .layer {
@@ -27,9 +27,9 @@ export function useZoomPan({ enabled, resetKey }) {
27
27
  * Layout sizes, never `getBoundingClientRect()`: the layer's rect includes the transform we
28
28
  * are about to change — and, mid-easing, the transition — which would feed back into the
29
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}.
30
+ * picture's, not the full-width frame's; that is what lets fitting fill a maximized screen,
31
+ * keeps the minimap honest about where the diagram ends, and tells the clamp how much empty
32
+ * space a fitted diagram may be dragged into.
33
33
  */
34
34
  const measure = useCallback(() => {
35
35
  const viewport = viewportRef.current;
@@ -41,20 +41,6 @@ export function useZoomPan({ enabled, resetKey }) {
41
41
  contentHeight: layer?.offsetHeight ?? 0,
42
42
  };
43
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]);
58
44
  const write = useCallback((next) => {
59
45
  transformRef.current = next;
60
46
  const layer = layerRef.current;
@@ -76,20 +62,26 @@ export function useZoomPan({ enabled, resetKey }) {
76
62
  };
77
63
  }, []);
78
64
  const getTransform = useCallback(() => transformRef.current, []);
65
+ /**
66
+ * Every gesture ends here, so the clamp is the single place that decides where a diagram may
67
+ * sit. It is handed the measured picture: the floor that keeps focal zoom honest lives inside
68
+ * {@link clampTransform}, applied to that end of the range only, so the other end can offer a
69
+ * fitted diagram the real empty space beside it.
70
+ */
79
71
  const apply = useCallback((next) => {
80
- write(clampTransform(next, clampBounds()));
81
- }, [clampBounds, write]);
72
+ write(clampTransform(next, measure()));
73
+ }, [measure, write]);
82
74
  const reset = useCallback(() => {
83
75
  apply(IDENTITY);
84
76
  }, [apply]);
85
77
  /**
86
78
  * Zooms about the viewport's top-left corner.
87
79
  *
88
- * Not the centre: a diagram rarely fills its viewport, and a diagram that fits is
89
- * left-aligned, so the empty space sits to its right and below. Zooming about the centre
90
- * scales that empty space too and pushes the diagram off the top and left edges — most
91
- * visibly when maximized. Anchoring at the top-left leaves a left-aligned diagram exactly
92
- * where it is and grows it into the empty space, keeping it visible as long as possible.
80
+ * Not the centre: a diagram rarely fills its viewport, and one that fits opens at the origin,
81
+ * so the empty space sits to its right and below. Zooming about the centre scales that empty
82
+ * space too and pushes the diagram off the top and left edges — most visibly when maximized.
83
+ * Anchoring at the top-left leaves the diagram exactly where it is and grows it into the
84
+ * empty space, keeping it visible as long as possible.
93
85
  *
94
86
  * Wheel zoom still tracks the pointer, which is what direct manipulation should do.
95
87
  */
@@ -104,8 +96,8 @@ export function useZoomPan({ enabled, resetKey }) {
104
96
  *
105
97
  * Only offered while maximized. Inline it would duplicate Reset, because the inline frame
106
98
  * 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.
99
+ * a fit to add. Translation returns to the origin: fitting is "show me all of it from the
100
+ * top", and the reader can drag it wherever they like from there.
109
101
  */
110
102
  const fit = useCallback(() => {
111
103
  apply({ ...IDENTITY, k: fitScale(measure()) });
@@ -42,12 +42,18 @@ export declare function clamp(value: number, min: number, max: number): number;
42
42
  export declare function zoomAbout(transform: Transform, factor: number, focalX: number, focalY: number, min?: number, max?: number): Transform;
43
43
  export declare function panBy(transform: Transform, dx: number, dy: number): Transform;
44
44
  /**
45
- * Keeps the diagram reachable: an edge can never be dragged inside the viewport, and content
46
- * that fits is left-aligned — matching the `justify-content: flex-start` behaviour diagrams
47
- * had before they became zoomable.
45
+ * Keeps the diagram reachable, in both directions, one axis at a time.
48
46
  *
49
- * The axes are clamped independently, so a wide, short diagram pans horizontally while staying
50
- * pinned vertically.
47
+ * The two ends of the range answer different questions, and are deliberately measured against
48
+ * different things — see {@link clampAxis}. Forwards, the limit is the *empty space* beside the
49
+ * picture: a diagram that fits can be moved through it but never out of the viewport. Content
50
+ * that fits used to be pinned at `0` outright, which left the reader with a grab cursor that
51
+ * moved nothing — most obviously while maximized, where a fitted diagram sits in a corner of a
52
+ * screen with room to spare. Backwards, the limit keeps a magnified diagram from being dragged
53
+ * until an edge comes inside the viewport.
54
+ *
55
+ * The axes are clamped independently, so a wide, short diagram pans horizontally while moving
56
+ * only through the little vertical space it has.
51
57
  */
52
58
  export declare function clampTransform(transform: Transform, bounds: Bounds): Transform;
53
59
  /**
@@ -39,23 +39,45 @@ export function panBy(transform, dx, dy) {
39
39
  return { k: transform.k, x: transform.x + dx, y: transform.y + dy };
40
40
  }
41
41
  /**
42
- * Keeps the diagram reachable: an edge can never be dragged inside the viewport, and content
43
- * that fits is left-aligned — matching the `justify-content: flex-start` behaviour diagrams
44
- * had before they became zoomable.
42
+ * Keeps the diagram reachable, in both directions, one axis at a time.
45
43
  *
46
- * The axes are clamped independently, so a wide, short diagram pans horizontally while staying
47
- * pinned vertically.
44
+ * The two ends of the range answer different questions, and are deliberately measured against
45
+ * different things — see {@link clampAxis}. Forwards, the limit is the *empty space* beside the
46
+ * picture: a diagram that fits can be moved through it but never out of the viewport. Content
47
+ * that fits used to be pinned at `0` outright, which left the reader with a grab cursor that
48
+ * moved nothing — most obviously while maximized, where a fitted diagram sits in a corner of a
49
+ * screen with room to spare. Backwards, the limit keeps a magnified diagram from being dragged
50
+ * until an edge comes inside the viewport.
51
+ *
52
+ * The axes are clamped independently, so a wide, short diagram pans horizontally while moving
53
+ * only through the little vertical space it has.
48
54
  */
49
55
  export function clampTransform(transform, bounds) {
50
- const scaledWidth = bounds.contentWidth * transform.k;
51
- const scaledHeight = bounds.contentHeight * transform.k;
52
- const x = scaledWidth <= bounds.viewportWidth
53
- ? 0
54
- : clamp(transform.x, bounds.viewportWidth - scaledWidth, 0);
55
- const y = scaledHeight <= bounds.viewportHeight
56
- ? 0
57
- : clamp(transform.y, bounds.viewportHeight - scaledHeight, 0);
58
- return { k: transform.k, x, y };
56
+ return {
57
+ k: transform.k,
58
+ x: clampAxis(transform.x, bounds.viewportWidth, bounds.contentWidth, transform.k),
59
+ y: clampAxis(transform.y, bounds.viewportHeight, bounds.contentHeight, transform.k),
60
+ };
61
+ }
62
+ /**
63
+ * One axis of the clamp.
64
+ *
65
+ * **Forwards** (positive translation) the picture may travel exactly as far as the empty space
66
+ * beside it, `viewport - content * k`, and no further — so it never leaves the viewport. Once
67
+ * the content overflows there is no empty space and the limit is `0`.
68
+ *
69
+ * **Backwards** the content size is floored at the viewport's *before* scaling. That looks odd
70
+ * until you zoom: focal zoom must hold the point under the pointer, and a picture narrower than
71
+ * its frame reaches a scale where it overflows while the pointer is still asking it to travel
72
+ * further left than a flush right edge allows. Clamping to the picture there yanks it sideways
73
+ * mid-gesture. The floor buys that fidelity for the price of some empty space at the right
74
+ * while zoomed, which is the trade this component has always made — it is only the *forward*
75
+ * limit, above, that had to stop being measured this way for a fitted diagram to move at all.
76
+ */
77
+ function clampAxis(value, viewport, content, k) {
78
+ const forward = Math.max(0, viewport - content * k);
79
+ const backward = Math.min(0, viewport - Math.max(content, viewport) * k);
80
+ return clamp(value, backward, forward);
59
81
  }
60
82
  /** `WheelEvent.deltaMode` constants, which jsdom does not define. */
61
83
  const DELTA_PIXEL = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matfsw/docusaurus-plantuml-plugin",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
4
4
  "description": "Render PlantUML diagrams in Docusaurus 3 entirely in the browser - no Java, no PlantUML server, no CDN.",
5
5
  "keywords": [
6
6
  "docusaurus",