UNPKG

@ichigo_san/graphing

Version:

A lightweight UML-style diagram editor built with React Flow and Tailwind CSS

254 lines (227 loc) 7.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SIDE_VALUES = exports.SIDES = exports.EdgeConfig = exports.EDGE_STYLE_VALUES = exports.EDGE_STYLES = void 0; exports.createDefaultEdge = createDefaultEdge; exports.createDefaultPort = createDefaultPort; exports.default = void 0; exports.isValidEdgeStyle = isValidEdgeStyle; exports.isValidPoint = isValidPoint; exports.isValidPort = isValidPort; exports.isValidSide = isValidSide; exports.validateEdge = validateEdge; /** * Edge & Arrow System - Core Types and Constants * Module A1: Define types: Edge, Port, Label, Side, EdgeStyle, Point + config constants */ // ============================================================================ // CORE TYPES // ============================================================================ /** * @typedef {'N' | 'E' | 'S' | 'W'} Side * Cardinal directions for port constraints */ /** * @typedef {'orthogonal' | 'segment' | 'straight' | 'elbow'} EdgeStyle * Edge routing and editing styles */ /** * @typedef {Object} Point * @property {number} x - X coordinate * @property {number} y - Y coordinate */ /** * @typedef {Object} Port * @property {Side} [side] - Side constraint (N/E/S/W) * @property {boolean} [sticky] - Whether to persist side across reroutes * @property {number} [align] - Alignment fraction 0..1 along the side * @property {number} [x] - Fixed X coordinate (node-relative, overrides side) * @property {number} [y] - Fixed Y coordinate (node-relative, overrides side) */ /** * @typedef {Object} Label * @property {string} text - Label text content * @property {number} t - Position along polyline (0..1) * @property {number} dx - X offset in screen space * @property {number} dy - Y offset in screen space */ /** * @typedef {Object} EdgeMeta * @property {number} [parallelIndex] - Index for parallel edge spacing * @property {Object} [custom] - Custom metadata */ /** * @typedef {Object} Edge * @property {string} id - Unique edge identifier * @property {string} source - Source node ID * @property {string} target - Target node ID * @property {EdgeStyle} style - Edge routing style * @property {Label} [label] - Edge label * @property {Point[]} [waypoints] - Intermediate points (model coordinates) * @property {Port} [sourcePort] - Source port configuration * @property {Port} [targetPort] - Target port configuration * @property {EdgeMeta} [meta] - Edge metadata */ // ============================================================================ // CONFIGURATION CONSTANTS // ============================================================================ /** * Edge system configuration constants * These match the requirements specification */ const EdgeConfig = exports.EdgeConfig = { // Spacing and sizing JETTY_SIZE: 20, // Connection extension from node perimeter ORTH_BUFFER: 10, // Base spacing for orthogonal routing PARALLEL_SPACING: 8, // Spacing between parallel edges OVERLAP_NUDGE: 6, // Minimal offset to avoid exact overlap MIN_SEGMENT_FOR_HANDLE: 24, // Minimum segment length for virtual handles // Tolerance and precision SNAP_TOLERANCE: 6, // Grid snapping tolerance COLLINEAR_EPS: 0.75, // Collinearity detection epsilon WAYPOINT_SOFT_CAP: 12, // Soft limit on waypoints per edge // Performance FRAME_BUDGET_MS: 16, // Target frame time (60fps) DRAG_DEBOUNCE_MS: 16 // Drag update debounce }; // ============================================================================ // SIDE CONSTANTS // ============================================================================ const SIDES = exports.SIDES = { NORTH: 'N', EAST: 'E', SOUTH: 'S', WEST: 'W' }; const SIDE_VALUES = exports.SIDE_VALUES = Object.values(SIDES); // ============================================================================ // EDGE STYLES // ============================================================================ const EDGE_STYLES = exports.EDGE_STYLES = { ORTHOGONAL: 'orthogonal', SEGMENT: 'segment', STRAIGHT: 'straight', ELBOW: 'elbow' }; const EDGE_STYLE_VALUES = exports.EDGE_STYLE_VALUES = Object.values(EDGE_STYLES); // ============================================================================ // TYPE GUARDS // ============================================================================ /** * Type guard to check if a value is a valid Side */ function isValidSide(value) { return SIDE_VALUES.includes(value); } /** * Type guard to check if a value is a valid EdgeStyle */ function isValidEdgeStyle(value) { return EDGE_STYLE_VALUES.includes(value); } /** * Type guard to check if a value is a valid Point */ function isValidPoint(value) { return value !== null && value !== undefined && typeof value === 'object' && typeof value.x === 'number' && typeof value.y === 'number' && !isNaN(value.x) && !isNaN(value.y); } /** * Type guard to check if a value is a valid Port */ function isValidPort(value) { if (!value || typeof value !== 'object') return false; // Must have either side or fixed coordinates const hasSide = value.side && isValidSide(value.side); const hasFixed = typeof value.x === 'number' && typeof value.y === 'number'; return hasSide || hasFixed; } // ============================================================================ // UTILITY FUNCTIONS // ============================================================================ /** * Create a default port configuration */ function createDefaultPort(side = SIDES.EAST) { return { side, sticky: true, align: 0.5 }; } /** * Create a default edge configuration */ function createDefaultEdge(id, source, target, style = EDGE_STYLES.ORTHOGONAL) { return { id, source, target, style, sourcePort: createDefaultPort(SIDES.EAST), targetPort: createDefaultPort(SIDES.WEST), waypoints: [], meta: { parallelIndex: 0, custom: {} } }; } /** * Validate an edge object against the schema */ function validateEdge(edge) { if (!edge || typeof edge !== 'object') { throw new Error('Edge must be an object'); } if (!edge.id || typeof edge.id !== 'string') { throw new Error('Edge must have a string id'); } if (!edge.source || typeof edge.source !== 'string') { throw new Error('Edge must have a string source'); } if (!edge.target || typeof edge.target !== 'string') { throw new Error('Edge must have a string target'); } if (!isValidEdgeStyle(edge.style)) { throw new Error(`Invalid edge style: ${edge.style}`); } if (edge.sourcePort && !isValidPort(edge.sourcePort)) { throw new Error('Invalid sourcePort configuration'); } if (edge.targetPort && !isValidPort(edge.targetPort)) { throw new Error('Invalid targetPort configuration'); } if (edge.waypoints && !Array.isArray(edge.waypoints)) { throw new Error('waypoints must be an array'); } if (edge.waypoints) { for (let i = 0; i < edge.waypoints.length; i++) { if (!isValidPoint(edge.waypoints[i])) { throw new Error(`Invalid waypoint at index ${i}`); } } } return true; } var _default = exports.default = { EdgeConfig, SIDES, EDGE_STYLES, isValidSide, isValidEdgeStyle, isValidPoint, isValidPort, createDefaultPort, createDefaultEdge, validateEdge };