function shapeEvtProc(canvas, svgGrp, shapeData, connectorsInnerPosition, onEdit, onUnselect) {
	classAdd(svgGrp, 'hovertrack');

	/** @type {ConnectorsData} */
	const connectorsData = deepCopy(connectorsInnerPosition);

	/** @type { Set<PathElement> } */
	const paths = new Set();

	function drawPosition() {
		svgGrp.style.transform = `translate(${shapeData.position.x}px, ${shapeData.position.y}px)`;

		// paths
		for (const connectorKey in connectorsInnerPosition) {
			connectorsData[connectorKey].position = {
				x: connectorsInnerPosition[connectorKey].position.x + shapeData.position.x,
				y: connectorsInnerPosition[connectorKey].position.y + shapeData.position.y
			};
		}

		for (const path of paths) {
			path[PathSmbl].draw();
		}
	};

	/**
	 * @type {0|1|2}
	 * 0 - init, 1 - selected, 2 - edit
	*/
	let state = 0;

	/** @type {()=>void} */
	let listenCopyDispose;

	function unSelect() {
		onUnselect();

		state = 0;
		classDel(svgGrp, 'select');
		classDel(svgGrp, 'highlight');

		canvasSelectionClearSet(canvas, null);
		if (listenCopyDispose) { listenCopyDispose(); listenCopyDispose = null;	}
	}

	const moveProcReset = moveEvtProc(
		canvas.ownerSVGElement,
		svgGrp,
		canvas[CanvasSmbl].data,
		shapeData.position,
		// onMoveStart
		/** @param {PointerEvent & { target: Element} } evt */
		evt => {
			unSelect();

			const connectorKey = evt.target.getAttribute('data-connect');
			if (connectorKey) {
				moveProcReset();

				const pathEl = path(canvas, {
					s: { shape: { shapeEl: svgGrp, connectorKey } },
					e: {
						data: {
							dir: dirReverse(connectorsData[connectorKey].dir),
							position: pointInCanvas(canvas[CanvasSmbl].data, evt.clientX, evt.clientY)
						}
					}
				});
				svgGrp.parentNode.append(pathEl);
				pathEl[PathSmbl].pointerCapture(evt);
				paths.add(pathEl);
			}
		},
		// onMove
		drawPosition,
		// onMoveEnd
		_ => {
			placeToCell(shapeData.position, canvas[CanvasSmbl].data.cell);
			drawPosition();
		},
		// onClick
		_ => {
			// in edit mode
			if (state === 2) { return; }

			// to edit mode
			if (state === 1) {
				state = 2;
				classDel(svgGrp, 'select');
				classAdd(svgGrp, 'highlight');
				// edit mode
				onEdit();
				return;
			}

			// to select mode
			state = 1;
			classAdd(svgGrp, 'select');

			canvasSelectionClearSet(canvas, unSelect);
			listenCopyDispose = listenCopy(() => [svgGrp]);
		},
		// onOutdown
		unSelect);

	svgGrp[ShapeSmbl] = {
		/**
		 * @param {string} connectorKey
		 * @param {PathElement} pathEl
		 */
		pathAdd: function(connectorKey, pathEl) {
			paths.add(pathEl);
			return connectorsData[connectorKey];
		},

		/** @param {PathElement} pathEl */
		pathDel: function(pathEl) {
			paths.delete(pathEl);
		},

		drawPosition,

		data: shapeData
	};

	return {
		drawPosition,
		del: () => {
			unSelect();
			moveProcReset();
			for (const path of paths) {
				path[PathSmbl].del();
			}
		}
	};
}
provides:
 - shape move
 - connectors
 - copy fn
 - onEdit, onEditStop callbacks
@param {CanvasElement} canvas
@param {ShapeElement} svgGrp
@param {ShapeData} shapeData
@param {ConnectorsData} connectorsInnerPosition
@param {{():void}} onEdit
@param {{():void}} onUnselect