Skip to content

feat(VertexHandler): add margin and preview shape matching options#957

Draft
tbouffard wants to merge 1 commit intomainfrom
feat/CellHandler_story_use_more_options
Draft

feat(VertexHandler): add margin and preview shape matching options#957
tbouffard wants to merge 1 commit intomainfrom
feat/CellHandler_story_use_more_options

Conversation

@tbouffard
Copy link
Member

@tbouffard tbouffard commented Nov 24, 2025

Add new configuration options to VertexHandler that allow:

  • Customizable selection margins via options
  • Preview shapes that match the actual cell shape including shapes defined in stencils

Changes include:

  • Update getSelectionBounds to use margin from options
  • Update createSelectionShape to match cell shape styling
  • Add options support to VertexHandler story examples
  • Improve JSDoc documentation for new options

Screenshots

Margin
Preview_uses_shape_of_the_vertex.mp4

Summary by CodeRabbit

  • New Features

    • Selection shapes can now match vertex shapes using stencil-based rendering.
    • Added configurable margin settings for vertex selection handlers.
    • New configuration options for selection appearance and shape matching behavior.
  • Documentation

    • Updated documentation with improved formatting and reference links.

✏️ Tip: You can customize this high-level summary in your review settings.

Add new configuration options to CellHandler that allow:
- Customizable selection margins via options
- Preview shapes that match the actual cell shape including shapes defined in stencils

Changes include:
- Update getSelectionBounds to use margin from options
- Update createSelectionShape to match cell shape styling
- Add options support to CellHandler story examples
- Improve JSDoc documentation for new options
@tbouffard tbouffard added the enhancement New feature or request label Nov 24, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Walkthrough

This change introduces stencil-based selection shape support for vertex handlers. New configuration fields (margin, selectionShapeMatchVertex) are added to control selection border appearance. The VertexHandler implementation now attempts to create selection shapes from stencils when configured, with fallback to the existing rectangle shape. Configuration is applied to story examples, and documentation references are updated.

Changes

Cohort / File(s) Summary
Configuration additions
packages/core/src/view/handler/config.ts
Added two public fields to VertexHandlerConfig: margin: number (default 0) for adjustable selection bounds offset, and selectionShapeMatchVertex: boolean (default false) to enable stencil-based shape matching for selection borders.
Core handler implementation
packages/core/src/view/handler/VertexHandler.ts
Integrated stencil-based selection shapes: imports StencilShapeRegistry; createSelectionShape() now returns explicit Shape type and constructs shapes via stencil registry when selectionShapeMatchVertex is enabled, otherwise defaults to RectangleShape. Enhanced getSelectionBounds() to apply configurable margin adjustments. Updated isCenteredEvent() parameter names with underscore prefix to mark unused parameters.
Documentation updates
packages/core/src/view/event/InternalEvent.ts, packages/core/src/view/shape/Shape.ts
Updated JSDoc annotations: replaced plain type references with {@link} annotations in InternalEvent.ts; added @default tags and reference links in Shape.ts; minor internal refactor in Shape.paint() for outline handling (documentation and binding only, no behavioral change).
Story implementations
packages/html/stories/CustomHandlesConfiguration.stories.ts
Migrated vertex insertion calls from positional insertVertex(parent, null, value, x, y, width, height, style) to object-based insertVertex({ value, x, y, width, height, style }) syntax for six vertices (A1, A2, X1, A4, O1, v7). Updated vertex configurations with new VertexHandlerConfig properties (margin, selectionDashed, selectionShapeMatchVertex) and enriched style properties (perimeter, rounded, resizable, rotation, shape).
Story cleanup
packages/html/stories/Stencils.stories.ts
Removed unused Rectangle type import and custom createSelectionShape() override from CustomVertexHandler, relying instead on global VertexHandlerConfig.selectionShapeMatchVertex = true setting.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant VertexHandler
    participant VertexHandlerConfig
    participant StencilShapeRegistry
    participant Shape

    Caller->>VertexHandler: createSelectionShape(bounds)
    VertexHandler->>VertexHandlerConfig: check selectionShapeMatchVertex
    
    alt selectionShapeMatchVertex = true
        VertexHandler->>StencilShapeRegistry: getStencil(state)
        alt Stencil found
            StencilShapeRegistry-->>VertexHandler: stencil
            VertexHandler->>Shape: construct from stencil
            Shape-->>VertexHandler: shape
        else No stencil
            VertexHandler->>Shape: construct RectangleShape (fallback)
            Shape-->>VertexHandler: shape
        end
    else selectionShapeMatchVertex = false
        VertexHandler->>Shape: construct RectangleShape
        Shape-->>VertexHandler: shape
    end
    
    VertexHandler->>VertexHandlerConfig: check margin
    VertexHandler->>VertexHandler: getSelectionBounds(margin)
    VertexHandler-->>Caller: selection Shape
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • VertexHandler.ts: New conditional logic for stencil-based shape creation requires verification of fallback behavior, margin application, and integration with StencilShapeRegistry
  • config.ts: Two new public configuration fields need documentation review to ensure defaults and behavior descriptions are clear
  • Story files: Migration from positional to object-based insertVertex() calls across multiple vertices; verify all vertex configurations (perimeter, resizable, rounded properties) are correctly applied and intentional

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description lacks required template sections and provides minimal context about the changes. Add PR Checklist items (issue reference, scope confirmation, tests/demos), expand Overview with problem statement and keywords, and follow Conventional Commits format in the title if not already done.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding margin and preview shape matching options to VertexHandler, which aligns with the core modifications across multiple files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/CellHandler_story_use_more_options

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/view/handler/VertexHandler.ts (1)

54-55: Widen type declarations to Shape for fields assigned results of createSelectionShape()

The review comment is correct. The fields selectionBorder and parentHighlight in VertexHandler.ts are declared as RectangleShape, but they receive Shape instances from createSelectionShape() and createParentHighlightShape() (lines 431–459). This causes TypeScript strict type errors even though the runtime works.

Additionally, this affects:

  • EdgeHandler.ts line 205: parentHighlight: RectangleShape | null
  • CellState.ts line 150: parentHighlight: RectangleShape | null (shared across handlers)

All four fields should be widened to Shape:

-  selectionBorder: RectangleShape;
+  selectionBorder: Shape;

-  parentHighlight: RectangleShape | null = null;
+  parentHighlight: Shape | null = null;
🧹 Nitpick comments (3)
packages/core/src/view/handler/config.ts (1)

239-245: VertexHandlerConfig extensions are coherent with handler usage

The new margin and selectionShapeMatchVertex fields, plus the clarified selectionDashed docs, align with how VertexHandler consumes them. If you want to be extra explicit, you could mention that margin is interpreted in view (screen) units, but functionally this is fine as-is.

Also applies to: 259-265, 273-281

packages/core/src/view/handler/VertexHandler.ts (2)

431-459: Harden createSelectionShape against missing state.shape and improve fallback behavior

The new selectionShapeMatchVertex branch is great, but there are a couple of robustness details worth tightening:

  • new this.state.shape.constructor() assumes this.state.shape is always defined. If a consumer globally enables selectionShapeMatchVertex and some vertices have no state.shape (e.g. label-only/HTML cases), this will throw.
  • When falling back to this.state.shape.constructor, the new shape doesn't get apply(this.state), so shape-specific style (e.g. arc size, rounded flags) won't be available on the selection outline, unlike the stencil path.

You can make this safer and more consistent by:

  • Guarding against a missing state.shape and falling back to the rectangular selection for those cases.
  • Applying the state to non-stencil shapes as well.

For example:

   createSelectionShape(bounds: Rectangle): Shape {
-    let shape: Shape;
+    let shape: Shape;

     if (VertexHandlerConfig.selectionShapeMatchVertex) {
-      const stencil = StencilShapeRegistry.get(this.state.style.shape);
-
-      if (stencil) {
-        shape = new Shape(stencil);
-        shape.apply(this.state);
-      } else {
-        // @ts-ignore known to work at runtime
-        shape = new this.state.shape.constructor();
-      }
+      const stencil = StencilShapeRegistry.get(this.state.style.shape);
+
+      if (stencil) {
+        shape = new Shape(stencil);
+        shape.apply(this.state);
+      } else if (this.state.shape) {
+        // @ts-expect-error constructor signatures differ per shape type but work at runtime
+        shape = new (this.state.shape.constructor as typeof Shape)();
+        shape.apply(this.state);
+      } else {
+        shape = new RectangleShape(
+          Rectangle.fromRectangle(bounds),
+          NONE,
+          this.getSelectionColor()
+        );
+      }
     } else {
       shape = new RectangleShape(
         Rectangle.fromRectangle(bounds),
         NONE,
         this.getSelectionColor()
       );
     }

The rest of the configuration (outline, bounds, stroke, etc.) can remain as you have it.


411-419: Margin is not applied during interactive resize, only after reset/redraw

getSelectionBounds expands the initial selection bounds by VertexHandlerConfig.margin, and reset/redraw copy that into this.bounds. However, during resizeVertex you recompute this.bounds directly from the unscaled geometry without reapplying the margin, and drawPreview/getSelectionBorderBounds use this.bounds as-is.

Net effect: for non-zero margin, the selection border/handles will be flush with the vertex during drag, then “jump” outward by the margin once reset/redraw runs.

If you want the margin to be visually consistent while resizing, consider:

  • Keeping this.bounds as the geometry bounds; and
  • Applying the margin only in getSelectionBorderBounds() (and, if desired, the sizer bounds) instead of baking it into getSelectionBounds.

That would avoid the jump without changing geometry math.

Also applies to: 1070-1080, 2035-2037

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8357f9f and 244bd4a.

📒 Files selected for processing (6)
  • packages/core/src/view/event/InternalEvent.ts (1 hunks)
  • packages/core/src/view/handler/VertexHandler.ts (8 hunks)
  • packages/core/src/view/handler/config.ts (3 hunks)
  • packages/core/src/view/shape/Shape.ts (3 hunks)
  • packages/html/stories/CustomHandlesConfiguration.stories.ts (4 hunks)
  • packages/html/stories/Stencils.stories.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: tbouffard
Repo: maxGraph/maxGraph PR: 849
File: packages/html/stories/DragSource.stories.js:98-101
Timestamp: 2025-06-13T07:48:10.300Z
Learning: User tbouffard prefers answers in English; avoid switching to other languages in future replies.
📚 Learning: 2025-04-24T12:33:36.243Z
Learnt from: tbouffard
Repo: maxGraph/maxGraph PR: 776
File: packages/core/src/view/Graph.ts:77-83
Timestamp: 2025-04-24T12:33:36.243Z
Learning: All register functions in maxGraph (`registerDefaultShapes`, `registerDefaultEdgeStyles`, `registerDefaultPerimeters`, and `registerDefaultEdgeMarkers`) have internal state flags to prevent duplicate registrations, making them inherently idempotent.

Applied to files:

  • packages/core/src/view/shape/Shape.ts
🧬 Code graph analysis (3)
packages/html/stories/Stencils.stories.ts (1)
packages/core/src/view/handler/config.ts (1)
  • VertexHandlerConfig (232-281)
packages/core/src/view/handler/config.ts (1)
packages/core/src/util/Constants.ts (1)
  • VERTEX_SELECTION_DASHED (181-181)
packages/core/src/view/handler/VertexHandler.ts (2)
packages/core/src/view/shape/stencil/StencilShapeRegistry.ts (1)
  • StencilShapeRegistry (46-46)
packages/core/src/util/Constants.ts (1)
  • NONE (378-378)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build
  • GitHub Check: build (windows-2022)
  • GitHub Check: build (ubuntu-24.04)
  • GitHub Check: build (macos-14)
🔇 Additional comments (5)
packages/core/src/view/event/InternalEvent.ts (1)

232-236: Excellent JSDoc documentation improvements.

The updated documentation uses proper {@link} syntax for type references, which improves tooling support, IDE navigation, and documentation generation. The clarified description also better explains the method's behavior.

packages/core/src/view/shape/Shape.ts (1)

207-255: Outline and pointer-events refactor looks sound

The updated outline handling in createCanvas/paint and the new pointer-events flags (shapePointerEvents, stencilPointerEvents) preserve existing behavior while making outline-only rendering and event-capture rectangles more explicit. I don't see correctness or regression risks here.

Also applies to: 470-499, 575-587, 612-617

packages/html/stories/Stencils.stories.ts (1)

79-82: Stencil story wiring for selection-shape matching is appropriate

Using VertexHandlerConfig.selectionShapeMatchVertex = true in this story cleanly activates the new stencil-aware selection border behavior, and the added rotation comment is a nice hint without changing behavior. No issues here.

Also applies to: 88-93

packages/core/src/view/handler/VertexHandler.ts (1)

131-137: Minor doc and API-shape tweaks look good

The clarifications around livePreview, verticalOffset, isCenteredEvent (now explicitly unused params), and the extended JSDoc for handle padding/redrawing all match current behavior and improve readability without changing semantics.

Also applies to: 168-171, 370-372, 1721-1765

packages/html/stories/CustomHandlesConfiguration.stories.ts (1)

88-93: Story config and object-form vertex creation align well with the new handler options

Using VertexHandlerConfig.margin = 6, selectionDashed = false, and selectionShapeMatchVertex = true nicely showcases the new selection-border behavior in this custom-handles story. The switch to object-form insertVertex({ value, x, y, width, height, style }) for several vertices makes their capabilities (rotatable, pre-rotated, non-resizable, different perimeters/shapes) clearer while preserving the original graph structure. I don't see any functional regressions here.

Also applies to: 95-97, 126-156, 166-176, 188-193

Comment on lines +440 to +441
// @ts-ignore known to work at runtime
shape = new this.state.shape.constructor();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: review coderabit suggestion

Suggested change
// @ts-ignore known to work at runtime
shape = new this.state.shape.constructor();
// @ts-expect-error constructor signatures differ per shape type but work at runtime
shape = new (this.state.shape.constructor as typeof Shape)();
shape.apply(this.state);

@tbouffard tbouffard marked this pull request as draft December 11, 2025 08:49
/**
* Margin to be added between the selection border and the vertex's bounding box.
* @default 0
* @since 0.22.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since 0.23.0

* This mimics the behavior of {@link CellRenderer}: first check if the shape is available in the {@link StencilShapeRegistry}, otherwise use the shape of the vertex.
*
* @default false
* @since 0.22.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since 0.23.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant