Skip to content

Make EdgeHandler registration in SelectionCellsHandler optional and modular #890

@tbouffard

Description

@tbouffard

Is your feature request related to a problem? Please describe.

Currently, all EdgeHandler implementations are always loaded, even if they are not used. This prevents proper tree-shaking and adds unnecessary weight to applications that don’t use all EdgeStyles.

EdgeHandlers are used to manage interactive behavior on selected edges. maxGraph provides 3 built-in handlers to match the main EdgeStyle implementations.

Historically, handler selection was based on:

  • the EdgeStyle implementation,
  • or a handlerKind category defined at registration (v0.20.0).

Historically, the creation logic for handlers lived in factory methods (create...Handler) on AbstractGraph. This had several drawbacks:

  • all handlers were always imported, even if unused (e.g. for visualization-only use cases),
  • customizing the handler meant subclassing both the handler and the factory methods.

Once #823 has been merged, those methods are removed from AbstractGraph, and tree-shaking is now possible when the SelectionCellsHandler plugin is not used.

Still, when the SelectionCellsHandler plugin is used, all EdgeHandler classes are declared and imported, even if some of the corresponding EdgeStyles are not registered or used.

This limits optimization, and we’d like to improve that further.


With tree-shaking enabled, only loading the necessary EdgeHandlers can decrease the bundle size.
The following figures are estimates done in #823:

Handlers used Decreased size
Only default EdgeHandler −7–8 kB
Only ElbowEdgeHandler −5 kB (inherits EdgeHandler)
Only SegmentEdgeHandler −0 kB (inherits Elbow)

Note

These numbers reflect the size reduction compared to the previous behavior, where all handlers were always bundled.


Describe the solution you'd like

We’d like to:

  • only load the handlers actually used in the application, so avoid importing unused EdgeHandlers. Currently, in the ts-example-selected-features, the SegmentEdgeHandler is unused but still included.
  • allow customizing handler behavior without subclassing them (prefer composition),
  • configure them per Graph instance (local configuration, not global).

A minimal app that only uses the default connector should only load the default handler.


Describe alternatives you've considered

✅ Plugin with two variants

Have a base version of SelectionCellsHandler that only registers the default handler, and a second one (the current one) that registers all 3 handlers.

  • BaseSelectionCellsHandler: only registers EdgeHandler
  • SelectionCellsHandler: extends base and adds ElbowEdgeHandler and SegmentEdgeHandler

Warning

Both plugins would share the same plugin id for compatibility, but this may lead to confusion if misused.

Limitation: doesn’t support composability. Users can’t change the config of existing handlers without subclassing the plugin.


✅ Register handlers from the Graph constructor

Let Graph be responsible for registering the handlers. It would look up the plugin and register default handlers after plugin initialization.

  • BaseGraph would not do this
  • this promotes composition over inheritance
  • preserves current default behavior when using Graph

Note

This would need to be clearly documented, since the behavior differs slightly from current defaults.


❌ Global registry

Store the handler map globally, like existing registries.

Drawback: goes against the local configuration pattern we introduced for plugins. Would also introduce implicit behavior, harder to debug.


✅ Dedicated plugin for handler selection

Introduce EdgeHandlerSelectorPlugin that stores the handler mapping.

  • SelectionCellsHandler uses it internally to retrieve the correct handler
  • for default behavior, we include EdgeHandlerSelectorPlugin in the default plugin list

✅ Pros:

  • a single implementation of the selection plugin
  • flexible and composable
  • handler registration is explicit

⚠️ Cons:

  • adds a dependency between plugins
  • when using a custom plugin list, users must remember to include it
  • overall complexity isn’t lower than having two plugin variants

Additional context

This topic has been previously discussed:

It also relates to ongoing improvements for tree-shaking and plugin modularity.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions