function groupEvtProc(canvas, selected) {
	// only one shape selected
	if (selected.shapes?.length === 1 && !selected.pathEnds?.length) {
		classDel(selected.shapes[0], 'highlight');
		shapeSelect(selected.shapes[0]);
		return;
	}

	// only one pathEnd selected
	if (!selected.shapes?.length && selected.pathEnds?.length === 1) {
		pathUnhighlight(selected.pathEndsPaths[0]);
		return;
	}

	// only one path selected
	if (!selected.shapes?.length && selected.pathEnds?.length === 2 && selected.pathEndsPaths?.length === 1) {
		pathUnhighlight(selected.pathEndsPaths[0]);
		shapeSelect(selected.pathEndsPaths[0]);
		return;
	}

	const svg = canvas.ownerSVGElement;
	let isMove = false;
	let isDownOnSelectedShape = false;

	/** @type {{del():void}} */
	let settingsPnl;
	const pnlDel = () => { settingsPnl?.del(); settingsPnl = null; };

	/** @param {PointerEvent & {target:Node}} evt */
	function down(evt) {
		pnlDel();
		isDownOnSelectedShape =
			selected.shapes?.some(shapeEl => shapeEl.contains(evt.target)) ||
			selected.pathEnds?.some(pathEnd => pathEnd.el.contains(evt.target));

		// down on not selected shape
		if (!isDownOnSelectedShape && evt.target !== svg) {
			dispose();
			return;
		}

		if (isDownOnSelectedShape) {
			evt.stopImmediatePropagation();
		}

		svg.setPointerCapture(evt.pointerId);
		listen(svg, 'pointerup', up, true);
		listen(svg, 'pointermove', move);
	}

	/** @param { {(point:Point):void} } pointMoveFn */
	function drawSelection(pointMoveFn) {
		selected.shapes?.forEach(shapeEl => {
			pointMoveFn(shapeEl[ShapeSmbl].data.position);
			shapeEl[ShapeSmbl].drawPosition();
		});
		selected.pathEnds?.forEach(pathEnd => pointMoveFn(pathEnd.data.position));
		selected.pathEndsPaths?.forEach(path => path[PathSmbl].draw());
	}

	/** @param {PointerEvent} evt */
	function up(evt) {
		if (!isMove) {
			// click on canvas
			if (!isDownOnSelectedShape) { dispose(); return; }

			// click on selected shape - show settings panel
			settingsPnl = modalCreate(evt.clientX - 10, evt.clientY - 10, new GroupSettings(cmd => {
				switch (cmd) {
					case 'del':
						arrPop(selected.shapes, shapeEl => shapeEl[ShapeSmbl].del());
						arrPop(selected.pathEndsPaths, pathEl => pathEl[PathSmbl].del());
						dispose();
						break;
					case 'copy': {
						copyAndPast(canvas, elemsToCopyGet(selected)); // will call dispose
						break;
					}
				}
			}));
		} else {
			// move end
			drawSelection(point => placeToCell(point, canvas[CanvasSmbl].data.cell));
		}

		dispose(true);
	}

	/** @param {PointerEventFixMovement} evt */
	function move(evt) {
		// move canvas
		if (!isDownOnSelectedShape) { dispose(true); return; }

		// move selected shapes
		isMove = true;
		drawSelection(point => movementApplay(point, canvas[CanvasSmbl].data.scale, evt));
	}

	/** @param {boolean=} saveOnDown */
	function dispose(saveOnDown) {
		listenDel(svg, 'pointerup', up);
		listenDel(svg, 'pointermove', move);
		isMove = false;
		isDownOnSelectedShape = false;

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

			listenDel(svg, 'pointerdown', down, true);
			pnlDel();
			arrPop(selected.shapes, shapeEl => classDel(shapeEl, highlightClass));
			arrPop(selected.pathEndsPaths, pathEl => pathUnhighlight(pathEl));
			selected.pathEnds = null;
			selected.shapesPaths = null;
		}
	}

	svg.addEventListener('pointerdown', down, { passive: true, capture: true });

	canvasSelectionClearSet(canvas, dispose);
	let listenCopyDispose = listenCopy(() => elemsToCopyGet(selected));
}
@param {CanvasElement} canvas
@param {Selected} selected