Weave.js

WeaveNodesSnappingPlugin

Allow to snap nodes to guides and to themselves

Overview

The WeaveNodesSnappingPlugin class that enables smart snapping behavior when users move or resize nodes. It helps align nodes with each other, grids, or guides automatically, improving layout precision and visual consistency across the canvas.

Snapping provides users with subtle, intuitive assistance when organizing elements, making building clean and aligned interfaces much easier—especially in collaborative environments.

Guides can be:

  • STATIC: are guides that can be pre-defined, but the user cannot move, for example the center of a frame or its edges.

  • CUSTOM: are guides that users can create, edit and delete.

The class extends the WeavePlugin class

Name

This plugin name property value is nodesSnapping.

Import

import { WeaveNodesSnappingPlugin } from "@inditextech/weave-sdk";

Instantiation

new WeaveNodesSnappingPlugin(params?: WeaveNodesSnappingPluginParams);

TypeScript types

const GUIDE_STATE = {
  DEFAULT: "default",
  SELECTED: "selected",
} as const;

const GUIDE_KIND = {
  STATIC: "static",
  CUSTOM: "custom",
  EQUAL_DISTANCE: "equal-distance",
  CENTERED_HORIZONTAL: "centered-horizontal",
  CENTERED_VERTICAL: "centered-vertical",
} as const;

const GUIDE_ORIENTATION = {
  ["HORIZONTAL"]: "H",
  ["VERTICAL"]: "V",
} as const;

const GUIDE_DISTANCE_ORIGIN = {
  FROM: "from",
  TO: "to",
} as const;

const MOVE_ORIENTATION = {
  ["UP"]: "up",
  ["DOWN"]: "down",
  ["LEFT"]: "left",
  ["RIGHT"]: "right",
} as const;

const DEFAULT_SNAPPING_MANAGER_CONFIG: WeaveNodesSnappingPluginConfig = {
  snap: {
    tolerance: 5,
  },
  persistence: {
    enabled: false,
  },
  movement: {
    delta: 0.5,
    shiftDelta: 10,
  },
  style: {
    [GUIDE_KIND.CUSTOM]: {
      default: {
        stroke: "#FF3B30",
        strokeWidth: 0.5,
        dash: [],
        opacity: 1,
      },
      selected: {
        stroke: "#0D99FF",
        strokeWidth: 1,
        dash: [],
        opacity: 1,
      },
    },
    [GUIDE_KIND.STATIC]: {
      default: {
        stroke: "#FF3B30",
        strokeWidth: 0.5,
        dash: [6, 6],
        opacity: 1,
      },
      selected: {
        stroke: "#0D99FF",
        strokeWidth: 1,
        dash: [6, 6],
        opacity: 1,
      },
    },
  },
  targetDistanceStyle: {
    target: {
      stroke: "#FF3B30",
      strokeWidth: 1,
      dash: [],
      opacity: 1,
    },
    distance: {
      opacity: 1,
      line: {
        stroke: "#FF3B30",
        strokeWidth: 1,
        dash: [],
        opacity: 1,
      },
      text: {
        fill: "#ffffff",
        fontSize: 10,
        fontFamily: "monospace",
        opacity: 1,
      },
      background: {
        fill: "#FF3B30",
        cornerRadius: 4,
        stroke: "#FF3B30",
        strokeWidth: 0,
        opacity: 1,
      },
    },
  },
};

type MoveOrientationKeys = keyof typeof MOVE_ORIENTATION;
type MoveOrientation = (typeof MOVE_ORIENTATION)[MoveOrientationKeys];

type GuideKindKeys = keyof typeof GUIDE_KIND;
type GuideKind = (typeof GUIDE_KIND)[GuideKindKeys];

type DistanceOriginKeys = keyof typeof GUIDE_DISTANCE_ORIGIN;
type DistanceOrigin = (typeof GUIDE_DISTANCE_ORIGIN)[DistanceOriginKeys];

type GuideOrientationKeys = keyof typeof GUIDE_ORIENTATION;
type GuideOrientation = (typeof GUIDE_ORIENTATION)[GuideOrientationKeys];

type Guide = {
  orientation: GuideOrientation;
  value: number;
  renderValue?: number;
  kind: GuideKind;
  guideId: string;
  containerId: string;
  persist?: boolean;
} & (
  | {
      kind: "static" | "custom";
    }
  | {
      kind: "equal-distance";
      distanceCombinationIndex: number;
      distanceOrigin: DistanceOrigin;
      distance: number;
    }
  | {
      kind: "centered-horizontal" | "centered-vertical";
      distance: number;
      center: {
        from: BoundingBoxWithId;
        center: BoundingBoxWithId;
        to: BoundingBoxWithId;
      };
    }
);

type SnapPoint = {
  orientation: GuideOrientation;
  guideId: string;
  value: number;
  offset: number;
  kind: GuideKind;
};

type SnapMatch = {
  orientation: GuideOrientation;
  guideId: string;
  containerId: string;
  guide: number;
  offset: number;
  diff: number;
} & (
  | {
      kind: "static" | "custom";
    }
  | {
      kind: "equal-distance";
      distanceCombinationIndex: number;
      distanceOrigin: DistanceOrigin;
      distance: number;
    }
  | {
      kind: "centered-horizontal" | "centered-vertical";
      distance: number;
      center: {
        from: BoundingBoxWithId;
        center: BoundingBoxWithId;
        to: BoundingBoxWithId;
      };
    }
);

type SnapResult = {
  vertical?: SnapMatch;
  horizontal?: SnapMatch;
};

type SnapOptions = {
  tolerance: number;
};

type VisibleWorldRect = {
  x: number;
  y: number;
  width: number;
  height: number;
};

type WeaveNodesSnappingPluginParams = {
  config?: DeepPartial<WeaveNodesSnappingPluginConfig>;
};

type WeaveNodesSnappingPluginConfig = {
  snap: SnapOptions;
  persistence: SnappingManagerPersistenceConfig;
  movement: SnappingManagerMovementConfig;
  style: SnappingManagerStyle;
  targetDistanceStyle: GuideDistanceToTargetInfoStyle;
  getStaticGuides?: (params: {
    instance: Weave;
    containerId: string;
  }) => Guide[];
};

type SnappingManagerKindStyle = {
  default: {
    stroke: string;
    strokeWidth: number;
    opacity: number;
    dash?: number[];
  };
  selected: {
    stroke: string;
    strokeWidth: number;
    opacity: number;
    dash?: number[];
  };
};

type GuideKindOnlyCustomOrStatic = Exclude<
  GuideKind,
  "centered-horizontal" | "centered-vertical" | "equal-distance"
>;

type SnappingManagerStyle = Record<
  GuideKindOnlyCustomOrStatic,
  SnappingManagerKindStyle
>;

type SnappingManagerPersistenceConfig = {
  enabled: boolean;
};

type SnappingManagerMovementConfig = {
  delta: number;
  shiftDelta: number;
};

type WeaveGetStaticGuidesFunction = (params: {
  instance: Weave;
  containerId: string;
}) => Guide[];

type WeaveNodesSnappingCustomGuidesConfig = {
  persistence: SnappingManagerPersistenceConfig;
  movement: SnappingManagerMovementConfig;
  style: SnappingManagerStyle;
  targetDistanceStyle: GuideDistanceToTargetInfoStyle;
  getStaticGuides?: WeaveGetStaticGuidesFunction;
};

type GuideDistanceToTargetInfoParams = {
  config: GuideDistanceToTargetInfoConfig;
};

type GuideDistanceToTargetInfoConfig = {
  style: GuideDistanceToTargetInfoStyle;
};

type GuideDistanceToTargetInfoStyle = {
  target: {
    stroke: string;
    strokeWidth: number;
    opacity: number;
    dash?: number[];
  };
  distance: {
    opacity: number;
    line: {
      stroke: string;
      strokeWidth: number;
      opacity: number;
      dash?: number[];
    };
    text: {
      fill: string;
      fontSize: number;
      fontFamily: string;
      opacity: number;
    };
    background: {
      fill: string;
      cornerRadius: number;
      stroke: string;
      strokeWidth: number;
      opacity: number;
    };
  };
};

type DistanceInfoH = {
  from: { id: string; box: BoundingBox };
  to: { id: string; box: BoundingBox };
  midY: number;
  distance: number;
};

type DistanceInfoV = {
  from: { id: string; box: BoundingBox };
  to: { id: string; box: BoundingBox };
  midX: number;
  distance: number;
};

type BoundingBoxWithId = {
  id: string;
  box: BoundingBox;
};

type HIntersection = {
  combination: BoundingBoxWithId[];
  targetIndex: number;
  distances: DistanceInfoH[];
  targetDistanceIndexes: number[];
};

type VIntersection = {
  combination: BoundingBoxWithId[];
  targetIndex: number;
  distances: DistanceInfoV[];
  targetDistanceIndexes: number[];
};

Parameters

For WeaveNodesSnappingPluginParams:

Property Type Required Description

config

WeaveNodesSnappingPluginConfig

Config parameters for the plugin.

For WeaveNodesSnappingPluginConfig:

Property Type Required Default Description

snap.tolerance

number

5

Tolerance in px to magnet-snap towards guides.

persistence.enabled

SnappingManagerPersistenceConfig

true

Defines if the guides need to be persisted.

movement.delta

number

0.5

Defines the delta in px when moving the guides via keyboard.

movement.shiftDelta

number

10

Defines the delta in px when moving the guides via keyboard with shift pressed.

style

SnappingManagerStyle

DEFAULT_SNAPPING_MANAGER_CONFIG.style

Defines the UI style of the guides.

targetDistanceStyle

GuideDistanceToTargetInfoStyle

DEFAULT_SNAPPING_MANAGER_CONFIG.targetDistanceStyle

Defines the UI style to show the distance from the selected target when creating / updating a custom guide.

getStaticGuides

WeaveGetStaticGuidesFunction

undefined

Function that define the static guides to render.