"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectionImpl = void 0; const x6_1 = require("@antv/x6"); class SelectionImpl extends x6_1.View { get graph() { return this.options.graph; } get boxClassName() { return this.prefixClassName(Private.classNames.box); } get $boxes() { return x6_1.Dom.children(this.container, this.boxClassName); } get handleOptions() { return this.options; } constructor(options) { super(); this.options = options; if (this.options.model) { this.options.collection = this.options.model.collection; } if (this.options.collection) { this.collection = this.options.collection; } else { this.collection = new x6_1.Collection([], { comparator: Private.depthComparator, }); this.options.collection = this.collection; } this.boxCount = 0; this.createContainer(); this.startListening(); } startListening() { const graph = this.graph; const collection = this.collection; this.delegateEvents({ [`mousedown .${this.boxClassName}`]: 'onSelectionBoxMouseDown', [`touchstart .${this.boxClassName}`]: 'onSelectionBoxMouseDown', }, true); graph.on('scale', this.onGraphTransformed, this); graph.on('translate', this.onGraphTransformed, this); graph.model.on('updated', this.onModelUpdated, this); collection.on('added', this.onCellAdded, this); collection.on('removed', this.onCellRemoved, this); collection.on('reseted', this.onReseted, this); collection.on('updated', this.onCollectionUpdated, this); collection.on('node:change:position', this.onNodePositionChanged, this); collection.on('cell:changed', this.onCellChanged, this); } stopListening() { const graph = this.graph; const collection = this.collection; this.undelegateEvents(); graph.off('scale', this.onGraphTransformed, this); graph.off('translate', this.onGraphTransformed, this); graph.model.off('updated', this.onModelUpdated, this); collection.off('added', this.onCellAdded, this); collection.off('removed', this.onCellRemoved, this); collection.off('reseted', this.onReseted, this); collection.off('updated', this.onCollectionUpdated, this); collection.off('node:change:position', this.onNodePositionChanged, this); collection.off('cell:changed', this.onCellChanged, this); } onRemove() { this.stopListening(); } onGraphTransformed() { this.updateSelectionBoxes(); } onCellChanged() { this.updateSelectionBoxes(); } onNodePositionChanged({ node, options, }) { const { showNodeSelectionBox, pointerEvents } = this.options; const { ui, selection, translateBy, snapped } = options; const allowTranslating = (showNodeSelectionBox !== true || (pointerEvents && this.getPointerEventsValue(pointerEvents) === 'none')) && !this.translating && !selection; const translateByUi = ui && translateBy && node.id === translateBy; if (allowTranslating && (translateByUi || snapped)) { this.translating = true; const current = node.position(); const previous = node.previous('position'); const dx = current.x - previous.x; const dy = current.y - previous.y; if (dx !== 0 || dy !== 0) { this.translateSelectedNodes(dx, dy, node, options); } this.translating = false; } } onModelUpdated({ removed }) { if (removed && removed.length) { this.unselect(removed); } } isEmpty() { return this.length <= 0; } isSelected(cell) { return this.collection.has(cell); } get length() { return this.collection.length; } get cells() { return this.collection.toArray(); } select(cells, options = {}) { options.dryrun = true; const items = this.filter(Array.isArray(cells) ? cells : [cells]); this.collection.add(items, options); return this; } unselect(cells, options = {}) { // dryrun to prevent cell be removed from graph options.dryrun = true; this.collection.remove(Array.isArray(cells) ? cells : [cells], options); return this; } reset(cells, options = {}) { if (cells) { if (options.batch) { const filterCells = this.filter(Array.isArray(cells) ? cells : [cells]); this.collection.reset(filterCells, Object.assign(Object.assign({}, options), { ui: true })); return this; } const prev = this.cells; const next = this.filter(Array.isArray(cells) ? cells : [cells]); const prevMap = {}; const nextMap = {}; prev.forEach((cell) => (prevMap[cell.id] = cell)); next.forEach((cell) => (nextMap[cell.id] = cell)); const added = []; const removed = []; next.forEach((cell) => { if (!prevMap[cell.id]) { added.push(cell); } }); prev.forEach((cell) => { if (!nextMap[cell.id]) { removed.push(cell); } }); if (removed.length) { this.unselect(removed, Object.assign(Object.assign({}, options), { ui: true })); } if (added.length) { this.select(added, Object.assign(Object.assign({}, options), { ui: true })); } if (removed.length === 0 && added.length === 0) { this.updateContainer(); } return this; } return this.clean(options); } clean(options = {}) { if (this.length) { if (options.batch === false) { this.unselect(this.cells, options); } else { this.collection.reset([], Object.assign(Object.assign({}, options), { ui: true })); } } return this; } setFilter(filter) { this.options.filter = filter; } setContent(content) { this.options.content = content; } startSelecting(evt) { // Flow: startSelecting => adjustSelection => stopSelecting evt = this.normalizeEvent(evt); // eslint-disable-line this.clean(); let x; let y; const graphContainer = this.graph.container; if (evt.offsetX != null && evt.offsetY != null && graphContainer.contains(evt.target)) { x = evt.offsetX; y = evt.offsetY; } else { const offset = x6_1.Dom.offset(graphContainer); const scrollLeft = graphContainer.scrollLeft; const scrollTop = graphContainer.scrollTop; x = evt.clientX - offset.left + window.pageXOffset + scrollLeft; y = evt.clientY - offset.top + window.pageYOffset + scrollTop; } x6_1.Dom.css(this.container, { top: y, left: x, width: 1, height: 1, }); this.setEventData(evt, { action: 'selecting', clientX: evt.clientX, clientY: evt.clientY, offsetX: x, offsetY: y, scrollerX: 0, scrollerY: 0, moving: false, }); this.delegateDocumentEvents(Private.documentEvents, evt.data); } filter(cells) { const filter = this.options.filter; return cells.filter((cell) => { if (Array.isArray(filter)) { return filter.some((item) => { if (typeof item === 'string') { return cell.shape === item; } return cell.id === item.id; }); } if (typeof filter === 'function') { return x6_1.FunctionExt.call(filter, this.graph, cell); } return true; }); } stopSelecting(evt) { const graph = this.graph; const eventData = this.getEventData(evt); const action = eventData.action; switch (action) { case 'selecting': { let width = x6_1.Dom.width(this.container); let height = x6_1.Dom.height(this.container); const offset = x6_1.Dom.offset(this.container); const origin = graph.pageToLocal(offset.left, offset.top); const scale = graph.transform.getScale(); width /= scale.sx; height /= scale.sy; const rect = new x6_1.Rectangle(origin.x, origin.y, width, height); const cells = this.getCellViewsInArea(rect).map((view) => view.cell); this.reset(cells, { batch: true }); this.hideRubberband(); break; } case 'translating': { const client = graph.snapToGrid(evt.clientX, evt.clientY); if (!this.options.following) { const data = eventData; this.updateSelectedNodesPosition({ dx: data.clientX - data.originX, dy: data.clientY - data.originY, }); } this.graph.model.stopBatch('move-selection'); this.notifyBoxEvent('box:mouseup', evt, client.x, client.y); break; } default: { this.clean(); break; } } } onMouseUp(evt) { const action = this.getEventData(evt).action; if (action) { this.stopSelecting(evt); this.undelegateDocumentEvents(); } } onSelectionBoxMouseDown(evt) { if (!this.options.following) { evt.stopPropagation(); } const e = this.normalizeEvent(evt); if (this.options.movable) { this.startTranslating(e); } const activeView = this.getCellViewFromElem(e.target); this.setEventData(e, { activeView }); const client = this.graph.snapToGrid(e.clientX, e.clientY); this.notifyBoxEvent('box:mousedown', e, client.x, client.y); this.delegateDocumentEvents(Private.documentEvents, e.data); } startTranslating(evt) { this.graph.model.startBatch('move-selection'); const client = this.graph.snapToGrid(evt.clientX, evt.clientY); this.setEventData(evt, { action: 'translating', clientX: client.x, clientY: client.y, originX: client.x, originY: client.y, }); } getRestrictArea() { const restrict = this.graph.options.translating.restrict; const area = typeof restrict === 'function' ? x6_1.FunctionExt.call(restrict, this.graph, null) : restrict; if (typeof area === 'number') { return this.graph.transform.getGraphArea().inflate(area); } if (area === true) { return this.graph.transform.getGraphArea(); } return area || null; } getSelectionOffset(client, data) { let dx = client.x - data.clientX; let dy = client.y - data.clientY; const restrict = this.getRestrictArea(); if (restrict) { const cells = this.collection.toArray(); const totalBBox = x6_1.Cell.getCellsBBox(cells, { deep: true }) || x6_1.Rectangle.create(); const minDx = restrict.x - totalBBox.x; const minDy = restrict.y - totalBBox.y; const maxDx = restrict.x + restrict.width - (totalBBox.x + totalBBox.width); const maxDy = restrict.y + restrict.height - (totalBBox.y + totalBBox.height); if (dx < minDx) { dx = minDx; } if (dy < minDy) { dy = minDy; } if (maxDx < dx) { dx = maxDx; } if (maxDy < dy) { dy = maxDy; } if (!this.options.following) { const offsetX = client.x - data.originX; const offsetY = client.y - data.originY; dx = offsetX <= minDx || offsetX >= maxDx ? 0 : dx; dy = offsetY <= minDy || offsetY >= maxDy ? 0 : dy; } } return { dx, dy, }; } updateElementPosition(elem, dLeft, dTop) { const strLeft = x6_1.Dom.css(elem, 'left'); const strTop = x6_1.Dom.css(elem, 'top'); const left = strLeft ? parseFloat(strLeft) : 0; const top = strTop ? parseFloat(strTop) : 0; x6_1.Dom.css(elem, 'left', left + dLeft); x6_1.Dom.css(elem, 'top', top + dTop); } updateSelectedNodesPosition(offset) { const { dx, dy } = offset; if (dx || dy) { if ((this.translateSelectedNodes(dx, dy), this.boxesUpdated)) { if (this.collection.length > 1) { this.updateSelectionBoxes(); } } else { const scale = this.graph.transform.getScale(); for (let i = 0, $boxes = this.$boxes, len = $boxes.length; i < len; i += 1) { this.updateElementPosition($boxes[i], dx * scale.sx, dy * scale.sy); } this.updateElementPosition(this.selectionContainer, dx * scale.sx, dy * scale.sy); } } } autoScrollGraph(x, y) { const scroller = this.graph.getPlugin('scroller'); if (scroller) { return scroller.autoScroll(x, y); } return { scrollerX: 0, scrollerY: 0 }; } adjustSelection(evt) { const e = this.normalizeEvent(evt); const eventData = this.getEventData(e); const action = eventData.action; switch (action) { case 'selecting': { const data = eventData; if (data.moving !== true) { x6_1.Dom.appendTo(this.container, this.graph.container); this.showRubberband(); data.moving = true; } const { scrollerX, scrollerY } = this.autoScrollGraph(e.clientX, e.clientY); data.scrollerX += scrollerX; data.scrollerY += scrollerY; const dx = e.clientX - data.clientX + data.scrollerX; const dy = e.clientY - data.clientY + data.scrollerY; const left = parseInt(x6_1.Dom.css(this.container, 'left') || '0', 10); const top = parseInt(x6_1.Dom.css(this.container, 'top') || '0', 10); x6_1.Dom.css(this.container, { left: dx < 0 ? data.offsetX + dx : left, top: dy < 0 ? data.offsetY + dy : top, width: Math.abs(dx), height: Math.abs(dy), }); break; } case 'translating': { const client = this.graph.snapToGrid(e.clientX, e.clientY); const data = eventData; const offset = this.getSelectionOffset(client, data); if (this.options.following) { this.updateSelectedNodesPosition(offset); } else { this.updateContainerPosition(offset); } if (offset.dx) { data.clientX = client.x; } if (offset.dy) { data.clientY = client.y; } this.notifyBoxEvent('box:mousemove', evt, client.x, client.y); break; } default: break; } this.boxesUpdated = false; } translateSelectedNodes(dx, dy, exclude, otherOptions) { const map = {}; const excluded = []; if (exclude) { map[exclude.id] = true; } this.collection.toArray().forEach((cell) => { cell.getDescendants({ deep: true }).forEach((child) => { map[child.id] = true; }); }); if (otherOptions && otherOptions.translateBy) { const currentCell = this.graph.getCellById(otherOptions.translateBy); if (currentCell) { map[currentCell.id] = true; currentCell.getDescendants({ deep: true }).forEach((child) => { map[child.id] = true; }); excluded.push(currentCell); } } this.collection.toArray().forEach((cell) => { if (!map[cell.id]) { const options = Object.assign(Object.assign({}, otherOptions), { selection: this.cid, exclude: excluded }); cell.translate(dx, dy, options); this.graph.model.getConnectedEdges(cell).forEach((edge) => { if (!map[edge.id]) { edge.translate(dx, dy, options); map[edge.id] = true; } }); } }); } getCellViewsInArea(rect) { const graph = this.graph; const options = { strict: this.options.strict, }; let views = []; if (this.options.rubberNode) { views = views.concat(graph.model .getNodesInArea(rect, options) .map((node) => graph.renderer.findViewByCell(node)) .filter((view) => view != null)); } if (this.options.rubberEdge) { views = views.concat(graph.model .getEdgesInArea(rect, options) .map((edge) => graph.renderer.findViewByCell(edge)) .filter((view) => view != null)); } return views; } notifyBoxEvent(name, e, x, y) { const data = this.getEventData(e); const view = data.activeView; this.trigger(name, { e, view, x, y, cell: view.cell }); } getSelectedClassName(cell) { return this.prefixClassName(`${cell.isNode() ? 'node' : 'edge'}-selected`); } addCellSelectedClassName(cell) { const view = this.graph.renderer.findViewByCell(cell); if (view) { view.addClass(this.getSelectedClassName(cell)); } } removeCellUnSelectedClassName(cell) { const view = this.graph.renderer.findViewByCell(cell); if (view) { view.removeClass(this.getSelectedClassName(cell)); } } destroySelectionBox(cell) { this.removeCellUnSelectedClassName(cell); if (this.canShowSelectionBox(cell)) { x6_1.Dom.remove(this.container.querySelector(`[data-cell-id="${cell.id}"]`)); if (this.$boxes.length === 0) { this.hide(); } this.boxCount = Math.max(0, this.boxCount - 1); } } destroyAllSelectionBoxes(cells) { cells.forEach((cell) => this.removeCellUnSelectedClassName(cell)); this.hide(); x6_1.Dom.remove(this.$boxes); this.boxCount = 0; } hide() { x6_1.Dom.removeClass(this.container, this.prefixClassName(Private.classNames.rubberband)); x6_1.Dom.removeClass(this.container, this.prefixClassName(Private.classNames.selected)); } showRubberband() { x6_1.Dom.addClass(this.container, this.prefixClassName(Private.classNames.rubberband)); } hideRubberband() { x6_1.Dom.removeClass(this.container, this.prefixClassName(Private.classNames.rubberband)); } showSelected() { x6_1.Dom.removeAttribute(this.container, 'style'); x6_1.Dom.addClass(this.container, this.prefixClassName(Private.classNames.selected)); } createContainer() { this.container = document.createElement('div'); x6_1.Dom.addClass(this.container, this.prefixClassName(Private.classNames.root)); if (this.options.className) { x6_1.Dom.addClass(this.container, this.options.className); } this.selectionContainer = document.createElement('div'); x6_1.Dom.addClass(this.selectionContainer, this.prefixClassName(Private.classNames.inner)); this.selectionContent = document.createElement('div'); x6_1.Dom.addClass(this.selectionContent, this.prefixClassName(Private.classNames.content)); x6_1.Dom.append(this.selectionContainer, this.selectionContent); x6_1.Dom.attr(this.selectionContainer, 'data-selection-length', this.collection.length); x6_1.Dom.prepend(this.container, this.selectionContainer); } updateContainerPosition(offset) { if (offset.dx || offset.dy) { this.updateElementPosition(this.selectionContainer, offset.dx, offset.dy); } } updateContainer() { const origin = { x: Infinity, y: Infinity }; const corner = { x: 0, y: 0 }; const cells = this.collection .toArray() .filter((cell) => this.canShowSelectionBox(cell)); cells.forEach((cell) => { const view = this.graph.renderer.findViewByCell(cell); if (view) { const bbox = view.getBBox({ useCellGeometry: true, }); origin.x = Math.min(origin.x, bbox.x); origin.y = Math.min(origin.y, bbox.y); corner.x = Math.max(corner.x, bbox.x + bbox.width); corner.y = Math.max(corner.y, bbox.y + bbox.height); } }); x6_1.Dom.css(this.selectionContainer, { position: 'absolute', pointerEvents: 'none', left: origin.x, top: origin.y, width: corner.x - origin.x, height: corner.y - origin.y, }); x6_1.Dom.attr(this.selectionContainer, 'data-selection-length', this.collection.length); const boxContent = this.options.content; if (boxContent) { if (typeof boxContent === 'function') { const content = x6_1.FunctionExt.call(boxContent, this.graph, this, this.selectionContent); if (content) { this.selectionContent.innerHTML = content; } } else { this.selectionContent.innerHTML = boxContent; } } if (this.collection.length > 0 && !this.container.parentNode) { x6_1.Dom.appendTo(this.container, this.graph.container); } else if (this.collection.length <= 0 && this.container.parentNode) { this.container.parentNode.removeChild(this.container); } } canShowSelectionBox(cell) { return ((cell.isNode() && this.options.showNodeSelectionBox === true) || (cell.isEdge() && this.options.showEdgeSelectionBox === true)); } getPointerEventsValue(pointerEvents) { return typeof pointerEvents === 'string' ? pointerEvents : pointerEvents(this.cells); } createSelectionBox(cell) { this.addCellSelectedClassName(cell); if (this.canShowSelectionBox(cell)) { const view = this.graph.renderer.findViewByCell(cell); if (view) { const bbox = view.getBBox({ useCellGeometry: true, }); const className = this.boxClassName; const box = document.createElement('div'); const pointerEvents = this.options.pointerEvents; x6_1.Dom.addClass(box, className); x6_1.Dom.addClass(box, `${className}-${cell.isNode() ? 'node' : 'edge'}`); x6_1.Dom.attr(box, 'data-cell-id', cell.id); x6_1.Dom.css(box, { position: 'absolute', left: bbox.x, top: bbox.y, width: bbox.width, height: bbox.height, pointerEvents: pointerEvents ? this.getPointerEventsValue(pointerEvents) : 'auto', }); x6_1.Dom.appendTo(box, this.container); this.showSelected(); this.boxCount += 1; } } } updateSelectionBoxes() { if (this.collection.length > 0) { this.boxesUpdated = true; this.confirmUpdate(); // this.graph.renderer.requestViewUpdate(this as any, 1, options) } } confirmUpdate() { if (this.boxCount) { this.hide(); for (let i = 0, $boxes = this.$boxes, len = $boxes.length; i < len; i += 1) { const box = $boxes[i]; const cellId = x6_1.Dom.attr(box, 'data-cell-id'); x6_1.Dom.remove(box); this.boxCount -= 1; const cell = this.collection.get(cellId); if (cell) { this.createSelectionBox(cell); } } this.updateContainer(); } return 0; } getCellViewFromElem(elem) { const id = elem.getAttribute('data-cell-id'); if (id) { const cell = this.collection.get(id); if (cell) { return this.graph.renderer.findViewByCell(cell); } } return null; } onCellRemoved({ cell }) { this.destroySelectionBox(cell); this.updateContainer(); } onReseted({ previous, current }) { this.destroyAllSelectionBoxes(previous); current.forEach((cell) => { this.listenCellRemoveEvent(cell); this.createSelectionBox(cell); }); this.updateContainer(); } onCellAdded({ cell }) { // The collection do not known the cell was removed when cell was // removed by interaction(such as, by "delete" shortcut), so we should // manually listen to cell's remove event. this.listenCellRemoveEvent(cell); this.createSelectionBox(cell); this.updateContainer(); } listenCellRemoveEvent(cell) { cell.off('removed', this.onCellRemoved, this); cell.on('removed', this.onCellRemoved, this); } onCollectionUpdated({ added, removed, options, }) { added.forEach((cell) => { this.trigger('cell:selected', { cell, options }); if (cell.isNode()) { this.trigger('node:selected', { cell, options, node: cell }); } else if (cell.isEdge()) { this.trigger('edge:selected', { cell, options, edge: cell }); } }); removed.forEach((cell) => { this.trigger('cell:unselected', { cell, options }); if (cell.isNode()) { this.trigger('node:unselected', { cell, options, node: cell }); } else if (cell.isEdge()) { this.trigger('edge:unselected', { cell, options, edge: cell }); } }); const args = { added, removed, options, selected: this.cells.filter((cell) => !!this.graph.getCellById(cell.id)), }; this.trigger('selection:changed', args); } // #endregion dispose() { this.clean(); this.remove(); this.off(); } } __decorate([ x6_1.View.dispose() ], SelectionImpl.prototype, "dispose", null); exports.SelectionImpl = SelectionImpl; // private // ------- var Private; (function (Private) { const base = 'widget-selection'; Private.classNames = { root: base, inner: `${base}-inner`, box: `${base}-box`, content: `${base}-content`, rubberband: `${base}-rubberband`, selected: `${base}-selected`, }; Private.documentEvents = { mousemove: 'adjustSelection', touchmove: 'adjustSelection', mouseup: 'onMouseUp', touchend: 'onMouseUp', touchcancel: 'onMouseUp', }; function depthComparator(cell) { return cell.getAncestors().length; } Private.depthComparator = depthComparator; })(Private || (Private = {})); //# sourceMappingURL=selection.js.map