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:

PropTypeDefault
config?
WeaveNodesSnappingPluginConfig
-

For WeaveNodesSnappingPluginConfig:

PropTypeDefault
getStaticGuides?
WeaveGetStaticGuidesFunction
undefined
targetDistanceStyle?
GuideDistanceToTargetInfoStyle
DEFAULT_SNAPPING_MANAGER_CONFIG.targetDistanceStyle
style?
SnappingManagerStyle
DEFAULT_SNAPPING_MANAGER_CONFIG.style
movement.shiftDelta?
number
10
movement.delta?
number
0.5
persistence.enabled?
SnappingManagerPersistenceConfig
true
snap.tolerance?
number
5