@eclipse-glsp/client 2.7.0-next.1 → 2.7.0-next.11

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.
Files changed (142) hide show
  1. package/lib/base/auto-complete/auto-complete-widget.d.ts +1 -1
  2. package/lib/base/auto-complete/auto-complete-widget.js +3 -5
  3. package/lib/base/auto-complete/auto-complete-widget.js.map +1 -1
  4. package/lib/base/auto-complete/validation-decorator.d.ts +1 -1
  5. package/lib/base/auto-complete/validation-decorator.d.ts.map +1 -1
  6. package/lib/base/auto-complete/validation-decorator.js +1 -4
  7. package/lib/base/auto-complete/validation-decorator.js.map +1 -1
  8. package/lib/base/default.module.d.ts +1 -1
  9. package/lib/base/default.module.js +2 -2
  10. package/lib/base/default.module.js.map +1 -1
  11. package/lib/base/feedback/feedback-action-dispatcher.d.ts +1 -1
  12. package/lib/base/feedback/feedback-action-dispatcher.js +2 -4
  13. package/lib/base/feedback/feedback-action-dispatcher.js.map +1 -1
  14. package/lib/base/feedback/feedback-command.d.ts +1 -1
  15. package/lib/base/feedback/feedback-command.js +2 -2
  16. package/lib/base/feedback/feedback-command.js.map +1 -1
  17. package/lib/base/focus/focus-tracker.d.ts +1 -1
  18. package/lib/base/focus/focus-tracker.js +1 -1
  19. package/lib/base/model/model-change-service.d.ts +1 -1
  20. package/lib/base/model/model-change-service.js +1 -1
  21. package/lib/base/ui-extension/ui-extension.d.ts +1 -1
  22. package/lib/base/ui-extension/ui-extension.js +1 -1
  23. package/lib/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.d.ts +1 -1
  24. package/lib/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.js +1 -1
  25. package/lib/features/bounds/bounds-module.d.ts +1 -1
  26. package/lib/features/bounds/bounds-module.js +2 -2
  27. package/lib/features/bounds/bounds-module.js.map +1 -1
  28. package/lib/features/bounds/glsp-hidden-bounds-updater.d.ts +1 -1
  29. package/lib/features/bounds/glsp-hidden-bounds-updater.js +1 -1
  30. package/lib/features/bounds/hbox-layout.d.ts +1 -1
  31. package/lib/features/bounds/hbox-layout.js +1 -2
  32. package/lib/features/bounds/hbox-layout.js.map +1 -1
  33. package/lib/features/bounds/vbox-layout.d.ts +1 -1
  34. package/lib/features/bounds/vbox-layout.js +1 -2
  35. package/lib/features/bounds/vbox-layout.js.map +1 -1
  36. package/lib/features/change-bounds/model.d.ts +1 -1
  37. package/lib/features/change-bounds/model.d.ts.map +1 -1
  38. package/lib/features/change-bounds/model.js +1 -2
  39. package/lib/features/change-bounds/model.js.map +1 -1
  40. package/lib/features/change-bounds/point-position-updater.d.ts +1 -1
  41. package/lib/features/change-bounds/point-position-updater.js +3 -4
  42. package/lib/features/change-bounds/point-position-updater.js.map +1 -1
  43. package/lib/features/change-bounds/snap.d.ts +1 -1
  44. package/lib/features/change-bounds/snap.js +15 -0
  45. package/lib/features/change-bounds/snap.js.map +1 -1
  46. package/lib/features/command-palette/command-palette.d.ts +1 -1
  47. package/lib/features/command-palette/command-palette.js +1 -1
  48. package/lib/features/context-menu/context-menu-module.d.ts +1 -1
  49. package/lib/features/context-menu/context-menu-module.js +1 -2
  50. package/lib/features/context-menu/context-menu-module.js.map +1 -1
  51. package/lib/features/context-menu/delete-element-context-menu.d.ts +1 -1
  52. package/lib/features/context-menu/delete-element-context-menu.js +1 -1
  53. package/lib/features/copy-paste/copy-paste-context-menu.d.ts +1 -1
  54. package/lib/features/copy-paste/copy-paste-context-menu.js +2 -2
  55. package/lib/features/copy-paste/copy-paste-context-menu.js.map +1 -1
  56. package/lib/features/debug/debug-bounds-decorator.d.ts +1 -1
  57. package/lib/features/debug/debug-bounds-decorator.js +12 -13
  58. package/lib/features/debug/debug-bounds-decorator.js.map +1 -1
  59. package/lib/features/label-edit-ui/label-edit-ui.d.ts +4 -1
  60. package/lib/features/label-edit-ui/label-edit-ui.d.ts.map +1 -1
  61. package/lib/features/label-edit-ui/label-edit-ui.js +38 -1
  62. package/lib/features/label-edit-ui/label-edit-ui.js.map +1 -1
  63. package/lib/features/navigation/navigation-action-handler.d.ts +1 -1
  64. package/lib/features/navigation/navigation-action-handler.js +1 -1
  65. package/lib/features/routing/edge-router.d.ts +2 -1
  66. package/lib/features/routing/edge-router.d.ts.map +1 -1
  67. package/lib/features/routing/edge-router.js +2 -1
  68. package/lib/features/routing/edge-router.js.map +1 -1
  69. package/lib/features/routing/routing-module.d.ts +1 -1
  70. package/lib/features/routing/routing-module.d.ts.map +1 -1
  71. package/lib/features/routing/routing-module.js +7 -1
  72. package/lib/features/routing/routing-module.js.map +1 -1
  73. package/lib/features/routing/sticky-manhattan-anchors.d.ts +29 -0
  74. package/lib/features/routing/sticky-manhattan-anchors.d.ts.map +1 -0
  75. package/lib/features/routing/sticky-manhattan-anchors.js +62 -0
  76. package/lib/features/routing/sticky-manhattan-anchors.js.map +1 -0
  77. package/lib/features/routing/sticky-manhattan-edge-router.d.ts +107 -0
  78. package/lib/features/routing/sticky-manhattan-edge-router.d.ts.map +1 -0
  79. package/lib/features/routing/sticky-manhattan-edge-router.js +626 -0
  80. package/lib/features/routing/sticky-manhattan-edge-router.js.map +1 -0
  81. package/lib/features/tool-palette/tool-palette.d.ts +1 -1
  82. package/lib/features/tool-palette/tool-palette.d.ts.map +1 -1
  83. package/lib/features/tool-palette/tool-palette.js +1 -3
  84. package/lib/features/tool-palette/tool-palette.js.map +1 -1
  85. package/lib/index.d.ts +5 -1
  86. package/lib/index.d.ts.map +1 -1
  87. package/lib/index.js +5 -1
  88. package/lib/index.js.map +1 -1
  89. package/lib/views/gedge-view-with-gaps-on-intersections.d.ts +39 -0
  90. package/lib/views/gedge-view-with-gaps-on-intersections.d.ts.map +1 -0
  91. package/lib/views/gedge-view-with-gaps-on-intersections.js +85 -0
  92. package/lib/views/gedge-view-with-gaps-on-intersections.js.map +1 -0
  93. package/lib/views/issue-marker-view.d.ts +1 -1
  94. package/lib/views/issue-marker-view.js +9 -10
  95. package/lib/views/issue-marker-view.js.map +1 -1
  96. package/lib/views/rounded-corner-manhattan-edge-view.d.ts +58 -0
  97. package/lib/views/rounded-corner-manhattan-edge-view.d.ts.map +1 -0
  98. package/lib/views/rounded-corner-manhattan-edge-view.js +170 -0
  99. package/lib/views/rounded-corner-manhattan-edge-view.js.map +1 -0
  100. package/package.json +4 -4
  101. package/src/base/action-dispatcher.spec.ts +2 -2
  102. package/src/base/action-dispatcher.ts +1 -1
  103. package/src/base/auto-complete/auto-complete-widget.ts +3 -3
  104. package/src/base/auto-complete/validation-decorator.ts +3 -4
  105. package/src/base/default.module.ts +2 -2
  106. package/src/base/feedback/feedback-action-dispatcher.ts +2 -2
  107. package/src/base/feedback/feedback-command.ts +2 -2
  108. package/src/base/focus/focus-tracker.ts +1 -1
  109. package/src/base/model/model-change-service.ts +1 -1
  110. package/src/base/model/model-initialization-constraint.spec.ts +2 -2
  111. package/src/base/selection-service.spec.ts +3 -4
  112. package/src/base/ui-extension/ui-extension.ts +1 -1
  113. package/src/default-modules.spec.ts +2 -2
  114. package/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +1 -1
  115. package/src/features/bounds/bounds-module.ts +2 -2
  116. package/src/features/bounds/glsp-hidden-bounds-updater.ts +1 -1
  117. package/src/features/bounds/hbox-layout.spec.ts +2 -2
  118. package/src/features/bounds/hbox-layout.ts +2 -2
  119. package/src/features/bounds/vbox-layout.spec.ts +2 -2
  120. package/src/features/bounds/vbox-layout.ts +2 -2
  121. package/src/features/change-bounds/model.ts +1 -2
  122. package/src/features/change-bounds/point-position-updater.spec.ts +3 -3
  123. package/src/features/change-bounds/point-position-updater.ts +4 -4
  124. package/src/features/change-bounds/snap.ts +2 -2
  125. package/src/features/command-palette/command-palette.ts +1 -1
  126. package/src/features/context-menu/context-menu-module.ts +2 -2
  127. package/src/features/context-menu/delete-element-context-menu.ts +2 -2
  128. package/src/features/copy-paste/copy-paste-context-menu.ts +2 -2
  129. package/src/features/debug/debug-bounds-decorator.tsx +2 -2
  130. package/src/features/label-edit-ui/label-edit-ui.ts +36 -3
  131. package/src/features/layout/layout-elements-action.spec.ts +2 -2
  132. package/src/features/navigation/navigation-action-handler.ts +3 -3
  133. package/src/features/routing/edge-router.ts +2 -2
  134. package/src/features/routing/routing-module.ts +8 -1
  135. package/src/features/routing/sticky-manhattan-anchors.ts +53 -0
  136. package/src/features/routing/sticky-manhattan-edge-router.spec.ts +212 -0
  137. package/src/features/routing/sticky-manhattan-edge-router.ts +713 -0
  138. package/src/features/tool-palette/tool-palette.ts +1 -3
  139. package/src/index.ts +5 -1
  140. package/src/views/gedge-view-with-gaps-on-intersections.tsx +99 -0
  141. package/src/views/issue-marker-view.tsx +2 -2
  142. package/src/views/rounded-corner-manhattan-edge-view.ts +171 -0
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2019-2025 EclipseSource and others.
2
+ * Copyright (c) 2019-2026 EclipseSource and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -580,12 +580,10 @@ export function createToolGroup(item: PaletteItem): HTMLElement {
580
580
  }
581
581
 
582
582
  export function changeCSSClass(element: Element, css: string): void {
583
- // eslint-disable-next-line chai-friendly/no-unused-expressions
584
583
  element.classList.contains(css) ? element.classList.remove(css) : element.classList.add(css);
585
584
  }
586
585
 
587
586
  export function changeCodiconClass(element: Element, codiconId: string): void {
588
- // eslint-disable-next-line chai-friendly/no-unused-expressions
589
587
  element.classList.contains(codiconCSSClasses(codiconId)[1])
590
588
  ? element.classList.remove(codiconCSSClasses(codiconId)[1])
591
589
  : element.classList.add(codiconCSSClasses(codiconId)[1]);
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2024-2025 EclipseSource and others.
2
+ * Copyright (c) 2024-2026 EclipseSource and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -165,6 +165,8 @@ export * from './features/navigation/navigation-target-resolver';
165
165
  export * from './features/reconnect/model';
166
166
  export * from './features/routing/edge-router';
167
167
  export * from './features/routing/routing-module';
168
+ export * from './features/routing/sticky-manhattan-anchors';
169
+ export * from './features/routing/sticky-manhattan-edge-router';
168
170
  export * from './features/save/save-keylistener';
169
171
  export * from './features/save/save-module';
170
172
  export * from './features/search-palette/search-palette';
@@ -243,9 +245,11 @@ export * from './utils/viewpoint-util';
243
245
  export * from './views/base-view-module';
244
246
  export * from './views/compartments';
245
247
  export * from './views/gedge-view';
248
+ export * from './views/gedge-view-with-gaps-on-intersections';
246
249
  export * from './views/ggraph-view';
247
250
  export * from './views/glsp-projection-view';
248
251
  export * from './views/issue-marker-view';
249
252
  export * from './views/rounded-corner';
253
+ export * from './views/rounded-corner-manhattan-edge-view';
250
254
  export * from './views/rounded-corner-view';
251
255
  export * from './views/routing-point-handle-view';
@@ -0,0 +1,99 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the Eclipse Public License v. 2.0 which is available at
6
+ * http://www.eclipse.org/legal/epl-2.0.
7
+ *
8
+ * This Source Code may also be made available under the following Secondary
9
+ * Licenses when the conditions for such availability set forth in the Eclipse
10
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ * with the GNU Classpath Exception which is available at
12
+ * https://www.gnu.org/software/classpath/license.html.
13
+ *
14
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
+ ********************************************************************************/
16
+ /** @jsx svg */
17
+ import {
18
+ IViewArgs,
19
+ Point,
20
+ PolylineEdgeViewWithGapsOnIntersections,
21
+ RenderingContext,
22
+ isIntersectingRoutedPoint,
23
+ svg
24
+ } from '@eclipse-glsp/sprotty';
25
+ import { injectable } from 'inversify';
26
+ import { Classes, VNode } from 'snabbdom';
27
+ import { GEdge } from '../model';
28
+ import { EdgePadding } from '../utils/argument-utils';
29
+
30
+ /**
31
+ * Gap-rendering counterpart to {@link GEdgeView}: extends
32
+ * {@link PolylineEdgeViewWithGapsOnIntersections} and re-adds the GLSP-only
33
+ * bits from `GEdgeView` — router dispatch, dangling-edge rendering,
34
+ * `additionalClasses` hook, and the edge-padding mouse-handle overlay.
35
+ *
36
+ * Subclasses override {@link createPathForSegments} to inject per-point path
37
+ * fragments. Intersections are spliced into the rendered line
38
+ * (`addIntersectionPoints = true`) and skipped for the mouse-handle overlay
39
+ * so the hit area stays contiguous.
40
+ */
41
+ @injectable()
42
+ export class GEdgeViewWithGapsOnIntersections extends PolylineEdgeViewWithGapsOnIntersections {
43
+ override render(edge: Readonly<GEdge>, context: RenderingContext, args?: IViewArgs): VNode {
44
+ // route(edge, args) picks up the postprocessed routing populated by
45
+ // SGraphView — that's where IntersectionFinder tags points.
46
+ const route = this.edgeRouterRegistry.route(edge, args);
47
+ if (route.length === 0) {
48
+ return this.renderDanglingEdge('Cannot compute route', edge, context);
49
+ }
50
+ return (
51
+ <g class-sprotty-edge={true} class-mouseover={edge.hoverFeedback} {...this.additionalClasses(edge, context)}>
52
+ {this.renderLine(edge, route, context, args)}
53
+ {this.renderAdditionals(edge, route, context)}
54
+ {context.renderChildren(edge, { route })}
55
+ </g>
56
+ );
57
+ }
58
+
59
+ protected additionalClasses(_edge: Readonly<GEdge>, _context: RenderingContext): Classes {
60
+ return {};
61
+ }
62
+
63
+ protected override renderLine(edge: GEdge, segments: Point[], _context: RenderingContext, args?: IViewArgs): VNode {
64
+ return <path d={this.createPathForSegments(segments, edge, true, args)} />;
65
+ }
66
+
67
+ protected override renderAdditionals(edge: GEdge, segments: Point[], _context: RenderingContext): VNode[] {
68
+ const edgePadding = EdgePadding.from(edge);
69
+ return edgePadding ? [this.renderMouseHandle(edge, segments, edgePadding)] : [];
70
+ }
71
+
72
+ protected renderMouseHandle(edge: GEdge, segments: Point[], padding: number): VNode {
73
+ return (
74
+ <path
75
+ class-mouse-handle
76
+ d={this.createPathForSegments(segments, edge, false)}
77
+ style-stroke-width={padding * 2}
78
+ style-stroke='transparent'
79
+ style-stroke-dasharray='none'
80
+ style-stroke-dashoffset='0'
81
+ />
82
+ );
83
+ }
84
+
85
+ protected createPathForSegments(segments: Point[], edge: GEdge, addIntersectionPoints: boolean, args?: IViewArgs): string {
86
+ if (segments.length === 0) {
87
+ return '';
88
+ }
89
+ let path = `M ${segments[0].x},${segments[0].y}`;
90
+ for (let i = 1; i < segments.length; i++) {
91
+ const p = segments[i];
92
+ if (addIntersectionPoints && isIntersectingRoutedPoint(p)) {
93
+ path += this.intersectionPath(edge, segments, p, args);
94
+ }
95
+ path += ` L ${p.x},${p.y}`;
96
+ }
97
+ return path;
98
+ }
99
+ }
@@ -1,5 +1,5 @@
1
1
  /********************************************************************************
2
- * Copyright (c) 2020-2024 EclipseSource and others.
2
+ * Copyright (c) 2020-2026 EclipseSource and others.
3
3
  *
4
4
  * This program and the accompanying materials are made available under the
5
5
  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -13,7 +13,7 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
- /* eslint-disable max-len */
16
+
17
17
  /** @jsx svg */
18
18
  import { GIssueSeverity, IssueMarkerView, RenderingContext, setClass, svg } from '@eclipse-glsp/sprotty';
19
19
  import { injectable } from 'inversify';
@@ -0,0 +1,171 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2022-2026 Imixs Software Solutions GmbH.
3
+ * Copyright (c) 2026 EclipseSource and others.
4
+ *
5
+ * This program and the accompanying materials are made available under the
6
+ * terms of the Eclipse Public License v. 2.0 which is available at
7
+ * http://www.eclipse.org/legal/epl-2.0.
8
+ *
9
+ * This Source Code may also be made available under the following Secondary
10
+ * Licenses when the conditions for such availability set forth in the Eclipse
11
+ * Public License v. 2.0 are satisfied: GNU General Public License, version 2
12
+ * with the GNU Classpath Exception which is available at
13
+ * https://www.gnu.org/software/classpath/license.html.
14
+ *
15
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16
+ ********************************************************************************/
17
+ import { IntersectingRoutedPoint, IViewArgs, Point, isIntersectingRoutedPoint } from '@eclipse-glsp/sprotty';
18
+ import { injectable } from 'inversify';
19
+ import { GEdge } from '../model';
20
+ import { GEdgeViewWithGapsOnIntersections } from './gedge-view-with-gaps-on-intersections';
21
+
22
+ /**
23
+ * Manhattan edge view with rounded bends rendered as quadratic Béziers.
24
+ * Non-orthogonal inputs render as sharp corners. Radius is clamped per corner;
25
+ * override {@link cornerRadius} or {@link computeMaxRadius} to tune.
26
+ *
27
+ * Inherits gap / line-jump rendering from {@link GEdgeViewWithGapsOnIntersections}.
28
+ * The rendered line splices intersection fragments between points; the mouse-
29
+ * handle overlay skips them so the hit area stays contiguous.
30
+ *
31
+ * Based on Ralph Soika's `BPMNEdgeView`; see
32
+ * https://github.com/eclipse-glsp/glsp/discussions/1642.
33
+ */
34
+ @injectable()
35
+ export class RoundedCornerManhattanEdgeView extends GEdgeViewWithGapsOnIntersections {
36
+ /** Target corner radius in pixels. The effective radius is clamped per corner. */
37
+ protected readonly cornerRadius: number = 10;
38
+
39
+ /** Lower bound for the effective radius, even on very short adjacent segments. */
40
+ protected readonly minCornerRadius: number = 2;
41
+
42
+ /** Upper fraction of the shorter adjacent segment to use for the radius. */
43
+ protected readonly maxRadiusFactor: number = 0.45;
44
+
45
+ /** Segment length (px) below which {@link shortSegmentRadiusFactor} tightens the radius further. */
46
+ protected readonly shortSegmentThreshold: number = 5;
47
+
48
+ /** Fraction of the shorter adjacent segment to use as the radius below {@link shortSegmentThreshold}. */
49
+ protected readonly shortSegmentRadiusFactor: number = 0.3;
50
+
51
+ protected override createPathForSegments(segments: Point[], edge: GEdge, addIntersectionPoints: boolean, args?: IViewArgs): string {
52
+ if (segments.length === 0) {
53
+ return '';
54
+ }
55
+ let path = `M ${segments[0].x},${segments[0].y}`;
56
+ for (let i = 1; i < segments.length; i++) {
57
+ const p = segments[i];
58
+ if (addIntersectionPoints && isIntersectingRoutedPoint(p)) {
59
+ const effective = this.filterCornerZoneIntersections(p, segments, i);
60
+ if (effective.intersections.length > 0) {
61
+ path += this.intersectionPath(edge, segments, effective, args);
62
+ }
63
+ }
64
+ if (i < segments.length - 1) {
65
+ const prev = segments[i - 1];
66
+ const next = segments[i + 1];
67
+ const radius = this.computeMaxRadius(p, prev, next);
68
+ path += this.renderRoundedCorner(p, prev, next, radius);
69
+ } else {
70
+ path += ` L ${p.x},${p.y}`;
71
+ }
72
+ }
73
+ return path;
74
+ }
75
+
76
+ /**
77
+ * Drops intersections inside either endpoint's rounded-corner zone (padded
78
+ * by the gap's skip-offset). The gap fragment can't be placed there without
79
+ * drawing backward into the curve on one side, and the crossing is hidden
80
+ * behind the curve anyway.
81
+ */
82
+ protected filterCornerZoneIntersections(
83
+ intersectingPoint: IntersectingRoutedPoint,
84
+ segments: Point[],
85
+ pointIndex: number
86
+ ): IntersectingRoutedPoint {
87
+ const p1 = segments[pointIndex - 1];
88
+ const p2 = segments[pointIndex];
89
+ const p1Threshold = this.getCornerRadius(segments, pointIndex - 1) + this.skipOffsetBefore;
90
+ const p2Threshold = this.getCornerRadius(segments, pointIndex) + this.skipOffsetAfter;
91
+ const filtered = intersectingPoint.intersections.filter(
92
+ intersection =>
93
+ Point.maxDistance(intersection.intersectionPoint, p1) > p1Threshold &&
94
+ Point.maxDistance(intersection.intersectionPoint, p2) > p2Threshold
95
+ );
96
+ return { ...intersectingPoint, intersections: filtered };
97
+ }
98
+
99
+ /** Curve radius at `segments[cornerIndex]`, or 0 for source / target. */
100
+ protected getCornerRadius(segments: Point[], cornerIndex: number): number {
101
+ if (cornerIndex <= 0 || cornerIndex >= segments.length - 1) {
102
+ return 0;
103
+ }
104
+ const corner = segments[cornerIndex];
105
+ const prev = segments[cornerIndex - 1];
106
+ const next = segments[cornerIndex + 1];
107
+ return this.computeMaxRadius(corner, prev, next);
108
+ }
109
+
110
+ /** SVG fragment for one rounded corner; falls back to `L` if not a right angle. */
111
+ protected renderRoundedCorner(corner: Point, prev: Point, next: Point, radius: number): string {
112
+ // Round only right-angle bends; diagonal / collinear inputs render sharp.
113
+ const horizIn = Point.isHorizontalAligned(prev, corner);
114
+ const vertIn = Point.isVerticalAligned(prev, corner);
115
+ const horizOut = Point.isHorizontalAligned(corner, next);
116
+ const vertOut = Point.isVerticalAligned(corner, next);
117
+ const isRightAngle = (horizIn && vertOut && !vertIn) || (vertIn && horizOut && !horizIn);
118
+ if (!isRightAngle) {
119
+ return ` L ${corner.x},${corner.y}`;
120
+ }
121
+ if (prev.x < corner.x && corner.y < next.y) {
122
+ // right → down
123
+ return ` L ${corner.x - radius},${corner.y} Q ${corner.x},${corner.y} ${corner.x},${corner.y + radius}`;
124
+ }
125
+ if (prev.y < corner.y && corner.x < next.x) {
126
+ // down → right
127
+ return ` L ${corner.x},${corner.y - radius} Q ${corner.x},${corner.y} ${corner.x + radius},${corner.y}`;
128
+ }
129
+ if (prev.x < corner.x && corner.y > next.y) {
130
+ // right → up
131
+ return ` L ${corner.x - radius},${corner.y} Q ${corner.x},${corner.y} ${corner.x},${corner.y - radius}`;
132
+ }
133
+ if (prev.y > corner.y && corner.x < next.x) {
134
+ // up → right
135
+ return ` L ${corner.x},${corner.y + radius} Q ${corner.x},${corner.y} ${corner.x + radius},${corner.y}`;
136
+ }
137
+ if (prev.y < corner.y && corner.x > next.x) {
138
+ // down → left
139
+ return ` L ${corner.x},${corner.y - radius} Q ${corner.x},${corner.y} ${corner.x - radius},${corner.y}`;
140
+ }
141
+ if (prev.x > corner.x && corner.y < next.y) {
142
+ // left → down
143
+ return ` L ${corner.x + radius},${corner.y} Q ${corner.x},${corner.y} ${corner.x},${corner.y + radius}`;
144
+ }
145
+ if (prev.y > corner.y && corner.x > next.x) {
146
+ // up → left
147
+ return ` L ${corner.x},${corner.y + radius} Q ${corner.x},${corner.y} ${corner.x - radius},${corner.y}`;
148
+ }
149
+ if (prev.x > corner.x && corner.y > next.y) {
150
+ // left → up
151
+ return ` L ${corner.x + radius},${corner.y} Q ${corner.x},${corner.y} ${corner.x},${corner.y - radius}`;
152
+ }
153
+ return ` L ${corner.x},${corner.y}`;
154
+ }
155
+
156
+ /** Clamps {@link cornerRadius} to a fraction of the shorter adjacent segment. */
157
+ protected computeMaxRadius(corner: Point, prev: Point, next: Point): number {
158
+ const segBefore = Point.maxDistance(corner, prev);
159
+ const segAfter = Point.maxDistance(next, corner);
160
+ if (segBefore === 0 || segAfter === 0) {
161
+ return this.minCornerRadius;
162
+ }
163
+ const shortest = Math.min(segBefore, segAfter);
164
+ let radius = Math.min(this.cornerRadius, shortest * this.maxRadiusFactor);
165
+ if (shortest < this.shortSegmentThreshold) {
166
+ radius = Math.min(radius, shortest * this.shortSegmentRadiusFactor);
167
+ }
168
+ radius = Math.min(radius, shortest / 2);
169
+ return Math.max(this.minCornerRadius, radius);
170
+ }
171
+ }