@joint/core
Version:
JavaScript diagramming library
236 lines (226 loc) • 8.09 kB
JavaScript
import * as g from '../g/index.mjs';
import * as util from '../util/index.mjs';
import { ToolView } from '../dia/ToolView.mjs';
import { getAnchor, snapAnchor } from '../cellTools/helpers.mjs';
const Anchor = ToolView.extend({
tagName: 'g',
type: null,
children: [{
tagName: 'circle',
selector: 'anchor',
attributes: {
'cursor': 'pointer'
}
}, {
tagName: 'rect',
selector: 'area',
attributes: {
'pointer-events': 'none',
'fill': 'none',
'stroke': '#33334F',
'stroke-dasharray': '2,4',
'rx': 5,
'ry': 5
}
}],
events: {
mousedown: 'onPointerDown',
touchstart: 'onPointerDown',
dblclick: 'onPointerDblClick',
dbltap: 'onPointerDblClick'
},
documentEvents: {
mousemove: 'onPointerMove',
touchmove: 'onPointerMove',
mouseup: 'onPointerUp',
touchend: 'onPointerUp',
touchcancel: 'onPointerUp'
},
options: {
snap: snapAnchor,
anchor: getAnchor,
scale: null,
resetAnchor: true,
customAnchorAttributes: {
'stroke-width': 4,
'stroke': '#33334F',
'fill': '#FFFFFF',
'r': 5
},
defaultAnchorAttributes: {
'stroke-width': 2,
'stroke': '#FFFFFF',
'fill': '#33334F',
'r': 6
},
areaPadding: 6,
snapRadius: 10,
restrictArea: true,
redundancyRemoval: true
},
onRender: function() {
this.renderChildren();
this.toggleArea(false);
this.update();
},
update: function() {
var type = this.type;
var relatedView = this.relatedView;
var view = relatedView.getEndView(type);
if (view) {
this.updateAnchor();
this.updateArea();
this.el.style.display = '';
} else {
this.el.style.display = 'none';
}
return this;
},
updateAnchor: function() {
var childNodes = this.childNodes;
if (!childNodes) return;
var anchorNode = childNodes.anchor;
if (!anchorNode) return;
var relatedView = this.relatedView;
var type = this.type;
var position = relatedView.getEndAnchor(type);
var options = this.options;
var customAnchor = relatedView.model.prop([type, 'anchor']);
let transformString = `translate(${position.x},${position.y})`;
if (options.scale) {
transformString += ` scale(${options.scale})`;
}
anchorNode.setAttribute('transform', transformString);
var anchorAttributes = (customAnchor) ? options.customAnchorAttributes : options.defaultAnchorAttributes;
for (var attrName in anchorAttributes) {
anchorNode.setAttribute(attrName, anchorAttributes[attrName]);
}
},
updateArea: function() {
var childNodes = this.childNodes;
if (!childNodes) return;
var areaNode = childNodes.area;
if (!areaNode) return;
var relatedView = this.relatedView;
var type = this.type;
var view = relatedView.getEndView(type);
var model = view.model;
var magnet = relatedView.getEndMagnet(type);
var padding = this.options.areaPadding;
if (!isFinite(padding)) padding = 0;
var bbox, angle, center;
if (view.isNodeConnection(magnet)) {
bbox = view.getNodeBBox(magnet);
angle = 0;
center = bbox.center();
} else {
bbox = view.getNodeUnrotatedBBox(magnet);
angle = model.angle();
center = bbox.center();
if (angle) center.rotate(model.getBBox().center(), -angle);
// TODO: get the link's magnet rotation into account
}
bbox.inflate(padding);
areaNode.setAttribute('x', -bbox.width / 2);
areaNode.setAttribute('y', -bbox.height / 2);
areaNode.setAttribute('width', bbox.width);
areaNode.setAttribute('height', bbox.height);
areaNode.setAttribute('transform', 'translate(' + center.x + ',' + center.y + ') rotate(' + angle + ')');
},
toggleArea: function(visible) {
var childNodes = this.childNodes;
if (!childNodes) return;
var areaNode = childNodes.area;
if (!areaNode) return;
areaNode.style.display = (visible) ? '' : 'none';
},
onPointerDown: function(evt) {
if (this.guard(evt)) return;
evt.stopPropagation();
evt.preventDefault();
this.paper.undelegateEvents();
this.delegateDocumentEvents();
this.focus();
this.toggleArea(this.options.restrictArea);
this.relatedView.model.startBatch('anchor-move', { ui: true, tool: this.cid });
},
resetAnchor: function(anchor) {
var type = this.type;
var relatedModel = this.relatedView.model;
if (anchor) {
relatedModel.prop([type, 'anchor'], anchor, {
rewrite: true,
ui: true,
tool: this.cid
});
} else {
relatedModel.removeProp([type, 'anchor'], {
ui: true,
tool: this.cid
});
}
},
onPointerMove: function(evt) {
var relatedView = this.relatedView;
var type = this.type;
var view = relatedView.getEndView(type);
var model = view.model;
var magnet = relatedView.getEndMagnet(type);
var normalizedEvent = util.normalizeEvent(evt);
var coords = this.paper.clientToLocalPoint(normalizedEvent.clientX, normalizedEvent.clientY);
var snapFn = this.options.snap;
if (typeof snapFn === 'function') {
coords = snapFn.call(relatedView, coords, view, magnet, type, relatedView, this);
coords = new g.Point(coords);
}
if (this.options.restrictArea) {
if (view.isNodeConnection(magnet)) {
// snap coords to the link's connection
var pointAtConnection = view.getClosestPoint(coords);
if (pointAtConnection) coords = pointAtConnection;
} else {
// snap coords within node bbox
var bbox = view.getNodeUnrotatedBBox(magnet);
var angle = model.angle();
var origin = model.getBBox().center();
var rotatedCoords = coords.clone().rotate(origin, angle);
if (!bbox.containsPoint(rotatedCoords)) {
coords = bbox.pointNearestToPoint(rotatedCoords).rotate(origin, -angle);
}
}
}
var anchor;
var anchorFn = this.options.anchor;
if (typeof anchorFn === 'function') {
anchor = anchorFn.call(relatedView, coords, view, magnet, type, relatedView);
}
this.resetAnchor(anchor);
this.update();
},
onPointerUp: function(evt) {
const normalizedEvent = util.normalizeEvent(evt);
this.paper.delegateEvents();
this.undelegateDocumentEvents();
this.blur();
this.toggleArea(false);
var linkView = this.relatedView;
if (this.options.redundancyRemoval) linkView.removeRedundantLinearVertices({ ui: true, tool: this.cid });
linkView.checkMouseleave(normalizedEvent);
linkView.model.stopBatch('anchor-move', { ui: true, tool: this.cid });
},
onPointerDblClick: function() {
var anchor = this.options.resetAnchor;
if (anchor === false) return; // reset anchor disabled
if (anchor === true) anchor = null; // remove the current anchor
this.resetAnchor(util.cloneDeep(anchor));
this.update();
}
});
export const SourceAnchor = Anchor.extend({
name: 'source-anchor',
type: 'source'
});
export const TargetAnchor = Anchor.extend({
name: 'target-anchor',
type: 'target'
});